git-topic 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +11 -4
  3. data/Gemfile.lock +37 -15
  4. data/History.rdoc +19 -0
  5. data/README.rdoc +80 -95
  6. data/VERSION.yml +1 -1
  7. data/lib/git_topic.rb +105 -34
  8. data/lib/git_topic/cli.rb +35 -16
  9. data/lib/git_topic/comment.rb +2 -2
  10. data/lib/{core_ext.rb → git_topic/core_ext.rb} +70 -5
  11. data/lib/git_topic/git.rb +72 -35
  12. data/lib/git_topic/logger.rb +51 -0
  13. data/lib/git_topic/naming.rb +51 -8
  14. data/spec/comment_spec.rb +1 -0
  15. data/spec/git_topic_abandon_spec.rb +112 -0
  16. data/spec/git_topic_accept_spec.rb +8 -4
  17. data/spec/git_topic_comment_spec.rb +60 -7
  18. data/spec/git_topic_comments_spec.rb +1 -0
  19. data/spec/git_topic_done_spec.rb +8 -6
  20. data/spec/git_topic_install_aliases_spec.rb +1 -0
  21. data/spec/git_topic_logging_spec.rb +74 -0
  22. data/spec/git_topic_reject_spec.rb +5 -4
  23. data/spec/git_topic_review_spec.rb +51 -3
  24. data/spec/git_topic_setup_spec.rb +3 -2
  25. data/spec/git_topic_status_spec.rb +13 -2
  26. data/spec/git_topic_work_on_spec.rb +69 -9
  27. data/spec/spec_helper.rb +31 -12
  28. data/spec/template/origin/objects/16/f0fda5a88c44380ec3f687ec2e82fe702af7f7 +1 -0
  29. data/spec/template/origin/objects/17/5faa9939b9ac3d71ce53c42aee5e6e6a0c785c +0 -0
  30. data/spec/template/origin/objects/19/cf2c8a1f688b055774982d5010e6e30a664cc0 +0 -0
  31. data/spec/template/origin/objects/19/df194219c4296b82a9bfc8923def101c8485f1 +2 -0
  32. data/spec/template/origin/objects/28/222998cef35ffc5f39aa5c33c410624870e14e +0 -0
  33. data/spec/template/origin/objects/2b/56d40e3a8cdd99f6dbd02172231af5f44b1a4a +0 -0
  34. data/spec/template/origin/objects/3a/23df5be628d9dc86c3c201ed90666ca48706e8 +0 -0
  35. data/spec/template/origin/objects/47/a05bbad3ae0061aa6dcdefd5a2b91ef878e547 +0 -0
  36. data/spec/template/origin/objects/4a/f7e0cf66fa7ca6f4c64dabaf9cb4a7e75e530a +0 -0
  37. data/spec/template/origin/objects/51/30b0045082c311949fbe05bebbd7d77d2651fe +0 -0
  38. data/spec/template/origin/objects/53/348549b5f62b3d7fe308e314450c8dafb24a20 +1 -0
  39. data/spec/template/origin/objects/53/75f6d2da8e15dc1b93b53b794cc1c1a4b0a562 +2 -0
  40. data/spec/template/origin/objects/60/49ef3b01a46738c640dad45d5ea27d21ca3148 +2 -0
  41. data/spec/template/origin/objects/6a/671e28d05e12f0eb3a84b6c0e850acb5baa043 +0 -0
  42. data/spec/template/origin/objects/73/0d2b5fb7b28275d54672b24a10f9ff416151d9 +0 -0
  43. data/spec/template/origin/objects/77/8c48ec250d87693b1285518be6d32cd83c0d0e +0 -0
  44. data/spec/template/origin/objects/7b/2e0052793a417262e6b0a7049e27106e8df6df +0 -0
  45. data/spec/template/origin/objects/88/e563ba0dd5f822c9edcdb8a5d03f37c0b51efb +0 -0
  46. data/spec/template/origin/objects/8c/1eed9b8b7a0df74f6e0e4665f87f464b224e4f +1 -0
  47. data/spec/template/origin/objects/8e/59fbefc7a107f6c489a2ef65705f0b1944f7f0 +0 -0
  48. data/spec/template/origin/objects/ae/23634551b381284d41cc67060322d082f7cce4 +3 -0
  49. data/spec/template/origin/objects/b4/2dce0a04152ceb67d8120b5fc06cf583809590 +0 -0
  50. data/spec/template/origin/objects/c4/305717087f3413b5e50ae0c8037968758bf74d +0 -0
  51. data/spec/template/origin/objects/ca/9f1a38464dc0a7d099b7199126df2040f1b38b +0 -0
  52. data/spec/template/origin/objects/ce/66c4be9f1659f4621a0c709d9a87d2e6d464a2 +0 -0
  53. data/spec/template/origin/objects/d2/33cf6f04743aabaf4ea7ce546aed9a9758620f +1 -0
  54. data/spec/template/origin/objects/d3/f64a65197f61743cbe291f8c4dbaf09dbfb902 +2 -0
  55. data/spec/template/origin/objects/dd/1f38ef8037b85682079063d8c577c7b9f402bd +0 -0
  56. data/spec/template/origin/objects/df/8a312e41f9319aa4e73264d32ff65496867b37 +0 -0
  57. data/spec/template/origin/objects/e3/240e1f3328432e1708a2181d0b2e92bbb2b20d +1 -0
  58. data/spec/template/origin/objects/e8/4eaf06f50ed3b33e6846109436606668886c48 +0 -0
  59. data/spec/template/origin/objects/e8/b14aa4019584baf1ad3ab974503999fd170ce0 +0 -0
  60. data/spec/template/origin/objects/ed/ff0b85a5f8532df0bd6753d0942f4b7c0ede55 +0 -0
  61. data/spec/template/origin/refs/notes/reviews/USER/krakens +1 -0
  62. data/spec/template/origin/refs/notes/reviews/user24601/ninja-basic +1 -0
  63. metadata +87 -23
@@ -0,0 +1,51 @@
1
+ module GitTopic
2
+ class << self; attr_accessor :global_opts end
3
+ self.global_opts = {}
4
+
5
+ class << self
6
+ %w( debug info warn error fatal unknown ).each do |m|
7
+ define_method m do |*args, &block|
8
+ GitTopic::Logger.send( m, *args, &block )
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+
15
+ module GitTopic::Logger
16
+ class << self
17
+ def add method, undecorated_message, *args, &block
18
+ return if GitTopic.global_opts[:no_log]
19
+
20
+ message =
21
+ unless undecorated_message.blank?
22
+ sprintf '%-5s %s %s',
23
+ method.upcase,
24
+ DateTime.now.strftime( '[%d %b %Y %H:%M:%S]' ),
25
+ undecorated_message
26
+ else
27
+ ''
28
+ end
29
+
30
+ logger.send method, message, *args, &block
31
+ end
32
+
33
+ %w( debug info warn error fatal unknown ).each do |m|
34
+ define_method m do |*args, &block|
35
+ GitTopic::Logger.add( m, *args, &block )
36
+ end
37
+ end
38
+
39
+
40
+ protected
41
+
42
+ def logger
43
+ @logger ||= (
44
+ log_dir = "#{ENV['HOME']}/.git_topic"
45
+ FileUtils.mkdir( log_dir ) unless File.exists? log_dir
46
+
47
+ ActiveSupport::BufferedLogger.new "#{ENV['HOME']}/.git_topic/log"
48
+ )
49
+ end
50
+ end
51
+ end
@@ -21,24 +21,35 @@ module GitTopic::Naming
21
21
  "rejected/#{user}/#{strip_namespace topic}"
22
22
  end
23
23
 
24
- def review_branch( topic, user=user )
25
- "review/#{user}/#{strip_namespace topic}"
24
+ def review_branch( topic, user=user, opts={} )
25
+ rb = "review/#{user}/#{strip_namespace topic}"
26
+ if opts[:remote]
27
+ "origin/#{rb}"
28
+ else
29
+ rb
30
+ end
26
31
  end
27
32
 
28
33
  def remote_rejected_branch( topic, user=user )
29
34
  "rejected/#{user}/#{strip_namespace topic}"
30
35
  end
31
36
 
32
- def remote_branch( spec=current_branch )
37
+ def remote_branch( spec=current_branch, opts={} )
33
38
  parts = topic_parts( spec )
34
39
 
35
- remote_branches.find do |remote_branch|
40
+ rb = remote_branches.find do |remote_branch|
36
41
  bp = topic_parts( remote_branch )
37
42
 
38
43
  parts.all? do |part, value|
39
44
  bp[part] == value
40
45
  end
41
46
  end
47
+
48
+ if opts[:strip_remote]
49
+ rb.gsub %r{^origin/}, ''
50
+ else
51
+ rb
52
+ end
42
53
  end
43
54
 
44
55
 
@@ -82,7 +93,7 @@ module GitTopic::Naming
82
93
  when 1
83
94
  p[:topic] = parts.first
84
95
  else
85
- raise "Unexpected topic: #{ref}"
96
+ return nil
86
97
  end
87
98
 
88
99
  if opts[:lookup] && p[:user].nil?
@@ -123,14 +134,32 @@ module GitTopic::Naming
123
134
  @@remote_branches ||= capture_git( "branch -r --no-color" ).split( "\n" ).map{|b| b[2..-1]}
124
135
  end
125
136
 
126
- def others_review_branches
137
+ def review_branches
127
138
  remote_branches.select do
128
139
  |b| b =~ %r{/review/}
129
- end.reject do |b|
140
+ end
141
+ end
142
+
143
+ def others_review_branches
144
+ review_branches.reject do |b|
145
+ b =~ %r{/#{user}/}
146
+ end
147
+ end
148
+
149
+ def user_review_branches user=user
150
+ review_branches.select do |b|
130
151
  b =~ %r{/#{user}/}
131
152
  end
132
153
  end
133
154
 
155
+ # Returns review branches as a hash of namespace → user → topic* hashes,
156
+ # e.g.
157
+ #
158
+ # {
159
+ # :review => { 'davidjh' => [ 'topic1', 'topic2' ]},
160
+ # :rejected => { 'davidjh' => [ 'topic3' ]}
161
+ # }
162
+ #
134
163
  def remote_branches_organized
135
164
  @@remote_branches_organized ||= (
136
165
  rb = remote_branches.dup
@@ -177,6 +206,20 @@ module GitTopic::Naming
177
206
  end.strip[ 1..-2 ] # chomp the leading and trailing parenthesis
178
207
  end
179
208
 
209
+ def newest_pending_branch
210
+ return nil if user_review_branches.empty?
211
+
212
+ commits_by_age = capture_git([
213
+ "log --date-order --pretty=format:%d",
214
+ "^origin/master #{user_review_branches.join( ' ' )}",
215
+ ].join( " " )).split( "\n" )
216
+
217
+ commits_by_age.find do |ref|
218
+ # no ‘,’, i.e. only one ref matches the commit
219
+ !ref.strip.empty? && ref.index( ',' ).nil?
220
+ end.strip[ 1..-2 ] # chomp the leading and trailing parenthesis
221
+ end
222
+
180
223
  def oldest_review_user_topic
181
224
  user_topic_name( oldest_review_branch )
182
225
  end
@@ -187,7 +230,7 @@ module GitTopic::Naming
187
230
 
188
231
  end
189
232
 
190
- def self.included( base )
233
+ def self.included base
191
234
  base.extend ClassMethods
192
235
  end
193
236
  end
data/spec/comment_spec.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
 
3
4
  DiffWithNonCommentAddition = %Q{
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe GitTopic do
5
+
6
+ describe "#abandon" do
7
+ before( :each ){ use_repo 'in-progress' }
8
+
9
+
10
+ shared_examples_for :abandon_when_passed_an_argument do
11
+ it "should error if the arg does not name a topic" do
12
+ λ do
13
+ GitTopic.abandon 'non-existant-topic'
14
+ end.should raise_error
15
+ end
16
+
17
+ it "should remove the specified wip topic, locally and remotely" do
18
+ branch = "wip/#{@user}/zombie-basic"
19
+
20
+ previous_local_branches = git_branches
21
+ previous_remote_branches = git_remote_branches
22
+ previous_local_branches.should include branch
23
+ previous_remote_branches.should include branch
24
+
25
+ λ{ GitTopic.abandon "wip/#{@user}/zombie-basic" }.should_not raise_error
26
+
27
+ git_branches.should == ( previous_local_branches - [ branch ])
28
+ git_remote_branches.should == ( previous_remote_branches - [ branch ])
29
+ end
30
+
31
+ it "should remove the specified review topic" do
32
+ branch = "review/#{@user}/pirates"
33
+
34
+ previous_remote_branches = git_remote_branches
35
+ previous_remote_branches.should include branch
36
+
37
+ λ{ GitTopic.abandon 'pirates' }.should_not raise_error
38
+
39
+ git_remote_branches.should == ( previous_remote_branches - [ branch ])
40
+ end
41
+ end
42
+
43
+
44
+ describe "when on a wip topic branch" do
45
+ before :each do
46
+ λ{ GitTopic.work_on 'a-new-topic' }.should_not raise_error
47
+ end
48
+
49
+ it_should_behave_like :abandon_when_passed_an_argument
50
+
51
+ it "should remove the local topic" do
52
+ branch = "wip/#{@user}/a-new-topic"
53
+
54
+ previous_local_branches = git_branches
55
+ previous_remote_branches = git_remote_branches
56
+ previous_local_branches.should include branch
57
+ previous_remote_branches.should include branch
58
+
59
+ λ{ GitTopic.abandon }.should_not raise_error
60
+
61
+ git_branches.should == ( previous_local_branches - [ branch ])
62
+ git_remote_branches.should == ( previous_remote_branches - [ branch ])
63
+ end
64
+ end
65
+
66
+ describe "when on a review topic branch" do
67
+ before :each do
68
+ λ{ GitTopic.review }.should_not raise_error
69
+ git_head.should == '0ce06c616769768f09f5e629cfcc68eabe3dee81'
70
+ end
71
+
72
+ it_should_behave_like :abandon_when_passed_an_argument
73
+
74
+ it "should error" do
75
+ λ{ GitTopic.abandon }.should raise_error
76
+ end
77
+ end
78
+
79
+ describe "when on a rejected topic branch" do
80
+ before :each do
81
+ λ{ GitTopic.work_on 'krakens' }.should_not raise_error
82
+ git_head.should == '44ffd9c9c8b52b201659e3ad318cdad6ec836b46'
83
+ end
84
+ it_should_behave_like :abandon_when_passed_an_argument
85
+
86
+ it "should remove the topic, locally and remotely" do
87
+ branch = "wip/#{@user}/krakens"
88
+
89
+ previous_local_branches = git_branches
90
+ previous_remote_branches = git_remote_branches
91
+ previous_local_branches.should include branch
92
+ previous_remote_branches.should include branch
93
+
94
+ λ{ GitTopic.abandon }.should_not raise_error
95
+
96
+ git_branches.should == ( previous_local_branches - [ branch ])
97
+ git_remote_branches.should == ( previous_remote_branches - [ branch ])
98
+ end
99
+ end
100
+
101
+ describe "when on some other branch" do
102
+ before :each do
103
+ system "git checkout -q master > /dev/null 2> /dev/null"
104
+ end
105
+ it_should_behave_like :abandon_when_passed_an_argument
106
+
107
+ it "should error" do
108
+ λ{ GitTopic.abandon }.should raise_error
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
 
3
4
 
@@ -36,25 +37,28 @@ describe GitTopic do
36
37
 
37
38
  it "should fail if the working tree is dirty" do
38
39
  dirty_branch!
39
- lambda{ GitTopic.accept }.should raise_error
40
+ λ{ GitTopic.accept }.should raise_error
40
41
  end
41
42
  end
42
43
  end
43
44
 
45
+ # Normally the user should not even wind up in this state because +review+
46
+ # will complain if the branch does not automatically fast-forward.
44
47
  describe "while on a review branch that does not FF" do
45
48
  before( :each ) do
46
49
  use_repo 'in-progress'
50
+ GitTopic.review 'user24601/zombie-basic'
47
51
  system "
48
52
  git checkout master > /dev/null 2> /dev/null &&
49
53
  git merge origin/wip/prevent-ff > /dev/null 2> /dev/null
50
54
  "
51
55
  @original_git_Head = git_head
52
- GitTopic.review 'user24601/zombie-basic'
56
+ system "git checkout --quiet review/user24601/zombie-basic"
53
57
  end
54
58
 
55
59
  it "should refuse to accept the review branch" do
56
60
  git_branch.should == 'review/user24601/zombie-basic'
57
- lambda{ GitTopic.accept }.should raise_error
61
+ λ{ GitTopic.accept }.should raise_error
58
62
  git_branch.should == 'review/user24601/zombie-basic'
59
63
  git_remote_branches.should include( 'review/user24601/zombie-basic' )
60
64
 
@@ -67,7 +71,7 @@ describe GitTopic do
67
71
  before( :each ) { use_repo 'in-progress' }
68
72
 
69
73
  it "should fail" do
70
- lambda{ GitTopic.accept }.should raise_error
74
+ λ{ GitTopic.accept }.should raise_error
71
75
  end
72
76
  end
73
77
  end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
 
3
4
  GeneralCommentOnly = %Q{
@@ -24,9 +25,30 @@ Spec 123: I have some general comments, mostly relating to the quality
24
25
  This should take care of our issues with zombies.
25
26
  }.strip
26
27
 
28
+ FirstCommentMultiParagraph = %Q{
29
+ Spec 123: I have some general comments, mostly relating to the quality
30
+ of our zombie-control policies. Basically, they're not
31
+ working.
32
+
33
+ ./zombies
34
+
35
+ Line 2
36
+ Spec 123: I suggest we do the following instead:
37
+ zombies.each{ |z| reason_with( z )}
38
+ zomies.select do |z|
39
+ z.unconvinced?
40
+ end.each do |z|
41
+ destroy z
42
+ end
43
+ This should take care of our issues with zombies.
44
+
45
+ On another subject entirely, I rather like herring.
46
+ }.strip
47
+
27
48
  FirstCommentUpdated = %Q{
28
49
  Spec 123: I agree, though I wonder if maybe we've become a little too
29
50
  obsessed with bacon. Umm, wait, sorry, wrong thread.
51
+
30
52
  There is no way this is going to work. Sorry, but there's just not.
31
53
 
32
54
  ./ninjas
@@ -114,7 +136,7 @@ describe GitTopic do
114
136
  f.puts %Q{
115
137
  I have some general comments, mostly relating to the quality of our
116
138
  zombie-control policies. Basically, they're not working.
117
- }.cleanup
139
+ }.unindent
118
140
  end
119
141
  end
120
142
  end
@@ -176,7 +198,7 @@ describe GitTopic do
176
198
  # destroy z
177
199
  # end
178
200
  # This should take care of our issues with zombies.
179
- }.cleanup
201
+ }.unindent
180
202
  end
181
203
  lambda{ GitTopic.comment }.should raise_error
182
204
  end
@@ -214,7 +236,7 @@ describe GitTopic do
214
236
  # destroy z
215
237
  # end
216
238
  # This should take care of our issues with zombies.
217
- }.cleanup
239
+ }.unindent
218
240
  end
219
241
  GitTopic.should_receive( :invoke_git_editor ).once
220
242
  GitTopic.should_receive(
@@ -233,6 +255,38 @@ describe GitTopic do
233
255
  git_diff.should be_empty
234
256
  end
235
257
 
258
+ it "should respect paragraph breaks (double newline) in formatting" do
259
+ File.open( 'zombies', 'a' ) do |f|
260
+ f.puts %Q{
261
+ # I suggest we do the following instead:
262
+ # zombies.each{ |z| reason_with( z )}
263
+ # zomies.select do |z|
264
+ # z.unconvinced?
265
+ # end.each do |z|
266
+ # destroy z
267
+ # end
268
+ # This should take care of our issues with zombies.
269
+ #
270
+ # On another subject entirely, I rather like herring.
271
+ }.unindent
272
+ end
273
+ GitTopic.should_receive( :invoke_git_editor ).once
274
+ GitTopic.should_receive(
275
+ :git_author_name_short
276
+ ).once.and_return( "Spec 123" )
277
+
278
+ lambda{ GitTopic.comment }.should_not raise_error
279
+ git_notes_list(
280
+ "refs/notes/reviews/user24601/zombie-basic"
281
+ ).should_not be_empty
282
+
283
+ git_notes_show(
284
+ "refs/notes/reviews/user24601/zombie-basic"
285
+ ).should == FirstCommentMultiParagraph
286
+
287
+ git_diff.should be_empty
288
+ end
289
+
236
290
  end
237
291
 
238
292
 
@@ -261,7 +315,7 @@ describe GitTopic do
261
315
  # destroy z
262
316
  # end
263
317
  # This should take care of our issues with zombies.
264
- }.cleanup
318
+ }.unindent
265
319
  end
266
320
 
267
321
  lambda{ GitTopic.comment }.should raise_error
@@ -273,7 +327,7 @@ describe GitTopic do
273
327
  # I suggest we do the following instead:
274
328
  # everyone.giveup
275
329
  # This should take care of our issues with zombies.
276
- }.cleanup
330
+ }.unindent
277
331
  end
278
332
 
279
333
  GitTopic.should_receive( :invoke_git_editor ).once
@@ -281,7 +335,6 @@ describe GitTopic do
281
335
  :git_author_name_short
282
336
  ).once.and_return( "Spec 123" )
283
337
 
284
-
285
338
  lambda do
286
339
  GitTopic.comment :force_update => true
287
340
  end.should_not raise_error
@@ -401,7 +454,7 @@ describe GitTopic do
401
454
  # destroy z
402
455
  # end
403
456
  # This should take care of our issues with zombies.
404
- }.cleanup
457
+ }.unindent
405
458
  end
406
459
 
407
460
  lambda{ GitTopic.comment }.should raise_error