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
data/lib/git_topic/cli.rb CHANGED
@@ -11,7 +11,8 @@ require 'git_topic'
11
11
 
12
12
  module GitTopic
13
13
  SubCommands = %w(
14
- work-on done status review comment comments accept reject install-aliases
14
+ work-on done abandon status review comment comments accept reject
15
+ install-aliases
15
16
  )
16
17
  Version = lambda {
17
18
  h = YAML::load_file( "#{File.dirname( __FILE__ )}/../../VERSION.yml" )
@@ -34,13 +35,15 @@ module GitTopic
34
35
  " )}
35
36
 
36
37
  Global Options:
37
- ".cleanup
38
+ ".unindent
38
39
  version Version
39
40
 
40
41
  opt :verbose,
41
42
  "Verbose output, including complete traces on errors."
42
43
  opt :completion_help,
43
44
  "View instructions for setting up autocompletion."
45
+ opt :no_log,
46
+ "Disable logging."
44
47
 
45
48
  stop_on SubCommands
46
49
  end
@@ -62,17 +65,18 @@ module GitTopic
62
65
  For more information, see:
63
66
 
64
67
  http://redmine.ruby-lang.org/issues/show/3465
65
- }.cleanup
68
+ }.unindent
66
69
  exit 0
67
70
  end
68
71
 
69
-
72
+ info ''
73
+ info ARGV.join( " " )
70
74
  cmd = ARGV.shift
71
75
  cmd_opts = Trollop::options do
72
76
  case cmd
73
77
  when "work-on"
74
78
  banner "
75
- git[-topic] work-on <topic>
79
+ git[-topic] work-on <topic> [<upstream> | --continue]
76
80
 
77
81
  Switches to a local work-in-progress (wip) branch for <topic>. The
78
82
  branch (and a matching remote branch) is created if necessary.
@@ -81,11 +85,23 @@ module GitTopic
81
85
  the rejected topic branch. Similarly, if this is a review topic,
82
86
  the review will be pulled and work will continue on that topic.
83
87
 
84
- <topic>'s branches HEAD will point to <upstream>. If <upstream> is
85
- omitted, it will default to the current HEAD.
88
+ <topic>'s branch's HEAD will point to <upstream>, if supplied. If
89
+ --continue is supplied instead, HEAD will point to the most recent
90
+ review (i.e. submitted) of your topic branches. If you have just
91
+ submitted a topic with git done, git work-on next-topic --continue
92
+ would begin the next topic starting from where you had left off.
93
+
94
+ If both <upstream> and --continue are omitted, <topic>'s branch's
95
+ HEAD will default to the current HEAD.
86
96
 
87
97
  Options:
88
- ".cleanup
98
+ ".unindent
99
+ when "abandon"
100
+ banner "
101
+ git[-topic] abandon [<topic>]
102
+
103
+ Deletes <topic> locally and remotely. Defaults to current topic if unspecified.
104
+ ".unindent
89
105
  when /done(-with)?/
90
106
  banner "
91
107
  git[-topic] done
@@ -94,7 +110,7 @@ module GitTopic
94
110
  remote review branch and switch back to master.
95
111
 
96
112
  Options:
97
- ".cleanup
113
+ ".unindent
98
114
  when "status"
99
115
  banner "
100
116
  git st
@@ -104,7 +120,7 @@ module GitTopic
104
120
  that can be reviewed.
105
121
 
106
122
  Options:
107
- ".cleanup
123
+ ".unindent
108
124
  opt :prepended,
109
125
  "
110
126
  Prepend status to git status output (for a complete view of
@@ -118,7 +134,7 @@ module GitTopic
118
134
  Review <topic>. If <topic> is unspecified, review the oldest (by HEAD) topic.
119
135
 
120
136
  Options:
121
- ".cleanup
137
+ ".unindent
122
138
  when "comment"
123
139
  banner "
124
140
  git[-topic] comment
@@ -153,7 +169,7 @@ module GitTopic
153
169
  comments.
154
170
 
155
171
  Options:
156
- ".cleanup
172
+ ".unindent
157
173
 
158
174
  opt :force_update,
159
175
  "
@@ -169,7 +185,7 @@ module GitTopic
169
185
  know what to do to appease the reviewer.
170
186
 
171
187
  Options:
172
- ".cleanup
188
+ ".unindent
173
189
  when "accept"
174
190
  banner "
175
191
  git[-topic] accept
@@ -180,7 +196,7 @@ module GitTopic
180
196
  should either be rejected, or you can manually rebase.
181
197
 
182
198
  Options:
183
- ".cleanup
199
+ ".unindent
184
200
  when "reject"
185
201
  banner "
186
202
  git[-topic] reject
@@ -188,7 +204,7 @@ module GitTopic
188
204
  Reject the current in-review topic.
189
205
 
190
206
  Options:
191
- ".cleanup
207
+ ".unindent
192
208
 
193
209
  opt :save_comments,
194
210
  "
@@ -211,7 +227,7 @@ module GitTopic
211
227
  st topic status --prepended
212
228
 
213
229
  Options:
214
- ".cleanup
230
+ ".unindent
215
231
 
216
232
  opt :local,
217
233
  "
@@ -235,6 +251,9 @@ module GitTopic
235
251
  :upstream => upstream
236
252
  })
237
253
  work_on topic, opts
254
+ when "abandon"
255
+ topic = ARGV.shift
256
+ abandon topic
238
257
  when /done(-with)?/
239
258
  topic = ARGV.shift
240
259
  done topic, opts
@@ -75,7 +75,7 @@ module GitTopic::Comment
75
75
  # Ceterum censeo, Carthaginem esse delendam.
76
76
  #
77
77
  #
78
- }.cleanup
78
+ }.unindent
79
79
  f.puts( fs_notes.lines.map do |line|
80
80
  "# #{line}"
81
81
  end.join )
@@ -235,7 +235,7 @@ module GitTopic::Comment
235
235
  # Douglas Adams
236
236
  #
237
237
  #
238
- }.cleanup
238
+ }.unindent
239
239
  f.puts( notes.lines.map do |line|
240
240
  "# #{line}"
241
241
  end.join )
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'open3'
4
+
3
5
  require 'active_support'
4
6
  require 'active_support/inflector'
5
7
  require 'active_support/core_ext/module/aliasing'
@@ -7,7 +9,7 @@ require 'active_support/core_ext/string/inflections'
7
9
 
8
10
 
9
11
  class String
10
- def cleanup
12
+ def unindent
11
13
  indent = (index /^([ \t]+)/; $1) || ''
12
14
  regex = /^#{Regexp::escape( indent )}/
13
15
  strip.gsub regex, ''
@@ -17,6 +19,10 @@ class String
17
19
  strip.gsub( /\n\s+/, ' ' )
18
20
  end
19
21
 
22
+ def blank?
23
+ nil? || empty?
24
+ end
25
+
20
26
  # Annoyingly, the useful version of pluralize in texthelpers isn't in the
21
27
  # string core extensions.
22
28
  def pluralize_with_count( count )
@@ -49,11 +55,18 @@ class String
49
55
  original_indentation = $1.size
50
56
  end
51
57
 
58
+ # If the line has more than the original indentation, it is ‘indented’
59
+ # and to be left unformatted
52
60
  if line =~ /^\s{#{original_indentation}}\s/
53
61
  sbuf << "\n#{indent}" unless start_of_line.call
54
62
  sbuf << line.slice( original_indentation, line.size )
55
63
  new_line.call
56
64
  next
65
+ # Leave line breaks intact to allow paragraph separataion.
66
+ elsif line.strip.empty?
67
+ sbuf << "\n"
68
+ new_line.call
69
+ next
57
70
  end
58
71
 
59
72
  leading_space =
@@ -78,7 +91,7 @@ class String
78
91
 
79
92
  sbuf[ 0...heading.size ] = heading
80
93
 
81
- sbuf
94
+ sbuf.rstrip
82
95
  end
83
96
 
84
97
  def wrap!( width, heading_indentation=nil, heading=nil )
@@ -89,8 +102,60 @@ end
89
102
 
90
103
 
91
104
  class Object
92
- # Deep duplicate via remarshaling. Not always applicable.
93
- def ddup
94
- Marshal.load( Marshal.dump( self ))
105
+ # Deep duplicate via remarshaling. Not always applicable.
106
+ def ddup
107
+ Marshal.load( Marshal.dump( self ))
108
+ end
109
+ end
110
+
111
+
112
+ module Kernel
113
+ alias_method :λ, :lambda
114
+ end
115
+
116
+
117
+ class IO
118
+
119
+ def self.dpopen *args, &block
120
+ Execution.new.run *args, &block
121
+ end
122
+
123
+ class Execution
124
+
125
+ attr_accessor :output_callback, :error_callback
126
+
127
+ def run *args, &block
128
+ self.output_callback = proc{ }
129
+ self.error_callback = proc{ }
130
+
131
+ # Let the block set up error, output hooks
132
+ self.instance_eval &block
133
+
134
+ Open3.popen3 *args do |pin, pout, perr, wait_thread|
135
+ nothing_read = false
136
+ until nothing_read do
137
+ nothing_read = true
138
+ if s = pout.gets
139
+ self.output_callback[ s ]
140
+ nothing_read = false
141
+ end
142
+
143
+ if s = perr.gets
144
+ self.error_callback[ s ]
145
+ nothing_read = false
146
+ end
147
+ end
148
+
149
+ wait_thread.value
150
+ end
151
+ end
152
+
153
+ def on_error &block
154
+ self.error_callback = block
95
155
  end
156
+
157
+ def on_output &block
158
+ self.output_callback = block
159
+ end
160
+ end
96
161
  end
data/lib/git_topic/git.rb CHANGED
@@ -25,8 +25,8 @@ module GitTopic::Git
25
25
  @@git_author_name_short ||= (
26
26
  full_name = capture_git( "config user.name" )
27
27
  raise "
28
- whatever
29
- " unless $?.success?
28
+ Unable to determine author name from git config user.name
29
+ " unless $pstatus.success?
30
30
 
31
31
  parts = full_name.split( " " )
32
32
  fname = parts.shift
@@ -39,19 +39,33 @@ module GitTopic::Git
39
39
 
40
40
  def working_tree_clean?
41
41
  git [ "diff --quiet", "diff --quiet --cached" ]
42
- $?.success?
43
42
  end
44
43
 
45
44
  def working_tree_dirty?
46
45
  not working_tree_clean?
47
46
  end
48
47
 
49
- def existing_comments?( branch=current_branch )
50
- ref = notes_ref( branch )
51
- not capture_git( "notes --ref #{ref} list" ).chomp.empty?
48
+ def rebased_to_master? from=nil
49
+ capture_git( "rev-list -n 1 #{from}..master" ).strip.empty?
52
50
  end
53
51
 
54
- def existing_comments( spec=nil )
52
+ def existing_comments? branch=current_branch
53
+ ref = notes_ref( branch )
54
+ # The list of all notes objects, and the commits they annotate, for the
55
+ # given ref
56
+ notes_list = capture_git( "notes --ref #{ref} list" ).split "\n"
57
+ # simply checking ! notes_list.empty? tells us that there were some
58
+ # comments on this ref at some point. However, they may be comments on
59
+ # commits from a previous topic with the same name, so we check to ensure
60
+ # at least one of them is not an ancestor of origin/master (i.e. a commit
61
+ # that has already been accepted).
62
+ ! notes_list.find do |pair|
63
+ commit = pair.split( ' ' ).last
64
+ ! capture_git( "rev-list -n 1 origin/master..#{commit}" ).chomp.empty?
65
+ end.nil?
66
+ end
67
+
68
+ def existing_comments spec=nil
55
69
  ref = notes_ref( *[ spec ].compact )
56
70
  capture_git( "notes --ref #{ref} show" ).chomp
57
71
  end
@@ -69,6 +83,13 @@ module GitTopic::Git
69
83
  capture_git( "name-rev --name-only HEAD" )
70
84
  end
71
85
 
86
+ def ref_age ref=current_branch
87
+ date_str = capture_git "log #{ref}^..#{ref} --pretty=%aD"
88
+ return nil if date_str.empty?
89
+
90
+ ( Date.today - Date.parse( date_str )).to_i
91
+ end
92
+
72
93
 
73
94
  def display_git_output?
74
95
  @@display_git_output ||= false
@@ -79,7 +100,7 @@ module GitTopic::Git
79
100
  end
80
101
 
81
102
 
82
- def switch_to_branch( branch, tracking=nil )
103
+ def switch_to_branch branch, tracking=nil
83
104
  if branches.include?( branch )
84
105
  "checkout #{branch}"
85
106
  else
@@ -87,59 +108,75 @@ module GitTopic::Git
87
108
  end
88
109
  end
89
110
 
90
- def invoke_git_editor( file )
111
+ def invoke_git_editor file
91
112
  system "#{git_editor} #{file}"
92
113
  end
93
114
 
94
- def cmd_redirect_suffix( opts )
115
+ def cmd_redirect_suffix opts
95
116
  if !opts[:show] && !display_git_output?
96
117
  "> /dev/null 2> /dev/null"
97
118
  end
98
119
  end
99
120
 
100
- def git( cmds=[], opts={} )
121
+ def git *args
122
+ output, err, pstatus = _git *args
123
+ pstatus && pstatus.success?
124
+ end
125
+
126
+ def capture_git *args
127
+ output, err, pstatus = _git *args
128
+ output
129
+ end
130
+
131
+
132
+ protected
133
+
134
+ def _git cmds=[], opts={}
101
135
  opts.assert_valid_keys :must_succeed, :show
102
136
 
103
- cmds = [cmds] if cmds.is_a? String
104
- redir = cmd_redirect_suffix( opts )
105
- cmd = cmds.map{|c| "git #{c} #{redir}"}.join( " && " )
137
+ cmds = [ cmds ] if cmds.is_a? String
138
+ return if cmds.empty?
106
139
 
107
- puts cmd if GitTopic::global_opts[:verbose]
108
- result = system( cmd )
140
+ cmd = "#{cmds.map{|c| "git #{c}"}.join( " && " )}"
109
141
 
110
- if opts[:must_succeed] && !$?.success?
111
- raise "
112
- Required git command failed:\n #{cmd}.\n re-run with --verbose to
113
- see git output.
114
- ".cleanup
115
- end
116
142
 
117
- result
118
- end
143
+ puts cmd if GitTopic::global_opts[:verbose]
144
+ debug cmd
119
145
 
120
- def capture_git( cmds=[], opts={} )
121
- opts.assert_valid_keys :must_succeed
146
+ cmd_error = ''
147
+ cmd_output = ''
148
+ $pstatus = pstatus = IO.dpopen( cmd ) do
149
+ on_output{ |o| cmd_output << o }
150
+ on_error{ |e| cmd_error << e }
151
+ end
122
152
 
123
- cmds = [cmds] if cmds.is_a? String
124
- redir = "2> /dev/null" unless display_git_output?
125
- cmd = "#{cmds.map{|c| "git #{c} #{redir}"}.join( " && " )}"
153
+ if display_git_output? || opts[:show]
154
+ puts cmd_output
155
+ puts cmd_error
156
+ end
126
157
 
127
- puts cmd if GitTopic::global_opts[:verbose]
128
- result = `#{cmd}`
158
+ debug cmd_output unless cmd_output.empty?
159
+ unless cmd_error.empty?
160
+ if pstatus.success?
161
+ debug cmd_error
162
+ else
163
+ warn cmd_error
164
+ end
165
+ end
129
166
 
130
- if opts[:must_succeed] && !$?.success?
167
+ if opts[:must_succeed] && ! pstatus.success?
131
168
  raise "
132
169
  Required git command failed:\n #{cmd}.\n re-run with --verbose to
133
170
  see git output.
134
- ".cleanup
171
+ ".unindent
135
172
  end
136
173
 
137
- result
174
+ [ cmd_output, cmd_error, pstatus ]
138
175
  end
139
176
 
140
177
  end
141
178
 
142
- def self.included( base )
179
+ def self.included base
143
180
  base.extend ClassMethods
144
181
  end
145
182
  end