git-topic 0.2.5 → 0.2.6

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.
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