twig 1.4 → 1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY.md +15 -0
- data/README.md +17 -4
- data/bin/twig-gh-open +1 -1
- data/bin/twig-gh-open-issue +1 -1
- data/bin/twig-gh-update +1 -1
- data/bin/twig-help +1 -1
- data/bin/twig-init-completion-bash +22 -3
- data/bin/twig-rebase +1 -1
- data/lib/twig.rb +23 -13
- data/lib/twig/branch.rb +24 -9
- data/lib/twig/cli.rb +84 -25
- data/lib/twig/commit_time.rb +10 -0
- data/lib/twig/display.rb +9 -0
- data/lib/twig/options.rb +88 -46
- data/lib/twig/subcommands.rb +26 -0
- data/lib/twig/system.rb +9 -0
- data/lib/twig/version.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/twig/branch_spec.rb +134 -60
- data/spec/twig/cli_spec.rb +247 -156
- data/spec/twig/commit_time_spec.rb +20 -18
- data/spec/twig/display_spec.rb +108 -57
- data/spec/twig/github_spec.rb +90 -73
- data/spec/twig/options_spec.rb +311 -152
- data/spec/twig/subcommands_spec.rb +29 -0
- data/spec/twig/system_spec.rb +36 -0
- data/spec/twig/util_spec.rb +20 -20
- data/spec/twig_spec.rb +103 -76
- data/twig.gemspec +9 -6
- metadata +17 -11
data/HISTORY.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
Twig
|
2
2
|
====
|
3
3
|
|
4
|
+
1.5 (2013-11-21)
|
5
|
+
----------------
|
6
|
+
* ENHANCEMENT: Add `--format=json` option for printing branch data as JSON
|
7
|
+
instead of a list. Useful for integrating Twig data into other tools.
|
8
|
+
* ENHANCEMENT: Add tab completion for all subcommands, built-in (e.g., `twig
|
9
|
+
diff`, `twig gh-update`) and custom.
|
10
|
+
* ENHANCEMENT: Paginate help content where possible.
|
11
|
+
* ENHANCEMENT: Improve error messages `~/.twigconfig` isn't readable or contains
|
12
|
+
invalid lines.
|
13
|
+
* ENHANCEMENT: Include default option values for branch listing and GitHub
|
14
|
+
integration in `twig --help`. (GH-30, GH-31)
|
15
|
+
* FIX: Fix warnings when listing branches when a branch has UTF-8 characters in
|
16
|
+
its name. (GH-20)
|
17
|
+
* FIX: Fix showing relative time for very old branches (e.g., "2y ago"). (GH-29)
|
18
|
+
|
4
19
|
1.4 (2013-08-07)
|
5
20
|
----------------
|
6
21
|
* ENHANCEMENT: Speed up listing branches by 3–4x.
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
Twig: Your personal Git branch assistant.
|
2
2
|
=========================================
|
3
3
|
|
4
|
+
[](http://badge.fury.io/rb/twig)
|
5
|
+
[](https://travis-ci.org/rondevera/twig)
|
6
|
+
|
4
7
|
It's hard enough trying to remember the names of all of your Git branches. You
|
5
8
|
also need those branches' issue tracker ids, issue statuses, and reminders of
|
6
9
|
what to do next with each branch. `git branch` only lists them in alphabetical
|
@@ -76,10 +79,10 @@ them by age, name, and custom properties:
|
|
76
79
|
* `twig --all`:
|
77
80
|
List all branches regardless of other filtering options
|
78
81
|
|
79
|
-
You can put your most frequently used options into `~/.
|
80
|
-
automatically included when you run `twig`. Example:
|
82
|
+
You can put your most frequently used options into `~/.twigconfig`, and they'll
|
83
|
+
be automatically included when you run `twig`. Example:
|
81
84
|
|
82
|
-
# ~/.
|
85
|
+
# ~/.twigconfig:
|
83
86
|
except-branch: staging
|
84
87
|
header-style: green bold
|
85
88
|
max-days-old: 30
|
@@ -221,6 +224,9 @@ open a browser window if possible:
|
|
221
224
|
$ twig gh-open
|
222
225
|
GitHub URL: https://github.com/myname/myproject
|
223
226
|
|
227
|
+
For GitHub Enterprise or other installations, you can change
|
228
|
+
`https://github.com` by setting `github-uri-prefix` in `~/.twigrc`.
|
229
|
+
|
224
230
|
|
225
231
|
twig gh-open-issue
|
226
232
|
------------------
|
@@ -236,6 +242,9 @@ subcommand to view that issue on GitHub:
|
|
236
242
|
$ twig gh-open-issue -b <branch name>
|
237
243
|
GitHub issue URL: https://github.com/myname/myproject/issues/222
|
238
244
|
|
245
|
+
For GitHub Enterprise or other installations, you can change
|
246
|
+
`https://github.com` by setting `github-uri-prefix` in `~/.twigrc`.
|
247
|
+
|
239
248
|
|
240
249
|
twig gh-update
|
241
250
|
--------------
|
@@ -272,6 +281,10 @@ subcommand syncs issue statuses with GitHub:
|
|
272
281
|
|
273
282
|
Run `twig gh-update` periodically to keep up with GitHub issues locally.
|
274
283
|
|
284
|
+
For GitHub Enterprise or other installations, you can change the default
|
285
|
+
`https://api.github.com` endpoint prefix by setting `github-api-uri-prefix` in
|
286
|
+
`~/.twigrc`.
|
287
|
+
|
275
288
|
|
276
289
|
Writing a subcommand
|
277
290
|
--------------------
|
@@ -309,7 +322,7 @@ add it to the [Twig wiki][wiki]!
|
|
309
322
|
More info
|
310
323
|
=========
|
311
324
|
|
312
|
-
* **Requirements:** Tested with Git 1.6.5 and Ruby 1.8.7/1.9.2/1.9.3. Probably
|
325
|
+
* **Requirements:** Tested with Git 1.6.5+ and Ruby 1.8.7/1.9.2/1.9.3. Probably
|
313
326
|
works with older software, but it's not guaranteed.
|
314
327
|
* **Contributing:** Found a bug or have a suggestion? [Please open an
|
315
328
|
issue][issues] or ping [@ronalddevera on Twitter][twitter]. If you want to
|
data/bin/twig-gh-open
CHANGED
@@ -14,7 +14,7 @@ Twig::GithubRepo.new do |gh_repo|
|
|
14
14
|
twig.read_config_file!
|
15
15
|
twig.read_cli_options!(ARGV)
|
16
16
|
|
17
|
-
gh_uri_prefix = twig.options[:github_uri_prefix]
|
17
|
+
gh_uri_prefix = twig.options[:github_uri_prefix]
|
18
18
|
url = "#{gh_uri_prefix}/#{gh_repo.username}/#{gh_repo.repository}"
|
19
19
|
|
20
20
|
puts "GitHub URL: #{url}"
|
data/bin/twig-gh-open-issue
CHANGED
@@ -27,7 +27,7 @@ Twig::GithubRepo.new do |gh_repo|
|
|
27
27
|
abort %{The branch "#{branch_name}" doesn't have an "issue" property.}
|
28
28
|
end
|
29
29
|
|
30
|
-
gh_uri_prefix = twig.options[:github_uri_prefix]
|
30
|
+
gh_uri_prefix = twig.options[:github_uri_prefix]
|
31
31
|
url = "#{gh_uri_prefix}/#{gh_repo.username}/#{gh_repo.repository}"
|
32
32
|
url << "/issues/#{issue_id}"
|
33
33
|
|
data/bin/twig-gh-update
CHANGED
@@ -20,7 +20,7 @@ Twig::GithubRepo.new do |gh_repo|
|
|
20
20
|
print 'Getting latest states for GitHub issues...'
|
21
21
|
|
22
22
|
issues = {}
|
23
|
-
issues_uri_prefix = twig.options[:github_api_uri_prefix]
|
23
|
+
issues_uri_prefix = twig.options[:github_api_uri_prefix]
|
24
24
|
issues_uri_base =
|
25
25
|
"#{issues_uri_prefix}/repos/#{gh_repo.username}/#{gh_repo.repository}/issues"
|
26
26
|
issues_uris = [
|
data/bin/twig-help
CHANGED
@@ -7,19 +7,20 @@
|
|
7
7
|
# Author: Ron DeVera <http://rondevera.com>
|
8
8
|
|
9
9
|
require 'fileutils'
|
10
|
+
require 'rubygems'
|
11
|
+
require 'twig'
|
10
12
|
|
11
|
-
version = `twig --version`.strip
|
12
13
|
script = <<-SCRIPT
|
13
14
|
|
14
15
|
#!/usr/bin/env bash
|
15
16
|
|
16
|
-
# AUTO-GENERATED with Twig v#{
|
17
|
+
# AUTO-GENERATED with Twig v#{Twig::VERSION}. Regenerate with
|
17
18
|
# `twig init-completion --force`.
|
18
19
|
#
|
19
20
|
# Initializes bash tab completion for Twig. To use this, run
|
20
21
|
# `twig init-completion` and follow the instructions.
|
21
22
|
#
|
22
|
-
# Twig:
|
23
|
+
# Twig: <#{Twig::HOMEPAGE}>
|
23
24
|
# Author: Ron DeVera <http://rondevera.com>
|
24
25
|
|
25
26
|
__twig_branches() {
|
@@ -30,6 +31,22 @@ __twig_branches() {
|
|
30
31
|
return 0
|
31
32
|
}
|
32
33
|
|
34
|
+
__twig_formats() {
|
35
|
+
local current words
|
36
|
+
current="${COMP_WORDS[COMP_CWORD]}"
|
37
|
+
words="json"
|
38
|
+
COMPREPLY=($(compgen -W "$words" -- "$current"))
|
39
|
+
return 0
|
40
|
+
}
|
41
|
+
|
42
|
+
__twig_subcommands() {
|
43
|
+
local current words
|
44
|
+
current="${COMP_WORDS[COMP_CWORD]}"
|
45
|
+
words="#{Twig::Subcommands.all_names.join(' ')}"
|
46
|
+
COMPREPLY=($(compgen -W "$words" -- "$current"))
|
47
|
+
return 0
|
48
|
+
}
|
49
|
+
|
33
50
|
__twig() {
|
34
51
|
if [ -z "$(git rev-parse HEAD 2>/dev/null)" ]; then
|
35
52
|
return 0;
|
@@ -39,9 +56,11 @@ __twig() {
|
|
39
56
|
|
40
57
|
case "${previous}" in
|
41
58
|
-b|--branch) __twig_branches ;;
|
59
|
+
--format) __twig_formats ;;
|
42
60
|
diff) __twig_branches ;;
|
43
61
|
diff-branch) __twig_branches ;;
|
44
62
|
rebase) __twig_branches ;;
|
63
|
+
twig) __twig_subcommands ;;
|
45
64
|
esac
|
46
65
|
|
47
66
|
return 0
|
data/bin/twig-rebase
CHANGED
@@ -35,7 +35,7 @@ rebase_options = args.join(' ') # Pass remaining options to `git-rebase`
|
|
35
35
|
abort if base_branch.empty?
|
36
36
|
|
37
37
|
print %{Rebase "#{topic_branch}" onto "#{base_branch}"? (y/n) }
|
38
|
-
input =
|
38
|
+
input = $stdin.gets.strip.downcase
|
39
39
|
if input == 'y' || input == 'yes'
|
40
40
|
exec %{git rebase #{rebase_options} "#{base_branch}" "#{topic_branch}"}
|
41
41
|
else
|
data/lib/twig.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
Dir[File.join(File.dirname(__FILE__), 'twig', '*.rb')].each { |file| require file }
|
2
2
|
require 'time'
|
3
3
|
|
4
|
+
# The main class.
|
4
5
|
class Twig
|
5
6
|
include Cli
|
6
7
|
include Display
|
@@ -8,11 +9,13 @@ class Twig
|
|
8
9
|
|
9
10
|
attr_accessor :options
|
10
11
|
|
11
|
-
|
12
|
+
DEFAULT_GITHUB_API_URI_PREFIX = 'https://api.github.com'
|
13
|
+
DEFAULT_GITHUB_URI_PREFIX = 'https://github.com'
|
14
|
+
DEFAULT_HEADER_COLOR = :blue
|
15
|
+
REF_FORMAT_SEPARATOR = '|'
|
12
16
|
REF_FORMAT = %w[refname:short committerdate committerdate:relative].
|
13
17
|
map { |field| '%(' + field + ')' }.join(REF_FORMAT_SEPARATOR)
|
14
18
|
REF_PREFIX = 'refs/heads/'
|
15
|
-
DEFAULT_HEADER_COLOR = :blue
|
16
19
|
|
17
20
|
def self.run(command)
|
18
21
|
`#{command}`.strip
|
@@ -27,6 +30,8 @@ class Twig
|
|
27
30
|
self.options = {}
|
28
31
|
|
29
32
|
# Set defaults
|
33
|
+
set_option(:github_api_uri_prefix, DEFAULT_GITHUB_API_URI_PREFIX)
|
34
|
+
set_option(:github_uri_prefix, DEFAULT_GITHUB_URI_PREFIX)
|
30
35
|
set_option(:header_style, DEFAULT_HEADER_COLOR.to_s)
|
31
36
|
end
|
32
37
|
|
@@ -53,18 +58,21 @@ class Twig
|
|
53
58
|
def branches
|
54
59
|
branches = all_branches
|
55
60
|
now = Time.now
|
56
|
-
|
61
|
+
max_days_old = options[:max_days_old]
|
62
|
+
max_seconds_old = max_days_old * 86400 if max_days_old
|
57
63
|
|
58
|
-
branches.select do |branch|
|
64
|
+
branches = branches.select do |branch|
|
59
65
|
catch :skip_branch do
|
60
66
|
if max_seconds_old
|
61
67
|
seconds_old = now.to_i - branch.last_commit_time.to_i
|
62
68
|
next if seconds_old > max_seconds_old
|
63
69
|
end
|
64
70
|
|
71
|
+
branch_name = branch.name
|
72
|
+
|
65
73
|
(options[:property_except] || {}).each do |property_name, property_value|
|
66
74
|
if property_name == :branch
|
67
|
-
throw :skip_branch if
|
75
|
+
throw :skip_branch if branch_name =~ property_value
|
68
76
|
elsif branch.get_property(property_name.to_s) =~ property_value
|
69
77
|
throw :skip_branch
|
70
78
|
end
|
@@ -72,7 +80,7 @@ class Twig
|
|
72
80
|
|
73
81
|
(options[:property_only] || {}).each do |property_name, property_value|
|
74
82
|
if property_name == :branch
|
75
|
-
throw :skip_branch if
|
83
|
+
throw :skip_branch if branch_name !~ property_value
|
76
84
|
elsif branch.get_property(property_name.to_s) !~ property_value
|
77
85
|
throw :skip_branch
|
78
86
|
end
|
@@ -81,6 +89,14 @@ class Twig
|
|
81
89
|
true
|
82
90
|
end
|
83
91
|
end
|
92
|
+
|
93
|
+
# List least recently modified branches first
|
94
|
+
branches = branches.sort_by { |branch| branch.last_commit_time }
|
95
|
+
if options[:reverse] != true
|
96
|
+
branches.reverse! # List most recently modified branches first
|
97
|
+
end
|
98
|
+
|
99
|
+
branches
|
84
100
|
end
|
85
101
|
|
86
102
|
def all_branch_names
|
@@ -102,13 +118,7 @@ class Twig
|
|
102
118
|
|
103
119
|
out = "\n" << branch_list_headers(options)
|
104
120
|
|
105
|
-
|
106
|
-
listable_branches = branches.sort_by { |branch| branch.last_commit_time }
|
107
|
-
if options[:reverse] != true
|
108
|
-
listable_branches.reverse! # List most recently modified branches first
|
109
|
-
end
|
110
|
-
|
111
|
-
branch_lines = listable_branches.inject([]) do |result, branch|
|
121
|
+
branch_lines = branches.inject([]) do |result, branch|
|
112
122
|
result << branch_list_line(branch)
|
113
123
|
end
|
114
124
|
|
data/lib/twig/branch.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
class Twig
|
2
|
+
|
3
|
+
# Represents a Git branch.
|
2
4
|
class Branch
|
3
5
|
|
4
6
|
EMPTY_PROPERTY_NAME_ERROR = 'Branch property names cannot be empty strings.'
|
@@ -22,8 +24,8 @@ class Twig
|
|
22
24
|
properties = config_lines.map do |line|
|
23
25
|
# Split by rightmost `=`, allowing branch names to contain `=`:
|
24
26
|
key = value = nil
|
25
|
-
line.match(/(.+)=(.+)/).tap { |
|
26
|
-
next
|
27
|
+
line.match(/(.+)=(.+)/).tap { |md| key, value = md[1..2] if md }
|
28
|
+
next unless key
|
27
29
|
|
28
30
|
key_parts = key.split('.')
|
29
31
|
key_parts.last if key_parts[0] == 'branch' && key_parts.size > 2
|
@@ -42,27 +44,40 @@ class Twig
|
|
42
44
|
|
43
45
|
def to_s ; name ; end
|
44
46
|
|
47
|
+
def to_hash
|
48
|
+
all_property_names = Twig::Branch.all_property_names
|
49
|
+
|
50
|
+
{
|
51
|
+
'name' => name,
|
52
|
+
'last-commit-time' => last_commit_time.iso8601,
|
53
|
+
'properties' => get_properties(all_property_names)
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
45
57
|
def sanitize_property(property_name)
|
46
58
|
property_name.gsub(/[ _]+/, '')
|
47
59
|
end
|
48
60
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
property_name_regexps = property_names.map do |property_name|
|
61
|
+
def escaped_property_names(property_names)
|
62
|
+
property_names.map do |property_name|
|
53
63
|
property_name = sanitize_property(property_name)
|
54
64
|
raise EmptyPropertyNameError if property_name.empty?
|
55
65
|
Regexp.escape(property_name)
|
56
|
-
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_properties(property_names)
|
70
|
+
return {} if property_names.empty?
|
57
71
|
|
58
|
-
|
72
|
+
property_names_regexp = escaped_property_names(property_names).join('|')
|
73
|
+
git_config_regexp = "branch\.#{name}\.(#{ property_names_regexp })$"
|
59
74
|
cmd = %{git config --get-regexp "#{git_config_regexp}"}
|
60
75
|
|
61
76
|
git_result = Twig.run(cmd) || ''
|
62
77
|
git_result_lines = git_result.split("\n")
|
63
78
|
|
64
79
|
git_result_lines.inject({}) do |properties, line|
|
65
|
-
match_data = line.match(/^branch\.#{name}\.([^\s]+)\s+(.*)$/)
|
80
|
+
match_data = line.match(/^branch\.#{Regexp.escape(name)}\.([^\s]+)\s+(.*)$/)
|
66
81
|
|
67
82
|
if match_data
|
68
83
|
property_name = match_data[1]
|
data/lib/twig/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'optparse'
|
2
|
+
require 'rbconfig'
|
2
3
|
|
3
4
|
class Twig
|
4
5
|
module Cli
|
@@ -25,24 +26,25 @@ class Twig
|
|
25
26
|
intro + ' ' # Force extra blank line
|
26
27
|
end
|
27
28
|
|
28
|
-
def help_separator(option_parser, text, options={})
|
29
|
+
def help_separator(option_parser, text, options = {})
|
29
30
|
options[:trailing] ||= "\n\n"
|
30
31
|
option_parser.separator "\n#{text}#{options[:trailing]}"
|
31
32
|
end
|
32
33
|
|
33
|
-
def help_description(text, options={})
|
34
|
+
def help_description(text, options = {})
|
34
35
|
width = options[:width] || 40
|
35
36
|
words = text.gsub(/\n?\s+/, ' ').strip.split(' ')
|
36
37
|
lines = []
|
37
38
|
|
38
39
|
# Split words into lines
|
39
40
|
while words.any?
|
40
|
-
current_word
|
41
|
+
current_word = words.shift
|
41
42
|
current_word_size = unformat_string(current_word).size
|
42
|
-
|
43
|
+
last_line = lines.last
|
44
|
+
last_line_size = last_line && unformat_string(last_line).size
|
43
45
|
|
44
46
|
if last_line_size && (last_line_size + current_word_size + 1 <= width)
|
45
|
-
|
47
|
+
last_line << ' ' << current_word
|
46
48
|
elsif current_word_size >= width
|
47
49
|
lines << current_word[0...width]
|
48
50
|
words.unshift(current_word[width..-1])
|
@@ -55,7 +57,8 @@ class Twig
|
|
55
57
|
lines
|
56
58
|
end
|
57
59
|
|
58
|
-
def help_description_for_custom_property(option_parser, desc_lines)
|
60
|
+
def help_description_for_custom_property(option_parser, desc_lines, options = {})
|
61
|
+
options[:trailing] ||= "\n"
|
59
62
|
indent = ' '
|
60
63
|
left_column_width = 29
|
61
64
|
|
@@ -64,7 +67,7 @@ class Twig
|
|
64
67
|
sprintf("%-#{left_column_width}s", left_column) + right_column + "\n"
|
65
68
|
end
|
66
69
|
|
67
|
-
help_separator(option_parser, help_desc, :trailing =>
|
70
|
+
help_separator(option_parser, help_desc, :trailing => options[:trailing])
|
68
71
|
end
|
69
72
|
|
70
73
|
def help_paragraph(text)
|
@@ -93,6 +96,36 @@ class Twig
|
|
93
96
|
is_custom_property_width
|
94
97
|
end
|
95
98
|
|
99
|
+
def run_pager
|
100
|
+
# Starts a pager so that all following STDOUT output is paginated.
|
101
|
+
# Based on: http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
|
102
|
+
|
103
|
+
return if Twig::System.windows? || !$stdout.tty?
|
104
|
+
|
105
|
+
read_io, write_io = IO.pipe
|
106
|
+
|
107
|
+
# Create child process
|
108
|
+
unless Kernel.fork
|
109
|
+
# The following runs only in the child process:
|
110
|
+
$stdout.reopen(write_io)
|
111
|
+
$stderr.reopen(write_io) if $stderr.tty?
|
112
|
+
read_io.close
|
113
|
+
write_io.close
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
$stdin.reopen(read_io)
|
118
|
+
read_io.close
|
119
|
+
write_io.close
|
120
|
+
|
121
|
+
ENV['LESS'] = 'FSRX' # Don't page if the input fits on screen
|
122
|
+
Kernel.select([$stdin]) # Wait for input before starting pager
|
123
|
+
|
124
|
+
# Turn parent process into pager
|
125
|
+
pager = ENV['PAGER'] || 'less'
|
126
|
+
exec pager rescue exec '/bin/sh', '-c', pager
|
127
|
+
end
|
128
|
+
|
96
129
|
def read_cli_options!(args)
|
97
130
|
custom_properties = Twig::Branch.all_property_names
|
98
131
|
|
@@ -120,6 +153,7 @@ class Twig
|
|
120
153
|
desc = 'Show this help content.'
|
121
154
|
opts.on('--help', *help_description(desc)) do
|
122
155
|
summary_lines = opts.to_s.split("\n")
|
156
|
+
run_pager
|
123
157
|
|
124
158
|
# Filter out custom property lines
|
125
159
|
prev_line = nil
|
@@ -166,23 +200,34 @@ class Twig
|
|
166
200
|
end
|
167
201
|
|
168
202
|
custom_properties.each do |property_name|
|
203
|
+
property_name_sym = property_name.to_sym
|
204
|
+
|
169
205
|
opts.on("--only-#{property_name} PATTERN") do |pattern|
|
170
|
-
set_option(:property_only,
|
206
|
+
set_option(:property_only, property_name_sym => pattern)
|
171
207
|
end
|
172
208
|
|
173
209
|
opts.on("--except-#{property_name} PATTERN") do |pattern|
|
174
|
-
set_option(:property_except,
|
210
|
+
set_option(:property_except, property_name_sym => pattern)
|
175
211
|
end
|
176
212
|
end
|
177
213
|
help_description_for_custom_property(opts, [
|
178
214
|
['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
|
179
215
|
['', 'that matches a given pattern.'],
|
180
|
-
])
|
216
|
+
], :trailing => '')
|
181
217
|
help_description_for_custom_property(opts, [
|
182
218
|
['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
|
183
219
|
['', 'that matches a given pattern.']
|
184
220
|
])
|
185
221
|
|
222
|
+
desc =
|
223
|
+
'Print branch properties in a format that can be used by other ' +
|
224
|
+
'tools. Currently, the only supported value is `json`.'
|
225
|
+
opts.on(
|
226
|
+
'--format FORMAT', *help_description(desc, :add_separator => true)
|
227
|
+
) do |format|
|
228
|
+
set_option(:format, format)
|
229
|
+
end
|
230
|
+
|
186
231
|
desc =
|
187
232
|
'Lists all branches regardless of other filtering options. ' +
|
188
233
|
'Useful for overriding options in ' +
|
@@ -197,7 +242,10 @@ class Twig
|
|
197
242
|
|
198
243
|
help_separator(opts, 'Listing branches:')
|
199
244
|
|
200
|
-
desc =
|
245
|
+
desc = <<-DESC
|
246
|
+
Set the width for the `branch` column.
|
247
|
+
(Default: #{Twig::DEFAULT_BRANCH_COLUMN_WIDTH})
|
248
|
+
DESC
|
201
249
|
opts.on('--branch-width NUMBER', *help_description(desc)) do |width|
|
202
250
|
set_option(:property_width, :branch => width)
|
203
251
|
end
|
@@ -208,7 +256,8 @@ class Twig
|
|
208
256
|
end
|
209
257
|
end
|
210
258
|
help_description_for_custom_property(opts, [
|
211
|
-
['--PROPERTY-width NUMBER', "Set the width for a given property's column."]
|
259
|
+
['--PROPERTY-width NUMBER', "Set the width for a given property's column."],
|
260
|
+
['', "(Default: #{Twig::DEFAULT_PROPERTY_COLUMN_WIDTH})"]
|
212
261
|
])
|
213
262
|
|
214
263
|
colors = Twig::Display::COLORS.keys.map do |value|
|
@@ -217,14 +266,14 @@ class Twig
|
|
217
266
|
weights = Twig::Display::WEIGHTS.keys.map do |value|
|
218
267
|
format_string(value, :weight => value)
|
219
268
|
end.join(' and ')
|
220
|
-
|
269
|
+
default_header_style = format_string(
|
221
270
|
Twig::DEFAULT_HEADER_COLOR.to_s,
|
222
271
|
:color => Twig::DEFAULT_HEADER_COLOR
|
223
272
|
)
|
224
273
|
desc = <<-DESC
|
225
274
|
STYLE is a color, weight, or a space-separated pair of one of each.
|
226
275
|
Valid colors are #{colors}. Valid weights are #{weights}.
|
227
|
-
|
276
|
+
(Default: "#{default_header_style}")
|
228
277
|
DESC
|
229
278
|
opts.on(
|
230
279
|
'--header-style "STYLE"',
|
@@ -233,7 +282,7 @@ class Twig
|
|
233
282
|
set_option(:header_style, style)
|
234
283
|
end
|
235
284
|
|
236
|
-
desc = 'Show oldest branches first.'
|
285
|
+
desc = 'Show oldest branches first. (Default: false)'
|
237
286
|
opts.on('--reverse', *help_description(desc)) do
|
238
287
|
set_option(:reverse, true)
|
239
288
|
end
|
@@ -245,6 +294,7 @@ class Twig
|
|
245
294
|
desc = <<-DESC
|
246
295
|
Set a custom GitHub API URI prefix, e.g.,
|
247
296
|
https://github-enterprise.example.com/api/v3.
|
297
|
+
(Default: "#{Twig::DEFAULT_GITHUB_API_URI_PREFIX}")
|
248
298
|
DESC
|
249
299
|
opts.on(
|
250
300
|
'--github-api-uri-prefix PREFIX',
|
@@ -256,6 +306,7 @@ class Twig
|
|
256
306
|
desc = <<-DESC
|
257
307
|
Set a custom GitHub URI prefix, e.g.,
|
258
308
|
https://github-enterprise.example.com.
|
309
|
+
(Default: "#{Twig::DEFAULT_GITHUB_URI_PREFIX}")
|
259
310
|
DESC
|
260
311
|
opts.on(
|
261
312
|
'--github-uri-prefix PREFIX',
|
@@ -287,6 +338,8 @@ class Twig
|
|
287
338
|
option_parser.parse!(args)
|
288
339
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => exception
|
289
340
|
abort_for_option_exception(exception)
|
341
|
+
ensure
|
342
|
+
args
|
290
343
|
end
|
291
344
|
|
292
345
|
def abort_for_option_exception(exception)
|
@@ -295,19 +348,22 @@ class Twig
|
|
295
348
|
exit
|
296
349
|
end
|
297
350
|
|
298
|
-
def
|
299
|
-
if
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
exec(command)
|
306
|
-
end
|
351
|
+
def exec_subcommand_if_any(args)
|
352
|
+
# Run subcommand binary, if any, and exit here
|
353
|
+
possible_subcommand_name = Twig::Subcommands::BIN_PREFIX + args[0]
|
354
|
+
command_path = Twig.run("which #{possible_subcommand_name} 2>/dev/null")
|
355
|
+
unless command_path.empty?
|
356
|
+
command = ([command_path] + args[1..-1]).join(' ')
|
357
|
+
exec(command)
|
307
358
|
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def read_cli_args!(args)
|
362
|
+
exec_subcommand_if_any(args) if args.any?
|
308
363
|
|
309
|
-
read_cli_options!(args)
|
364
|
+
args = read_cli_options!(args)
|
310
365
|
branch_name = options[:branch] || current_branch_name
|
366
|
+
format = options.delete(:format)
|
311
367
|
property_to_unset = options.delete(:unset_property)
|
312
368
|
|
313
369
|
# Handle remaining arguments, if any
|
@@ -327,6 +383,9 @@ class Twig
|
|
327
383
|
elsif property_to_unset
|
328
384
|
# `$ twig --unset <key>`
|
329
385
|
unset_branch_property_for_cli(branch_name, property_to_unset)
|
386
|
+
elsif format == :json
|
387
|
+
# `$ twig --format json`
|
388
|
+
puts branches_json
|
330
389
|
else
|
331
390
|
# `$ twig`
|
332
391
|
puts list_branches
|