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