twig 1.6 → 1.7
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.
- 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/subcommands.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
class Twig
|
2
2
|
module Subcommands
|
3
|
-
|
4
3
|
BIN_PREFIX = 'twig-'
|
5
4
|
|
6
5
|
def self.all_names
|
@@ -22,5 +21,18 @@ class Twig
|
|
22
21
|
ENV['PATH'].split(':')
|
23
22
|
end
|
24
23
|
|
24
|
+
def self.exec_subcommand_if_any(cli_args)
|
25
|
+
# Run subcommand binary, if any, and exit here
|
26
|
+
|
27
|
+
subcommand_name = cli_args[0]
|
28
|
+
bin_name = Twig::Subcommands::BIN_PREFIX + subcommand_name
|
29
|
+
subcommand_path = Twig.run("which #{bin_name} 2>/dev/null")
|
30
|
+
return if subcommand_path.empty?
|
31
|
+
|
32
|
+
subcommand_args = cli_args[1..-1]
|
33
|
+
command = ([subcommand_path] + subcommand_args).join(' ')
|
34
|
+
|
35
|
+
exec(command)
|
36
|
+
end
|
25
37
|
end
|
26
38
|
end
|
data/lib/twig/system.rb
CHANGED
data/lib/twig/util.rb
CHANGED
data/lib/twig/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'twig'
|
2
2
|
require 'json'
|
3
|
-
require 'rspec/radar'
|
4
3
|
|
5
4
|
RSpec.configure do |config|
|
6
|
-
config.
|
7
|
-
|
5
|
+
config.mock_with :rspec do |mocks|
|
6
|
+
mocks.verify_doubled_constant_names = true
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
mocks.yield_receiver_to_any_instance_implementation_blocks = true
|
8
9
|
end
|
9
10
|
end
|
data/spec/twig/branch_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'spec_helper'
|
3
|
+
require 'shellwords'
|
3
4
|
|
4
5
|
describe Twig::Branch do
|
5
6
|
before :each do
|
@@ -13,16 +14,18 @@ describe Twig::Branch do
|
|
13
14
|
fix_some_other_of_the_things
|
14
15
|
fix_nothing
|
15
16
|
]
|
16
|
-
@commit_time_strings = [
|
17
|
-
|
17
|
+
@commit_time_strings = %w[
|
18
|
+
2001-01-01
|
19
|
+
2002-02-02
|
20
|
+
2003-03-03
|
21
|
+
]
|
18
22
|
@command =
|
19
23
|
%{git for-each-ref #{Twig::REF_PREFIX} --format="#{Twig::REF_FORMAT}"}
|
20
24
|
|
21
25
|
@branch_tuples = (0..2).map do |i|
|
22
26
|
[
|
23
27
|
@branch_names[i],
|
24
|
-
@commit_time_strings[i]
|
25
|
-
@commit_time_agos[i]
|
28
|
+
@commit_time_strings[i]
|
26
29
|
].join(Twig::REF_FORMAT_SEPARATOR)
|
27
30
|
end.join("\n")
|
28
31
|
end
|
@@ -34,15 +37,15 @@ describe Twig::Branch do
|
|
34
37
|
|
35
38
|
expect(branches[0].name).to eq(@branch_names[0])
|
36
39
|
expect(branches[0].last_commit_time.to_s).to match(
|
37
|
-
|
40
|
+
/#{@commit_time_strings[0]}/
|
38
41
|
)
|
39
42
|
expect(branches[1].name).to eq(@branch_names[1])
|
40
43
|
expect(branches[1].last_commit_time.to_s).to match(
|
41
|
-
|
44
|
+
/#{@commit_time_strings[1]}/
|
42
45
|
)
|
43
46
|
expect(branches[2].name).to eq(@branch_names[2])
|
44
47
|
expect(branches[2].last_commit_time.to_s).to match(
|
45
|
-
|
48
|
+
/#{@commit_time_strings[2]}/
|
46
49
|
)
|
47
50
|
end
|
48
51
|
|
@@ -144,6 +147,26 @@ describe Twig::Branch do
|
|
144
147
|
end
|
145
148
|
end
|
146
149
|
|
150
|
+
describe '.shellescape_property_value' do
|
151
|
+
it 'escapes backticks' do
|
152
|
+
value = 'value_`ls`'
|
153
|
+
result = Twig::Branch.shellescape_property_value(value)
|
154
|
+
expect(result).to eql('value_\`ls\`')
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'escapes dollar signs' do
|
158
|
+
value = 'value_$PATH'
|
159
|
+
result = Twig::Branch.shellescape_property_value(value)
|
160
|
+
expect(result).to eql('value_\$PATH')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'does not escape spaces' do
|
164
|
+
value = 'foo bar'
|
165
|
+
result = Twig::Branch.shellescape_property_value(value)
|
166
|
+
expect(result).to eql('foo bar')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
147
170
|
describe '#initialize' do
|
148
171
|
it 'requires a name' do
|
149
172
|
branch = Twig::Branch.new('test')
|
@@ -155,7 +178,7 @@ describe Twig::Branch do
|
|
155
178
|
end
|
156
179
|
|
157
180
|
it 'accepts a last commit time' do
|
158
|
-
commit_time = Twig::CommitTime.new(Time.now
|
181
|
+
commit_time = Twig::CommitTime.new(Time.now)
|
159
182
|
branch = Twig::Branch.new('test', :last_commit_time => commit_time)
|
160
183
|
expect(branch.last_commit_time).to eq(commit_time)
|
161
184
|
end
|
@@ -172,7 +195,7 @@ describe Twig::Branch do
|
|
172
195
|
before :each do
|
173
196
|
@branch = Twig::Branch.new('test')
|
174
197
|
time = Time.parse('2000-01-01 18:30 UTC')
|
175
|
-
commit_time = Twig::CommitTime.new(time
|
198
|
+
commit_time = Twig::CommitTime.new(time)
|
176
199
|
@time_string = time.iso8601
|
177
200
|
allow(@branch).to receive(:last_commit_time) { commit_time }
|
178
201
|
end
|
@@ -207,6 +230,24 @@ describe Twig::Branch do
|
|
207
230
|
end
|
208
231
|
end
|
209
232
|
|
233
|
+
describe '#parent_name' do
|
234
|
+
before :each do
|
235
|
+
@branch = Twig::Branch.new('test')
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'returns the parent branch name' do
|
239
|
+
parent_name = 'parent'
|
240
|
+
allow(@branch).to receive(:get_property).with('diff-branch').and_return(parent_name)
|
241
|
+
|
242
|
+
expect(@branch.parent_name).to eq(parent_name)
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'returns nil if the parent branch is unknown' do
|
246
|
+
allow(@branch).to receive(:get_property).with('diff-branch')
|
247
|
+
expect(@branch.parent_name).to be_nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
210
251
|
describe '#sanitize_property' do
|
211
252
|
before :each do
|
212
253
|
@branch = Twig::Branch.new('test')
|
@@ -259,6 +300,7 @@ describe Twig::Branch do
|
|
259
300
|
branch = Twig::Branch.new('utf8_{・ิω・ิ}')
|
260
301
|
properties = { 'test1' => 'value1' }
|
261
302
|
git_result = "branch.#{branch}.test1 value1"
|
303
|
+
expect(Shellwords).to receive(:escape).with(branch.to_s) { branch.to_s }
|
262
304
|
expect(Twig).to receive(:run).
|
263
305
|
with(%{git config --get-regexp "branch.#{branch}.(test1)$"}).
|
264
306
|
and_return(git_result)
|
@@ -322,7 +364,7 @@ describe Twig::Branch do
|
|
322
364
|
end
|
323
365
|
|
324
366
|
expect(expected_exception.message).to eq(
|
325
|
-
Twig::Branch::
|
367
|
+
Twig::Branch::EmptyPropertyNameError::DEFAULT_MESSAGE
|
326
368
|
)
|
327
369
|
end
|
328
370
|
end
|
@@ -403,7 +445,7 @@ describe Twig::Branch do
|
|
403
445
|
end
|
404
446
|
|
405
447
|
expect(expected_exception.message).to eq(
|
406
|
-
Twig::Branch::
|
448
|
+
Twig::Branch::EmptyPropertyNameError::DEFAULT_MESSAGE
|
407
449
|
)
|
408
450
|
end
|
409
451
|
|
@@ -483,6 +525,37 @@ describe Twig::Branch do
|
|
483
525
|
%{Saved property "#{property}" as "#{value}" for branch "#{@branch}"}
|
484
526
|
)
|
485
527
|
end
|
528
|
+
|
529
|
+
it 'sets a property for a branch whose name contains a backtick' do
|
530
|
+
branch = Twig::Branch.new('branch_`ls`')
|
531
|
+
property = 'test'
|
532
|
+
value = 'value'
|
533
|
+
escaped_branch_name = branch.to_s.shellescape
|
534
|
+
expect(Twig).to receive(:run).
|
535
|
+
with(%{git config branch.#{escaped_branch_name}.#{property} "#{value}"}) do
|
536
|
+
`(exit 0)`; value # Set `$?` to `0`
|
537
|
+
end
|
538
|
+
|
539
|
+
result = branch.set_property(property, value)
|
540
|
+
expect(result).to include(
|
541
|
+
%{Saved property "#{property}" as "#{value}" for branch "#{branch}"}
|
542
|
+
)
|
543
|
+
end
|
544
|
+
|
545
|
+
it 'sets a property value that contains special shell characters' do
|
546
|
+
property = 'test'
|
547
|
+
value = 'value `ls` $PATH'
|
548
|
+
escaped_value = 'value \`ls\` \$PATH'
|
549
|
+
expect(Twig).to receive(:run).
|
550
|
+
with(%{git config branch.#{@branch}.#{property} "#{escaped_value}"}) do
|
551
|
+
`(exit 0)`; value # Set `$?` to `0`
|
552
|
+
end
|
553
|
+
|
554
|
+
result = @branch.set_property(property, value)
|
555
|
+
expect(result).to include(
|
556
|
+
%{Saved property "#{property}" as "#{value}" for branch "#{@branch}"}
|
557
|
+
)
|
558
|
+
end
|
486
559
|
end
|
487
560
|
|
488
561
|
describe '#unset_property' do
|
@@ -519,7 +592,6 @@ describe Twig::Branch do
|
|
519
592
|
|
520
593
|
it 'raises an error if the property name is an empty string' do
|
521
594
|
bad_property = ' '
|
522
|
-
property = ''
|
523
595
|
expect(@branch).not_to receive(:get_property)
|
524
596
|
expect(Twig).not_to receive(:run)
|
525
597
|
|
@@ -530,7 +602,7 @@ describe Twig::Branch do
|
|
530
602
|
end
|
531
603
|
|
532
604
|
expect(expected_exception.message).to eq(
|
533
|
-
Twig::Branch::
|
605
|
+
Twig::Branch::EmptyPropertyNameError::DEFAULT_MESSAGE
|
534
606
|
)
|
535
607
|
end
|
536
608
|
|
@@ -548,6 +620,21 @@ describe Twig::Branch do
|
|
548
620
|
%{The branch "#{@branch}" does not have the property "#{property}"}
|
549
621
|
)
|
550
622
|
end
|
623
|
+
|
624
|
+
it 'unsets a property for a branch whose name contains a backtick' do
|
625
|
+
branch = Twig::Branch.new('branch_`ls`')
|
626
|
+
property = 'test'
|
627
|
+
expect(branch).to receive(:get_property).
|
628
|
+
with(property).and_return('value')
|
629
|
+
expect(Twig).to receive(:run).with(
|
630
|
+
%{git config --unset branch.#{branch.to_s.shellescape}.#{property}}
|
631
|
+
)
|
632
|
+
|
633
|
+
result = branch.unset_property(property)
|
634
|
+
expect(result).to include(
|
635
|
+
%{Removed property "#{property}" for branch "#{branch}"}
|
636
|
+
)
|
637
|
+
end
|
551
638
|
end
|
552
639
|
|
553
640
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Twig::Cli::Help do
|
4
|
+
Help = Twig::Cli::Help
|
5
|
+
|
6
|
+
describe '.description' do
|
7
|
+
before :each do
|
8
|
+
@twig = Twig.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns short text in a single line' do
|
12
|
+
text = 'The quick brown fox.'
|
13
|
+
result = Help.description(text, :width => 80)
|
14
|
+
expect(result).to eq([text])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns long text in a string with line breaks' do
|
18
|
+
text = 'The quick brown fox jumps over the lazy, lazy dog.'
|
19
|
+
result = Help.description(text, :width => 20)
|
20
|
+
expect(result).to eq([
|
21
|
+
'The quick brown fox',
|
22
|
+
'jumps over the lazy,',
|
23
|
+
'lazy dog.'
|
24
|
+
])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'breaks a long word by max line length' do
|
28
|
+
text = 'Thequickbrownfoxjumpsoverthelazydog.'
|
29
|
+
result = Help.description(text, :width => 20)
|
30
|
+
expect(result).to eq([
|
31
|
+
'Thequickbrownfoxjump',
|
32
|
+
'soverthelazydog.'
|
33
|
+
])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'adds a blank line' do
|
37
|
+
text = 'The quick brown fox.'
|
38
|
+
result = Help.description(text, :width => 80, :add_blank_line => true)
|
39
|
+
expect(result).to eq([text, ' '])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.description_for_custom_property' do
|
44
|
+
before :each do
|
45
|
+
@twig = Twig.new
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns a help string for a custom property' do
|
49
|
+
option_parser = OptionParser.new
|
50
|
+
expect(Help).to receive(:print_section) do |opt_parser, desc, options|
|
51
|
+
expect(opt_parser).to eq(option_parser)
|
52
|
+
expect(desc).to eq(" --test-option Test option description\n")
|
53
|
+
expect(options).to eq(:trailing => "\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
Help.description_for_custom_property(option_parser, [
|
57
|
+
['--test-option', 'Test option description']
|
58
|
+
])
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'supports custom trailing whitespace' do
|
62
|
+
option_parser = OptionParser.new
|
63
|
+
expect(Help).to receive(:print_section) do |opt_parser, desc, options|
|
64
|
+
expect(opt_parser).to eq(option_parser)
|
65
|
+
expect(desc).to eq(" --test-option Test option description\n")
|
66
|
+
expect(options).to eq(:trailing => '')
|
67
|
+
end
|
68
|
+
|
69
|
+
Help.description_for_custom_property(option_parser, [
|
70
|
+
['--test-option', 'Test option description']
|
71
|
+
], :trailing => '')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '.line_for_custom_property?' do
|
76
|
+
before :each do
|
77
|
+
@twig = Twig.new
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'returns true for `--except-foo`' do
|
81
|
+
expect(Help.line_for_custom_property?(' --except-foo ')).to eql(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns false for `--except-branch`' do
|
85
|
+
expect(Help.line_for_custom_property?(' --except-branch ')).to be_falsy
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns false for `--except-property`' do
|
89
|
+
expect(Help.line_for_custom_property?(' --except-property ')).to be_falsy
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns false for `--except-PROPERTY`' do
|
93
|
+
expect(Help.line_for_custom_property?(' --except-PROPERTY ')).to be_falsy
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns true for `--only-foo`' do
|
97
|
+
expect(Help.line_for_custom_property?(' --only-foo ')).to eql(true)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'returns false for `--only-branch`' do
|
101
|
+
expect(Help.line_for_custom_property?(' --only-branch ')).to be_falsy
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns false for `--only-property`' do
|
105
|
+
expect(Help.line_for_custom_property?(' --only-property ')).to be_falsy
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns false for `--only-PROPERTY`' do
|
109
|
+
expect(Help.line_for_custom_property?(' --only-PROPERTY ')).to be_falsy
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns true for `--foo-width`' do
|
113
|
+
expect(Help.line_for_custom_property?(' --foo-width ')).to eql(true)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'returns false for `--branch-width`' do
|
117
|
+
expect(Help.line_for_custom_property?(' --branch-width ')).to be_falsy
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'returns false for `--PROPERTY-width`' do
|
121
|
+
expect(Help.line_for_custom_property?(' --PROPERTY-width ')).to be_falsy
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '.paragraph' do
|
126
|
+
before :each do
|
127
|
+
@twig = Twig.new
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'returns long text in a paragraph with line breaks' do
|
131
|
+
text = Array.new(5) do
|
132
|
+
'The quick brown fox jumps over the lazy dog.'
|
133
|
+
end.join(' ')
|
134
|
+
|
135
|
+
result = Help.paragraph(text)
|
136
|
+
|
137
|
+
expect(result).to eq([
|
138
|
+
'The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the',
|
139
|
+
'lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps',
|
140
|
+
'over the lazy dog. The quick brown fox jumps over the lazy dog.'
|
141
|
+
].join("\n"))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '.subcommand_descriptions' do
|
146
|
+
it 'returns a word-wrapped list of subcommand descriptions' do
|
147
|
+
output_lines = Help.subcommand_descriptions
|
148
|
+
|
149
|
+
# Some lines are actually multi-line descriptions. Split them so we can
|
150
|
+
# count characters per line.
|
151
|
+
output_lines = output_lines.map { |line| line.split("\n") }.flatten
|
152
|
+
|
153
|
+
output_line_max_width = output_lines.map { |line| line.length }.max
|
154
|
+
expect(output_line_max_width).to be <= Help.console_width
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe '.header' do
|
159
|
+
it 'generates a header section' do
|
160
|
+
option_parser = double
|
161
|
+
text = 'Some header'
|
162
|
+
expected_text = "Some header\n==========="
|
163
|
+
expect(Help).to receive(:print_section).with(
|
164
|
+
option_parser,
|
165
|
+
expected_text,
|
166
|
+
:trailing => "\n\n"
|
167
|
+
)
|
168
|
+
|
169
|
+
Help.header(option_parser, text)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '.subheader' do
|
174
|
+
it 'generates a subheader section' do
|
175
|
+
option_parser = double
|
176
|
+
text = 'Some subheader'
|
177
|
+
expected_text = "Some subheader\n--------------"
|
178
|
+
expect(Help).to receive(:print_section).with(
|
179
|
+
option_parser,
|
180
|
+
expected_text,
|
181
|
+
:trailing => "\n\n"
|
182
|
+
)
|
183
|
+
|
184
|
+
Help.subheader(option_parser, text)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|