git-topic 0.1.6.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/git_topic/git.rb CHANGED
@@ -9,6 +9,34 @@ module GitTopic::Git
9
9
 
10
10
  protected
11
11
 
12
+ def git_dir
13
+ @@git_dir ||= (
14
+ git_dir = capture_git( "rev-parse --git-dir" ).chomp;
15
+ raise "Unexpected gitdir: [#{git_dir}]" unless git_dir.index '.git'
16
+ git_dir
17
+ )
18
+ end
19
+
20
+ def git_editor
21
+ @@git_editor ||= capture_git( "var GIT_EDITOR" ).chomp
22
+ end
23
+
24
+ def git_author_name_short
25
+ @@git_author_name_short ||= (
26
+ full_name = capture_git( "config user.name" )
27
+ raise "
28
+ whatever
29
+ " unless $?.success?
30
+
31
+ parts = full_name.split( " " )
32
+ fname = parts.shift
33
+ fname = "#{fname[0..0].upcase}#{fname[1..-1]}"
34
+ suffix = parts.map{ |p| p[0..0].upcase }.join( "" )
35
+
36
+ "#{fname} #{suffix}".strip
37
+ )
38
+ end
39
+
12
40
  def working_tree_clean?
13
41
  git [ "diff --quiet", "diff --quiet --cached" ]
14
42
  $?.success?
@@ -18,6 +46,15 @@ module GitTopic::Git
18
46
  not working_tree_clean?
19
47
  end
20
48
 
49
+ def existing_comments?( branch=current_branch )
50
+ ref = notes_ref( branch )
51
+ not capture_git( "notes --ref #{ref} list" ).chomp.empty?
52
+ end
53
+
54
+ def existing_comments
55
+ capture_git( "notes --ref #{notes_ref} show" ).chomp
56
+ end
57
+
21
58
 
22
59
  def display_git_output?
23
60
  @@display_git_output ||= false
@@ -36,6 +73,10 @@ module GitTopic::Git
36
73
  end
37
74
  end
38
75
 
76
+ def invoke_git_editor( file )
77
+ system "#{git_editor} #{file}"
78
+ end
79
+
39
80
  def cmd_redirect_suffix( opts )
40
81
  if !opts[:show] && !display_git_output?
41
82
  "> /dev/null 2> /dev/null"
@@ -43,21 +84,43 @@ module GitTopic::Git
43
84
  end
44
85
 
45
86
  def git( cmds=[], opts={} )
87
+ opts.assert_valid_keys :must_succeed
88
+
46
89
  cmds = [cmds] if cmds.is_a? String
47
90
  redir = cmd_redirect_suffix( opts )
48
91
  cmd = cmds.map{|c| "git #{c} #{redir}"}.join( " && " )
49
92
 
50
93
  puts cmd if GitTopic::global_opts[:verbose]
51
- system cmd
94
+ result = system( cmd )
95
+
96
+ if opts[:must_succeed] && !$?.success?
97
+ raise "
98
+ Required git command failed:\n #{cmd}.\n re-run with --verbose to
99
+ see git output.
100
+ ".cleanup
101
+ end
102
+
103
+ result
52
104
  end
53
105
 
54
- def capture_git( cmds=[] )
106
+ def capture_git( cmds=[], opts={} )
107
+ opts.assert_valid_keys :must_succeed
108
+
55
109
  cmds = [cmds] if cmds.is_a? String
56
110
  redir = "2> /dev/null" unless display_git_output?
57
111
  cmd = "#{cmds.map{|c| "git #{c} #{redir}"}.join( " && " )}"
58
112
 
59
113
  puts cmd if GitTopic::global_opts[:verbose]
60
- `#{cmd}`
114
+ result = `#{cmd}`
115
+
116
+ if opts[:must_succeed] && !$?.success?
117
+ raise "
118
+ Required git command failed:\n #{cmd}.\n re-run with --verbose to
119
+ see git output.
120
+ ".cleanup
121
+ end
122
+
123
+ result
61
124
  end
62
125
 
63
126
  end
@@ -32,7 +32,7 @@ module GitTopic::Naming
32
32
  end
33
33
 
34
34
  def strip_namespace( ref )
35
- if ref =~ %r{(?:wip|rejected|review)/\S*/(.*)}
35
+ if ref =~ %r{(?:wip|rejected|review)/(?:(?:\S*)/)?(.*)}
36
36
  $1
37
37
  else
38
38
  ref
@@ -40,13 +40,20 @@ module GitTopic::Naming
40
40
  end
41
41
 
42
42
 
43
+ def notes_ref( branch=current_branch )
44
+ user, topic = user_topic_name( branch )
45
+ "refs/notes/reviews/#{user}/#{topic}"
46
+ end
47
+
48
+
43
49
  def user_topic_name( branch )
44
50
  if branch =~ %r{^origin}
45
51
  branch =~ %r{^\S*?/\S*?/(\S*?)/(\S*)}
46
52
  [$1, $2]
47
- else
48
- branch =~ %r{^\S*?/(\S*?)/(\S*)}
53
+ elsif branch =~ %r{^(?:\S*/)?(\S*?)/(\S*)}
49
54
  [$1, $2]
55
+ else
56
+ raise "Cannot compute user_topic for [#{branch}]"
50
57
  end
51
58
  end
52
59
 
@@ -54,6 +61,8 @@ module GitTopic::Naming
54
61
  p = {}
55
62
  parts = ref.split( '/' )
56
63
  case parts.size
64
+ when 3
65
+ _, p[:user], p[:topic] = parts
57
66
  when 2
58
67
  p[:user], p[:topic] = parts
59
68
  when 1
@@ -69,6 +78,11 @@ module GitTopic::Naming
69
78
  @@user ||= (ENV['USER'] || `whoami`)
70
79
  end
71
80
 
81
+ def current_namespace
82
+ current_branch =~ %r{(wip|review|rejected)/(\S*)}
83
+ $1
84
+ end
85
+
72
86
  def current_topic
73
87
  current_branch =~ %r{wip/\S*?/(\S*)}
74
88
  $1
@@ -120,9 +134,16 @@ module GitTopic::Naming
120
134
  end
121
135
 
122
136
  namespace_ut.symbolize_keys!
123
- namespace_ut[:review] ||= {}
137
+ namespace_ut[:review] ||= {}
124
138
  namespace_ut[:rejected] ||= {}
125
139
 
140
+ namespace_ut[:rejected].each do |user, topics|
141
+ topics.map! do |topic|
142
+ suffix = " (reviewer comments) "
143
+ "#{topic}#{suffix if existing_comments?( "#{user}/#{topic}" )}"
144
+ end
145
+ end
146
+
126
147
  namespace_ut[:review].reject!{|k,v| k == user}
127
148
  namespace_ut
128
149
  )
@@ -138,7 +159,7 @@ module GitTopic::Naming
138
159
 
139
160
  commits_by_age.find do |ref|
140
161
  # no ‘,’, i.e. only one ref matches the commit
141
- ref.index( ',' ).nil?
162
+ !ref.strip.empty? && ref.index( ',' ).nil?
142
163
  end.strip[ 1..-2 ] # chomp the leading and trailing parenthesis
143
164
  end
144
165
 
@@ -0,0 +1,28 @@
1
+
2
+
3
+
4
+ _git_work_on() {
5
+ __gitcomp "$(git-topic-completion work-on 2> /dev/null)"
6
+ return
7
+ }
8
+
9
+ _git_review() {
10
+ __gitcomp "$(git-topic-completion review 2> /dev/null)"
11
+ return
12
+ }
13
+
14
+
15
+ _git_done() {
16
+ __gitcomp "$(git-topic-completion done 2> /dev/null)"
17
+ return
18
+ }
19
+
20
+ _git_accept() {
21
+ __gitcomp "$(git-topic-completion accept 2> /dev/null)"
22
+ return
23
+ }
24
+
25
+ _git_reject() {
26
+ __gitcomp "$(git-topic-completion reject 2> /dev/null)"
27
+ return
28
+ }
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,396 @@
1
+ require 'spec_helper'
2
+
3
+ DiffWithNonCommentAddition = %Q{
4
+ diff --git a/lib/git_topic.rb b/lib/git_topic.rb
5
+ index edb53f1..adceb44 100644
6
+ --- a/lib/git_topic.rb
7
+ +++ b/lib/git_topic.rb
8
+ @@ -9,0 +10 @@ require 'git_topic/naming'
9
+ +require 'git_topic/comment'
10
+ }.strip
11
+
12
+ DiffWithDeletion = %Q{
13
+ diff --git a/Gemfile b/Gemfile
14
+ index ece2952..9d2ccec 100644
15
+ --- a/Gemfile
16
+ +++ b/Gemfile
17
+ @@ -7 +6,0 @@ group :runtime do
18
+ - gem 'trollop'
19
+ }.strip
20
+
21
+ DiffWithOnlyComments = %Q{
22
+ diff --git a/Gemfile b/Gemfile
23
+ index ece2952..8485775 100644
24
+ --- a/Gemfile
25
+ +++ b/Gemfile
26
+ @@ -5,0 +6,2 @@ group :runtime do
27
+ + # what was the point of this?
28
+ + # surely it would be better with crazy doom stuff and fingles, no?
29
+ diff --git a/lib/git_topic.rb b/lib/git_topic.rb
30
+ index adceb44..920e512 100644
31
+ --- a/lib/git_topic.rb
32
+ +++ b/lib/git_topic.rb
33
+ @@ -17,0 +18 @@ module GitTopic
34
+ + # This is kind of ugly. Why can't we pull this into cli?
35
+ @@ -55,0 +57 @@ module GitTopic
36
+ + # Oh the night patty murphy died... yadda yadda.
37
+ @@ -107,0 +110,3 @@ module GitTopic
38
+ + # That's how they showed their respect for patty murphy! that's how they
39
+ + # showed their honour and their pride! They said it was a cryin' shame
40
+ + # and they winked at one another.
41
+ @@ -111,0 +117 @@ module GitTopic
42
+ + # and so on and so forth. The point is, there was a lot of wailing.
43
+ @@ -118,0 +125 @@ module GitTopic
44
+ + # They put the bottle with the corpse to keep that whiskey cold!
45
+ diff --git a/Rakefile b/Rakefile
46
+ index 309d6df..73428b2 100644
47
+ --- a/Rakefile
48
+ +++ b/Rakefile
49
+ @@ -64,0 +65,4 @@ rescue LoadError
50
+ + # Awesome I guess. But why not something like:
51
+ + # Crazy good.
52
+ + # Or something vaguely similar.
53
+ + # It would seem better that way.
54
+ }.strip
55
+
56
+
57
+ EditorBufferResultFromClean = %Q{
58
+ ./Gemfile
59
+
60
+ Line 6
61
+
62
+ David: what was the point of this? surely it would be better with
63
+ crazy doom stuff and fingles, no?
64
+
65
+
66
+ ./lib/git_topic.rb
67
+
68
+ Line 18
69
+
70
+ David: This is kind of ugly. Why can't we pull this into cli?
71
+
72
+ Line 56
73
+
74
+ David: Oh the night patty murphy died... yadda yadda.
75
+
76
+ Line 108
77
+
78
+ David: That's how they showed their respect for patty murphy!
79
+ that's how they showed their honour and their pride! They
80
+ said it was a cryin' shame and they winked at one another.
81
+
82
+ Line 112
83
+
84
+ David: and so on and so forth. The point is, there was a lot of
85
+ wailing.
86
+
87
+ Line 119
88
+
89
+ David: They put the bottle with the corpse to keep that whiskey
90
+ cold!
91
+
92
+
93
+ ./Rakefile
94
+
95
+ Line 65
96
+
97
+ David: Awesome I guess. But why not something like:
98
+ Crazy good.
99
+ Or something vaguely similar. It would seem better that way.
100
+ }.strip
101
+
102
+
103
+ ExistingComment = %Q{
104
+ Spec 123: I have some general comments, mostly relating to the quality
105
+ of our zombie-control policies. Basically, they're not
106
+ working.
107
+
108
+ ./zombies
109
+
110
+ Line 2
111
+ Spec 123: I suggest we do the following instead:
112
+ zombies.each{ |z| reason_with( z )}
113
+ zomies.select do |z|
114
+ z.unconvinced?
115
+ end.each do |z|
116
+ destroy z
117
+ end
118
+ This should take care of our issues with zombies.
119
+
120
+
121
+ ./lib/foo/zombie_repellant
122
+
123
+ Line 6
124
+ Spec 123: Does this stuff really work? I'm not convinced.
125
+
126
+ Line 40
127
+ Spec 123: Please explain this.
128
+ }.strip
129
+
130
+ LegalReply = %Q{
131
+ # Spec 123: I have some general comments, mostly relating to the quality
132
+ # of our zombie-control policies. Basically, they're not
133
+ # working.
134
+
135
+ Sorry about that. I have fixed the branch to have better zombie-control policies.
136
+
137
+ #
138
+ # ./zombies
139
+ #
140
+ # Line 2
141
+ # Spec 123: I suggest we do the following instead:
142
+ # zombies.each{ |z| reason_with( z )}
143
+ # zomies.select do |z|
144
+ # z.unconvinced?
145
+ # end.each do |z|
146
+ # destroy z
147
+ # end
148
+ # This should take care of our issues with zombies.
149
+
150
+ Excellent suggestion. It's done.
151
+ #
152
+ # ./lib/foo/zombie_repellant
153
+ #
154
+ # Line 6
155
+ # Spec 123: Does this stuff really work? I'm not convinced.
156
+
157
+ You can look at the tests yourself. It seems to work.
158
+ #
159
+ # Line 40
160
+ # Spec 123: Please explain this.
161
+ }.strip
162
+
163
+ CommentWithReply = %Q{
164
+ Spec 123: I have some general comments, mostly relating to the quality
165
+ of our zombie-control policies. Basically, they're not
166
+ working.
167
+ Spec 456: Sorry about that. I have fixed the branch to have better
168
+ zombie-control policies.
169
+
170
+ ./zombies
171
+
172
+ Line 2
173
+ Spec 123: I suggest we do the following instead:
174
+ zombies.each{ |z| reason_with( z )}
175
+ zomies.select do |z|
176
+ z.unconvinced?
177
+ end.each do |z|
178
+ destroy z
179
+ end
180
+ This should take care of our issues with zombies.
181
+ Spec 456: Excellent suggestion. It's done.
182
+
183
+
184
+ ./lib/foo/zombie_repellant
185
+
186
+ Line 6
187
+ Spec 123: Does this stuff really work? I'm not convinced.
188
+ Spec 456: You can look at the tests yourself. It seems to work.
189
+
190
+ Line 40
191
+ Spec 123: Please explain this.
192
+ }.strip
193
+
194
+ MalformedReplyBadLines = %Q{
195
+ # Spec 123: I have some general comments, mostly relating to the quality
196
+ # of our zombie-control policies. Basically, they're not
197
+ # working.
198
+ #
199
+ # ./zombies
200
+
201
+ Uh oh. a malformed reply. This is not a legal spot for comments!
202
+
203
+ #
204
+ # Line 2
205
+ # Spec 123: I suggest we do the following instead:
206
+ # zombies.each{ |z| reason_with( z )}
207
+ # zomies.select do |z|
208
+ # z.unconvinced?
209
+ # end.each do |z|
210
+ # destroy z
211
+ # end
212
+ # This should take care of our issues with zombies.
213
+ #
214
+ # ./lib/foo/zombie_repellant
215
+ #
216
+ # Line 6
217
+ # Spec 123: Does this stuff really work? I'm not convinced.
218
+ #
219
+ # Line 40
220
+ # Spec 123: Please explain this.
221
+ }.strip
222
+
223
+ MalformedReplyNewFiles = %Q{
224
+ # Spec 123: I have some general comments, mostly relating to the quality
225
+ # of our zombie-control policies. Basically, they're not
226
+ # working.
227
+ #
228
+ # ./zombies
229
+ #
230
+ # Line 2
231
+ # Spec 123: I suggest we do the following instead:
232
+ # zombies.each{ |z| reason_with( z )}
233
+ # zomies.select do |z|
234
+ # z.unconvinced?
235
+ # end.each do |z|
236
+ # destroy z
237
+ # end
238
+ # This should take care of our issues with zombies.
239
+ #
240
+ # ./anew/and/illegal/path
241
+ Uh oh. This appears to be an invalid reply! Madness!
242
+ #
243
+ # ./lib/foo/zombie_repellant
244
+ #
245
+ # Line 6
246
+ # Spec 123: Does this stuff really work? I'm not convinced.
247
+ #
248
+ # Line 40
249
+ # Spec 123: Please explain this.
250
+ }.strip
251
+
252
+
253
+ describe GitTopic::Comment do
254
+ include GitTopic::Comment::ClassMethods
255
+
256
+ describe "#diff_to_file_specific_notes" do
257
+
258
+ it "should fail with non-comment additions" do
259
+ lambda do
260
+ diff_to_file_specific_notes DiffWithNonCommentAddition,
261
+ :author => "David"
262
+ end.should raise_error
263
+ end
264
+
265
+ it "should fail with deletions" do
266
+ lambda do
267
+ diff_to_file_specific_notes DiffWithDeletion,
268
+ :author => "David"
269
+ end.should raise_error
270
+ end
271
+
272
+ it "should fail if :author is not supplied" do
273
+ lambda do
274
+ diff_to_file_specific_notes DiffWithOnlyComments
275
+ end.should raise_error
276
+ end
277
+
278
+ describe "when there are no existing comments" do
279
+
280
+ it "should format the diff correctly for no existing comments" do
281
+ initial_contents = diff_to_file_specific_notes(
282
+ DiffWithOnlyComments,
283
+ :author => "David"
284
+ )
285
+
286
+ # Not interested in exact formatting: just the contents.
287
+ initial_contents.gsub( /\s+/, "\n").strip.should ==
288
+ EditorBufferResultFromClean.gsub( /\s+/, "\n" ).strip
289
+ end
290
+
291
+ end
292
+ end
293
+
294
+
295
+ describe "#notes_from_reply_to_comments" do
296
+
297
+ it "should fail if there are no existing comments" do
298
+ self.should_receive( :existing_comments? ).and_return( false )
299
+ lambda{ notes_from_reply_to_comments }.should raise_error
300
+ end
301
+
302
+ it "
303
+ should complain if the user malforms the file by responding to ‘new’
304
+ files
305
+ ".oneline do
306
+
307
+ self.should_receive( :existing_comments? ). and_return( true )
308
+ self.should_receive( :existing_comments ). and_return( ExistingComment )
309
+ self.should_receive( :git_dir ). and_return( "." )
310
+ self.should_receive( :notes_ref ).exactly( 0 ).times
311
+ self.stub!( :invoke_git_editor ) do |path|
312
+ File.open( path, 'w' ){ |f| f.puts MalformedReplyNewFiles }
313
+ end
314
+ self.should_receive( :invoke_git_editor )
315
+
316
+ lambda{ notes_from_reply_to_comments }.should raise_error
317
+ end
318
+
319
+ it "
320
+ should complain if the user malforms the file by responding to
321
+ file-specific comments before the line sections
322
+ ".oneline do
323
+
324
+ self.should_receive( :existing_comments? ). and_return( true )
325
+ self.should_receive( :existing_comments ). and_return( ExistingComment )
326
+ self.should_receive( :git_dir ). and_return( "." )
327
+ self.should_receive( :notes_ref ).exactly( 0 ).times
328
+ self.stub!( :invoke_git_editor ) do |path|
329
+ File.open( path, 'w' ){ |f| f.puts MalformedReplyBadLines }
330
+ end
331
+ self.should_receive( :invoke_git_editor )
332
+
333
+ lambda{ notes_from_reply_to_comments }.should raise_error
334
+ end
335
+
336
+ it "should add the users' comments as replies to the originals" do
337
+ self.should_receive( :existing_comments? ). and_return( true )
338
+ self.should_receive( :existing_comments ). and_return( ExistingComment )
339
+ self.should_receive( :git_dir ). and_return( "." )
340
+ self.should_receive( :git_author_name_short ). and_return( "Spec 456" )
341
+ self.should_receive( :notes_ref ).
342
+ and_return( "refs/notes/reviews/#{@user}/topic" )
343
+ self.stub!( :invoke_git_editor ) do |path|
344
+ File.open( path, 'w' ){ |f| f.write LegalReply }
345
+ end
346
+ self.should_receive( :invoke_git_editor )
347
+ self.should_receive( :git ) do
348
+ File.read( "./COMMENT_EDITMSG" ).should == CommentWithReply
349
+ end
350
+
351
+ lambda{ notes_from_reply_to_comments }.should_not raise_error
352
+ end
353
+
354
+ end
355
+
356
+
357
+ describe "#notes_to_hash" do
358
+ it "should convert notes to a hash" do
359
+ hash = notes_to_hash( ExistingComment )
360
+ hash.should_not be_nil
361
+ hash.keys.should == [ :general,
362
+ "./zombies",
363
+ "./lib/foo/zombie_repellant" ]
364
+ hash[ :general ].should == 3
365
+ hash[ "./zombies" ].should be_a Hash
366
+ hash[ "./lib/foo/zombie_repellant" ].should be_a Hash
367
+
368
+ zombie_hash = hash[ "./zombies" ]
369
+ repellant_hash = hash[ "./lib/foo/zombie_repellant" ]
370
+
371
+ zombie_hash.keys.should == [ 2 ]
372
+ repellant_hash.keys.should == [ 6, 40 ]
373
+
374
+ zombie_hash[ 2 ].should == 15
375
+ repellant_hash[ 6 ].should == 21
376
+ repellant_hash[ 40 ].should == 24
377
+ end
378
+ end
379
+
380
+ describe "#attrib" do
381
+
382
+ it "should return a 16-length string for short author names" do
383
+ attrib( "David" ).should == "David: "
384
+ end
385
+
386
+ it "should truncate very long author names" do
387
+ attrib( "DavidVonSuperLongNameStuff" ).
388
+ should == "DavidVonSuperLo:"
389
+ end
390
+
391
+ it "should respect padding for short author names" do
392
+ attrib( "David", 4 ).should == " David: "
393
+ end
394
+ end
395
+
396
+ end