twig 1.6 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/HISTORY.md +27 -0
- data/README.md +41 -22
- data/bin/twig +12 -4
- data/bin/twig-checkout-child +56 -25
- data/bin/twig-checkout-parent +54 -26
- data/bin/twig-create-branch +40 -15
- data/bin/twig-diff +44 -25
- data/bin/twig-gh-open +43 -17
- data/bin/twig-gh-open-issue +51 -23
- data/bin/twig-gh-update +46 -20
- data/bin/twig-help +49 -5
- data/bin/twig-init +46 -13
- data/bin/twig-init-completion +46 -19
- data/bin/twig-init-completion-bash +50 -25
- data/bin/twig-init-config +77 -0
- data/bin/twig-rebase +85 -33
- data/config/twigconfig +47 -0
- data/lib/twig.rb +16 -10
- data/lib/twig/branch.rb +19 -12
- data/lib/twig/cli.rb +118 -183
- data/lib/twig/cli/help.rb +174 -0
- data/lib/twig/commit_time.rb +69 -14
- data/lib/twig/display.rb +19 -6
- data/lib/twig/github.rb +10 -8
- data/lib/twig/options.rb +22 -14
- data/lib/twig/subcommands.rb +13 -1
- data/lib/twig/system.rb +0 -2
- data/lib/twig/util.rb +0 -2
- data/lib/twig/version.rb +1 -1
- data/spec/spec_helper.rb +4 -3
- data/spec/twig/branch_spec.rb +100 -13
- data/spec/twig/cli/help_spec.rb +187 -0
- data/spec/twig/cli_spec.rb +34 -189
- data/spec/twig/commit_time_spec.rb +185 -16
- data/spec/twig/display_spec.rb +69 -48
- data/spec/twig/github_spec.rb +29 -15
- data/spec/twig/options_spec.rb +42 -13
- data/spec/twig/subcommands_spec.rb +35 -2
- data/spec/twig/system_spec.rb +5 -6
- data/spec/twig/util_spec.rb +20 -20
- data/spec/twig_spec.rb +21 -6
- data/twig.gemspec +14 -16
- metadata +23 -27
data/lib/twig/branch.rb
CHANGED
@@ -2,14 +2,14 @@ class Twig
|
|
2
2
|
|
3
3
|
# Represents a Git branch.
|
4
4
|
class Branch
|
5
|
-
|
6
|
-
EMPTY_PROPERTY_NAME_ERROR = 'Branch property names cannot be empty strings.'
|
5
|
+
PARENT_PROPERTY = 'diff-branch'
|
7
6
|
PROPERTY_NAME_FROM_GIT_CONFIG = /^branch\.[^.]+\.([^=]+)=.*$/
|
8
7
|
RESERVED_BRANCH_PROPERTY_NAMES = %w[branch merge property rebase remote]
|
9
8
|
|
10
9
|
class EmptyPropertyNameError < ArgumentError
|
10
|
+
DEFAULT_MESSAGE = 'Branch property names cannot be empty strings.'
|
11
11
|
def initialize(message = nil)
|
12
|
-
message ||=
|
12
|
+
message ||= DEFAULT_MESSAGE
|
13
13
|
super
|
14
14
|
end
|
15
15
|
end
|
@@ -24,9 +24,9 @@ class Twig
|
|
24
24
|
split("\n")
|
25
25
|
|
26
26
|
branch_tuples.inject([]) do |result, branch_tuple|
|
27
|
-
name, time_string
|
27
|
+
name, time_string = branch_tuple.split(REF_FORMAT_SEPARATOR)
|
28
28
|
time = Time.parse(time_string)
|
29
|
-
commit_time = Twig::CommitTime.new(time
|
29
|
+
commit_time = Twig::CommitTime.new(time)
|
30
30
|
branch = Branch.new(name, :last_commit_time => commit_time)
|
31
31
|
result << branch
|
32
32
|
end
|
@@ -34,7 +34,7 @@ class Twig
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.all_branch_names
|
37
|
-
@_all_branch_names ||=
|
37
|
+
@_all_branch_names ||= all_branches.map { |branch| branch.name }
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.all_property_names
|
@@ -59,6 +59,10 @@ class Twig
|
|
59
59
|
raise EmptyPropertyNameError if property_name.empty?
|
60
60
|
end
|
61
61
|
|
62
|
+
def self.shellescape_property_value(property_value)
|
63
|
+
Shellwords.escape(property_value).gsub('\\ ', ' ')
|
64
|
+
end
|
65
|
+
|
62
66
|
def initialize(name, attrs = {})
|
63
67
|
self.name = name
|
64
68
|
raise ArgumentError, '`name` is required' if name.empty?
|
@@ -76,6 +80,10 @@ class Twig
|
|
76
80
|
}
|
77
81
|
end
|
78
82
|
|
83
|
+
def parent_name
|
84
|
+
get_property(PARENT_PROPERTY)
|
85
|
+
end
|
86
|
+
|
79
87
|
def sanitize_property(property_name)
|
80
88
|
property_name.gsub(/[ _]+/, '')
|
81
89
|
end
|
@@ -92,7 +100,7 @@ class Twig
|
|
92
100
|
return {} if property_names.empty?
|
93
101
|
|
94
102
|
property_names_regexp = escaped_property_names(property_names).join('|')
|
95
|
-
git_config_regexp = "branch\.#{name}\.(#{ property_names_regexp })$"
|
103
|
+
git_config_regexp = "branch\.#{Shellwords.escape(name)}\.(#{ property_names_regexp })$"
|
96
104
|
cmd = %{git config --get-regexp "#{git_config_regexp}"}
|
97
105
|
|
98
106
|
git_result = Twig.run(cmd) || ''
|
@@ -114,7 +122,6 @@ class Twig
|
|
114
122
|
properties.merge(property_name => property_value)
|
115
123
|
end
|
116
124
|
end
|
117
|
-
|
118
125
|
end
|
119
126
|
|
120
127
|
def get_property(property_name)
|
@@ -134,8 +141,9 @@ class Twig
|
|
134
141
|
raise ArgumentError,
|
135
142
|
%{Can't set a branch property to an empty string.}
|
136
143
|
else
|
137
|
-
git_config = "branch.#{name}.#{property_name}"
|
138
|
-
|
144
|
+
git_config = "branch.#{name.shellescape}.#{property_name}"
|
145
|
+
escaped_value = Branch.shellescape_property_value(value)
|
146
|
+
Twig.run(%{git config #{git_config} "#{escaped_value}"})
|
139
147
|
result_body = %{property "#{property_name}" as "#{value}" for branch "#{name}".}
|
140
148
|
if $?.success?
|
141
149
|
"Saved #{result_body}"
|
@@ -152,7 +160,7 @@ class Twig
|
|
152
160
|
value = get_property(property_name)
|
153
161
|
|
154
162
|
if value
|
155
|
-
git_config = "branch.#{name}.#{property_name}"
|
163
|
+
git_config = "branch.#{name.shellescape}.#{property_name}"
|
156
164
|
Twig.run(%{git config --unset #{git_config}})
|
157
165
|
%{Removed property "#{property_name}" for branch "#{name}".}
|
158
166
|
else
|
@@ -160,6 +168,5 @@ class Twig
|
|
160
168
|
%{The branch "#{name}" does not have the property "#{property_name}".}
|
161
169
|
end
|
162
170
|
end
|
163
|
-
|
164
171
|
end
|
165
172
|
end
|
data/lib/twig/cli.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'rbconfig'
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'cli', 'help'))
|
3
4
|
|
4
5
|
class Twig
|
6
|
+
# Handles raw input from the command-line interface.
|
5
7
|
module Cli
|
6
|
-
|
7
8
|
def self.prompt_with_choices(prompt, choices)
|
8
9
|
# Prints the given string `prompt` and the array `choices` numbered, and
|
9
10
|
# prompts the user to enter a number. Returns the matching value, or nil
|
10
11
|
# if the user input is invalid.
|
11
12
|
|
12
|
-
if choices.size < 2
|
13
|
-
raise ArgumentError, 'At least two choices required'
|
14
|
-
end
|
13
|
+
raise ArgumentError, 'At least two choices required' if choices.size < 2
|
15
14
|
|
16
15
|
puts prompt
|
17
16
|
choices.each_with_index do |choice, index|
|
@@ -23,106 +22,11 @@ class Twig
|
|
23
22
|
choices[input - 1]
|
24
23
|
end
|
25
24
|
|
26
|
-
def help_intro
|
27
|
-
version_string = "Twig v#{Twig::VERSION}"
|
28
|
-
|
29
|
-
intro = help_paragraph(%{
|
30
|
-
Twig is your personal Git branch assistant. It's a command-line tool for
|
31
|
-
listing your most recent branches, and for remembering branch details
|
32
|
-
for you, like issue tracker ids and todos. It also supports subcommands,
|
33
|
-
like automatically fetching statuses from your issue tracking system.
|
34
|
-
})
|
35
|
-
|
36
|
-
intro = <<-BANNER.gsub(/^[ ]+/, '')
|
37
|
-
|
38
|
-
#{version_string}
|
39
|
-
#{'-' * version_string.size}
|
40
|
-
|
41
|
-
#{intro}
|
42
|
-
|
43
|
-
#{Twig::HOMEPAGE}
|
44
|
-
BANNER
|
45
|
-
|
46
|
-
intro + ' ' # Force extra blank line
|
47
|
-
end
|
48
|
-
|
49
|
-
def help_separator(option_parser, text, options = {})
|
50
|
-
options[:trailing] ||= "\n\n"
|
51
|
-
option_parser.separator "\n#{text}#{options[:trailing]}"
|
52
|
-
end
|
53
|
-
|
54
|
-
def help_description(text, options = {})
|
55
|
-
width = options[:width] || 40
|
56
|
-
words = text.gsub(/\n?\s+/, ' ').strip.split(' ')
|
57
|
-
lines = []
|
58
|
-
|
59
|
-
# Split words into lines
|
60
|
-
while words.any?
|
61
|
-
current_word = words.shift
|
62
|
-
current_word_size = unformat_string(current_word).size
|
63
|
-
last_line = lines.last
|
64
|
-
last_line_size = last_line && unformat_string(last_line).size
|
65
|
-
|
66
|
-
if last_line_size && (last_line_size + current_word_size + 1 <= width)
|
67
|
-
last_line << ' ' << current_word
|
68
|
-
elsif current_word_size >= width
|
69
|
-
lines << current_word[0...width]
|
70
|
-
words.unshift(current_word[width..-1])
|
71
|
-
else
|
72
|
-
lines << current_word
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
lines << ' ' if options[:add_separator]
|
77
|
-
lines
|
78
|
-
end
|
79
|
-
|
80
|
-
def help_description_for_custom_property(option_parser, desc_lines, options = {})
|
81
|
-
options[:trailing] ||= "\n"
|
82
|
-
indent = ' '
|
83
|
-
left_column_width = 29
|
84
|
-
|
85
|
-
help_desc = desc_lines.inject('') do |desc, (left_column, right_column)|
|
86
|
-
desc + indent +
|
87
|
-
sprintf("%-#{left_column_width}s", left_column) + right_column + "\n"
|
88
|
-
end
|
89
|
-
|
90
|
-
help_separator(option_parser, help_desc, :trailing => options[:trailing])
|
91
|
-
end
|
92
|
-
|
93
|
-
def help_paragraph(text)
|
94
|
-
help_description(text, :width => 80).join("\n")
|
95
|
-
end
|
96
|
-
|
97
|
-
def help_line_for_custom_property?(line)
|
98
|
-
is_custom_property_except = (
|
99
|
-
line.include?('--except-') &&
|
100
|
-
!line.include?('--except-branch') &&
|
101
|
-
!line.include?('--except-property') &&
|
102
|
-
!line.include?('--except-PROPERTY')
|
103
|
-
)
|
104
|
-
is_custom_property_only = (
|
105
|
-
line.include?('--only-') &&
|
106
|
-
!line.include?('--only-branch') &&
|
107
|
-
!line.include?('--only-property') &&
|
108
|
-
!line.include?('--only-PROPERTY')
|
109
|
-
)
|
110
|
-
is_custom_property_width = (
|
111
|
-
line =~ /--.+-width/ &&
|
112
|
-
!line.include?('--branch-width') &&
|
113
|
-
!line.include?('--PROPERTY-width')
|
114
|
-
)
|
115
|
-
|
116
|
-
is_custom_property_except ||
|
117
|
-
is_custom_property_only ||
|
118
|
-
is_custom_property_width
|
119
|
-
end
|
120
|
-
|
121
25
|
def run_pager
|
122
26
|
# Starts a pager so that all following STDOUT output is paginated.
|
123
27
|
# Based on: http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
|
124
28
|
|
125
|
-
return if Twig::System.windows? || !$stdout.tty?
|
29
|
+
return if Twig::System.windows? || !$stdout.tty? || !Kernel.respond_to?(:fork)
|
126
30
|
|
127
31
|
read_io, write_io = IO.pipe
|
128
32
|
|
@@ -152,28 +56,28 @@ class Twig
|
|
152
56
|
custom_properties = Twig::Branch.all_property_names
|
153
57
|
|
154
58
|
option_parser = OptionParser.new do |opts|
|
155
|
-
opts.banner =
|
59
|
+
opts.banner = Help.intro
|
156
60
|
opts.summary_indent = ' ' * 2
|
157
61
|
opts.summary_width = 32
|
158
62
|
|
63
|
+
###
|
159
64
|
|
160
|
-
|
161
|
-
help_separator(opts, 'Common options:')
|
65
|
+
Help.header(opts, 'Common options')
|
162
66
|
|
163
67
|
desc = 'Use a specific branch.'
|
164
68
|
opts.on(
|
165
|
-
'-b BRANCH', '--branch BRANCH', *
|
69
|
+
'-b BRANCH', '--branch BRANCH', *Help.description(desc)
|
166
70
|
) do |branch|
|
167
71
|
set_option(:branch, branch)
|
168
72
|
end
|
169
73
|
|
170
74
|
desc = 'Unset a branch property.'
|
171
|
-
opts.on('--unset PROPERTY', *
|
75
|
+
opts.on('--unset PROPERTY', *Help.description(desc)) do |property_name|
|
172
76
|
set_option(:unset_property, property_name)
|
173
77
|
end
|
174
78
|
|
175
79
|
desc = 'Show this help content.'
|
176
|
-
opts.on('--help', *
|
80
|
+
opts.on('--help', *Help.description(desc)) do
|
177
81
|
summary_lines = opts.to_s.split("\n")
|
178
82
|
run_pager
|
179
83
|
|
@@ -183,7 +87,7 @@ class Twig
|
|
183
87
|
# Squash successive blank lines
|
184
88
|
next if line == "\n" && prev_line == "\n"
|
185
89
|
|
186
|
-
unless
|
90
|
+
unless Help.line_for_custom_property?(line)
|
187
91
|
puts line
|
188
92
|
prev_line = line
|
189
93
|
end
|
@@ -193,17 +97,18 @@ class Twig
|
|
193
97
|
end
|
194
98
|
|
195
99
|
desc = 'Show Twig version.'
|
196
|
-
opts.on('--version', *
|
197
|
-
puts Twig::VERSION
|
100
|
+
opts.on('--version', *Help.description(desc)) do
|
101
|
+
puts Twig::VERSION
|
102
|
+
exit
|
198
103
|
end
|
199
104
|
|
105
|
+
###
|
200
106
|
|
201
|
-
|
202
|
-
help_separator(opts, 'Filtering branches:')
|
107
|
+
Help.header(opts, 'Filtering branches')
|
203
108
|
|
204
109
|
desc = 'Only list branches below a given age.'
|
205
110
|
opts.on(
|
206
|
-
'--max-days-old AGE', *
|
111
|
+
'--max-days-old AGE', *Help.description(desc, :add_blank_line => true)
|
207
112
|
) do |age|
|
208
113
|
set_option(:max_days_old, age)
|
209
114
|
end
|
@@ -211,13 +116,13 @@ class Twig
|
|
211
116
|
desc = 'Only list branches whose name matches a given pattern.'
|
212
117
|
opts.on(
|
213
118
|
'--only-branch PATTERN',
|
214
|
-
*
|
119
|
+
*Help.description(desc, :add_blank_line => true)
|
215
120
|
) do |pattern|
|
216
121
|
set_option(:property_only, :branch => pattern)
|
217
122
|
end
|
218
123
|
|
219
124
|
desc = 'Do not list branches whose name matches a given pattern.'
|
220
|
-
opts.on('--except-branch PATTERN', *
|
125
|
+
opts.on('--except-branch PATTERN', *Help.description(desc)) do |pattern|
|
221
126
|
set_option(:property_except, :branch => pattern)
|
222
127
|
end
|
223
128
|
|
@@ -232,43 +137,43 @@ class Twig
|
|
232
137
|
set_option(:property_except, property_name_sym => pattern)
|
233
138
|
end
|
234
139
|
end
|
235
|
-
|
140
|
+
Help.description_for_custom_property(opts, [
|
236
141
|
['--only-PROPERTY PATTERN', 'Only list branches with a given property'],
|
237
142
|
['', 'that matches a given pattern.']
|
238
143
|
], :trailing => '')
|
239
|
-
|
144
|
+
Help.description_for_custom_property(opts, [
|
240
145
|
['--except-PROPERTY PATTERN', 'Do not list branches with a given property'],
|
241
146
|
['', 'that matches a given pattern.']
|
242
147
|
])
|
243
148
|
|
244
149
|
desc =
|
245
|
-
'Print branch properties in a format that can be used by other '
|
150
|
+
'Print branch properties in a format that can be used by other ' \
|
246
151
|
'tools. Currently, the only supported value is `json`.'
|
247
152
|
opts.on(
|
248
|
-
'--format FORMAT', *
|
153
|
+
'--format FORMAT', *Help.description(desc, :add_blank_line => true)
|
249
154
|
) do |format|
|
250
155
|
set_option(:format, format)
|
251
156
|
end
|
252
157
|
|
253
158
|
desc =
|
254
|
-
'Lists all branches regardless of other filtering options. '
|
159
|
+
'Lists all branches regardless of other filtering options. ' \
|
255
160
|
'Useful for overriding options in ' +
|
256
161
|
File.basename(Twig::Options::CONFIG_PATH) + '.'
|
257
|
-
opts.on('--all', *
|
162
|
+
opts.on('--all', *Help.description(desc)) do |pattern|
|
258
163
|
unset_option(:max_days_old)
|
259
164
|
unset_option(:property_except)
|
260
165
|
unset_option(:property_only)
|
261
166
|
end
|
262
167
|
|
168
|
+
###
|
263
169
|
|
264
|
-
|
265
|
-
help_separator(opts, 'Listing branches:')
|
170
|
+
Help.header(opts, 'Listing branches')
|
266
171
|
|
267
172
|
desc = <<-DESC
|
268
173
|
Set the width for the `branch` column.
|
269
174
|
(Default: #{Twig::DEFAULT_BRANCH_COLUMN_WIDTH})
|
270
175
|
DESC
|
271
|
-
opts.on('--branch-width NUMBER', *
|
176
|
+
opts.on('--branch-width NUMBER', *Help.description(desc)) do |width|
|
272
177
|
set_option(:property_width, :branch => width)
|
273
178
|
end
|
274
179
|
|
@@ -277,7 +182,7 @@ class Twig
|
|
277
182
|
set_option(:property_width, property_name.to_sym => width)
|
278
183
|
end
|
279
184
|
end
|
280
|
-
|
185
|
+
Help.description_for_custom_property(opts, [
|
281
186
|
['--PROPERTY-width NUMBER', "Set the width for a given property's column."],
|
282
187
|
['', "(Default: #{Twig::DEFAULT_PROPERTY_COLUMN_WIDTH})"]
|
283
188
|
])
|
@@ -288,7 +193,7 @@ class Twig
|
|
288
193
|
DESC
|
289
194
|
opts.on(
|
290
195
|
'--only-property PATTERN',
|
291
|
-
*
|
196
|
+
*Help.description(desc, :add_blank_line => true)
|
292
197
|
) do |pattern|
|
293
198
|
set_option(:property_only_name, pattern)
|
294
199
|
end
|
@@ -299,17 +204,17 @@ class Twig
|
|
299
204
|
DESC
|
300
205
|
opts.on(
|
301
206
|
'--except-property PATTERN',
|
302
|
-
*
|
207
|
+
*Help.description(desc, :add_blank_line => true)
|
303
208
|
) do |pattern|
|
304
209
|
set_option(:property_except_name, pattern)
|
305
210
|
end
|
306
211
|
|
307
|
-
colors = Twig::Display::COLORS.keys.
|
308
|
-
format_string(value, :color => value)
|
309
|
-
|
310
|
-
weights = Twig::Display::WEIGHTS.keys.
|
311
|
-
format_string(value, :weight => value)
|
312
|
-
|
212
|
+
colors = Twig::Display::COLORS.keys.
|
213
|
+
map { |value| format_string(value, :color => value) }.
|
214
|
+
join(', ')
|
215
|
+
weights = Twig::Display::WEIGHTS.keys.
|
216
|
+
map { |value| format_string(value, :weight => value) }.
|
217
|
+
join(' and ')
|
313
218
|
default_header_style = format_string(
|
314
219
|
Twig::DEFAULT_HEADER_COLOR.to_s,
|
315
220
|
:color => Twig::DEFAULT_HEADER_COLOR
|
@@ -321,19 +226,19 @@ class Twig
|
|
321
226
|
DESC
|
322
227
|
opts.on(
|
323
228
|
'--header-style "STYLE"',
|
324
|
-
*
|
229
|
+
*Help.description(desc, :add_blank_line => true)
|
325
230
|
) do |style|
|
326
231
|
set_option(:header_style, style)
|
327
232
|
end
|
328
233
|
|
329
234
|
desc = 'Show oldest branches first. (Default: false)'
|
330
|
-
opts.on('--reverse', *
|
235
|
+
opts.on('--reverse', *Help.description(desc)) do
|
331
236
|
set_option(:reverse, true)
|
332
237
|
end
|
333
238
|
|
239
|
+
###
|
334
240
|
|
335
|
-
|
336
|
-
help_separator(opts, 'GitHub integration:')
|
241
|
+
Help.header(opts, 'GitHub integration')
|
337
242
|
|
338
243
|
desc = <<-DESC
|
339
244
|
Set a custom GitHub API URI prefix, e.g.,
|
@@ -342,7 +247,7 @@ class Twig
|
|
342
247
|
DESC
|
343
248
|
opts.on(
|
344
249
|
'--github-api-uri-prefix PREFIX',
|
345
|
-
*
|
250
|
+
*Help.description(desc, :add_blank_line => true)
|
346
251
|
) do |prefix|
|
347
252
|
set_option(:github_api_uri_prefix, prefix)
|
348
253
|
end
|
@@ -352,31 +257,78 @@ class Twig
|
|
352
257
|
https://github-enterprise.example.com.
|
353
258
|
(Default: "#{Twig::DEFAULT_GITHUB_URI_PREFIX}")
|
354
259
|
DESC
|
355
|
-
opts.on(
|
356
|
-
'--github-uri-prefix PREFIX',
|
357
|
-
*help_description(desc, :add_separator => true)
|
358
|
-
) do |prefix|
|
260
|
+
opts.on('--github-uri-prefix PREFIX', *Help.description(desc)) do |prefix|
|
359
261
|
set_option(:github_uri_prefix, prefix)
|
360
262
|
end
|
361
263
|
|
264
|
+
###
|
265
|
+
|
266
|
+
Help.header(opts, 'Config files and tab completion', :trailing => '')
|
362
267
|
|
268
|
+
Help.print_paragraph(opts, %{
|
269
|
+
Twig can automatically set up a config file for you, where you can put
|
270
|
+
your most frequently used options for filtering and listing branches.
|
271
|
+
To get started, run `twig init` and follow the instructions. This does
|
272
|
+
two things:
|
273
|
+
})
|
363
274
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
})
|
275
|
+
Help.print_paragraph(opts, %{
|
276
|
+
* Creates #{Twig::Options::CONFIG_PATH}, where you can put your
|
277
|
+
favorite options, e.g.:
|
278
|
+
})
|
368
279
|
|
369
|
-
|
280
|
+
Help.print_section(opts, [
|
370
281
|
' except-branch: staging',
|
371
282
|
' header-style: green bold',
|
372
283
|
' max-days-old: 30',
|
373
284
|
' reverse: true'
|
285
|
+
].join("\n"))
|
286
|
+
|
287
|
+
Help.print_paragraph(opts, %{
|
288
|
+
* Enables tab completion for Twig subcommands and branch names, e.g.:
|
289
|
+
})
|
290
|
+
|
291
|
+
Help.print_section(opts, [
|
292
|
+
' `twig cre<tab>` -> `twig create-branch`',
|
293
|
+
' `twig status -b my-br<tab>` -> `twig status -b my-branch`'
|
374
294
|
].join("\n"), :trailing => '')
|
375
295
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
296
|
+
###
|
297
|
+
|
298
|
+
Help.header(opts, 'Subcommands', :trailing => '')
|
299
|
+
|
300
|
+
Help.print_paragraph(opts, "Twig comes with these subcommands:", :trailing => "\n\n")
|
301
|
+
|
302
|
+
Help.subcommand_descriptions.each do |desc|
|
303
|
+
Help.print_line(opts, desc)
|
304
|
+
end
|
305
|
+
|
306
|
+
Help.subheader(opts, 'Writing a subcommand', :trailing => '')
|
307
|
+
|
308
|
+
Help.print_paragraph(opts, %{
|
309
|
+
You can write any Twig subcommand that fits your own Git workflow. To
|
310
|
+
write a Twig subcommand:
|
311
|
+
})
|
312
|
+
|
313
|
+
Help.print_section(opts, [
|
314
|
+
'1. Write a script; any language will do. (If you want to take',
|
315
|
+
' advantage of Twig\'s option parsing and branch processing, you\'ll',
|
316
|
+
' need Ruby. See `twig-checkout-parent` for an example.)'
|
317
|
+
].join("\n"))
|
318
|
+
|
319
|
+
Help.print_section(opts, [
|
320
|
+
'2. Save it with the `twig-` prefix in your `$PATH`,',
|
321
|
+
' e.g., `~/bin/twig-my-subcommand`.'
|
322
|
+
].join("\n"))
|
323
|
+
|
324
|
+
Help.print_section(opts, [
|
325
|
+
'3. Make it executable: `chmod ugo+x ~/bin/twig-my-subcommand`'
|
326
|
+
].join("\n"))
|
327
|
+
|
328
|
+
Help.print_section(opts, [
|
329
|
+
'4. Run your subcommand: `twig my-subcommand` (with a *space* after',
|
330
|
+
' `twig`'
|
331
|
+
].join("\n"))
|
380
332
|
end
|
381
333
|
|
382
334
|
option_parser.parse!(args)
|
@@ -387,22 +339,12 @@ class Twig
|
|
387
339
|
end
|
388
340
|
|
389
341
|
def abort_for_option_exception(exception)
|
390
|
-
puts exception.message + "\nFor a list of options, run `twig
|
342
|
+
puts exception.message + "\nFor a list of options, run `twig help`."
|
391
343
|
exit 1
|
392
344
|
end
|
393
345
|
|
394
|
-
def exec_subcommand_if_any(args)
|
395
|
-
# Run subcommand binary, if any, and exit here
|
396
|
-
possible_subcommand_name = Twig::Subcommands::BIN_PREFIX + args[0]
|
397
|
-
command_path = Twig.run("which #{possible_subcommand_name} 2>/dev/null")
|
398
|
-
unless command_path.empty?
|
399
|
-
command = ([command_path] + args[1..-1]).join(' ')
|
400
|
-
exec(command)
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
346
|
def read_cli_args!(args)
|
405
|
-
exec_subcommand_if_any(args) if args.any?
|
347
|
+
Twig::Subcommands.exec_subcommand_if_any(args) if args.any?
|
406
348
|
|
407
349
|
args = read_cli_options!(args)
|
408
350
|
branch_name = target_branch_name
|
@@ -436,34 +378,27 @@ class Twig
|
|
436
378
|
end
|
437
379
|
|
438
380
|
def get_branch_property_for_cli(branch_name, property_name)
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
%{The branch "#{branch_name}" does not have the property "#{property_name}".}
|
446
|
-
end
|
447
|
-
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
448
|
-
abort exception.message
|
381
|
+
value = get_branch_property(branch_name, property_name)
|
382
|
+
if value
|
383
|
+
puts value
|
384
|
+
else
|
385
|
+
raise Twig::Branch::MissingPropertyError,
|
386
|
+
%{The branch "#{branch_name}" does not have the property "#{property_name}".}
|
449
387
|
end
|
388
|
+
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
389
|
+
abort exception.message
|
450
390
|
end
|
451
391
|
|
452
392
|
def set_branch_property_for_cli(branch_name, property_name, property_value)
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
abort exception.message
|
457
|
-
end
|
393
|
+
puts set_branch_property(branch_name, property_name, property_value)
|
394
|
+
rescue ArgumentError, RuntimeError => exception
|
395
|
+
abort exception.message
|
458
396
|
end
|
459
397
|
|
460
398
|
def unset_branch_property_for_cli(branch_name, property_name)
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
abort exception.message
|
465
|
-
end
|
399
|
+
puts unset_branch_property(branch_name, property_name)
|
400
|
+
rescue ArgumentError, Twig::Branch::MissingPropertyError => exception
|
401
|
+
abort exception.message
|
466
402
|
end
|
467
|
-
|
468
403
|
end
|
469
404
|
end
|