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.
- data/.gitignore +1 -0
- data/Gemfile +11 -4
- data/Gemfile.lock +37 -15
- data/History.rdoc +19 -0
- data/README.rdoc +80 -95
- data/VERSION.yml +1 -1
- data/lib/git_topic.rb +105 -34
- data/lib/git_topic/cli.rb +35 -16
- data/lib/git_topic/comment.rb +2 -2
- data/lib/{core_ext.rb → git_topic/core_ext.rb} +70 -5
- data/lib/git_topic/git.rb +72 -35
- data/lib/git_topic/logger.rb +51 -0
- data/lib/git_topic/naming.rb +51 -8
- data/spec/comment_spec.rb +1 -0
- data/spec/git_topic_abandon_spec.rb +112 -0
- data/spec/git_topic_accept_spec.rb +8 -4
- data/spec/git_topic_comment_spec.rb +60 -7
- data/spec/git_topic_comments_spec.rb +1 -0
- data/spec/git_topic_done_spec.rb +8 -6
- data/spec/git_topic_install_aliases_spec.rb +1 -0
- data/spec/git_topic_logging_spec.rb +74 -0
- data/spec/git_topic_reject_spec.rb +5 -4
- data/spec/git_topic_review_spec.rb +51 -3
- data/spec/git_topic_setup_spec.rb +3 -2
- data/spec/git_topic_status_spec.rb +13 -2
- data/spec/git_topic_work_on_spec.rb +69 -9
- data/spec/spec_helper.rb +31 -12
- data/spec/template/origin/objects/16/f0fda5a88c44380ec3f687ec2e82fe702af7f7 +1 -0
- data/spec/template/origin/objects/17/5faa9939b9ac3d71ce53c42aee5e6e6a0c785c +0 -0
- data/spec/template/origin/objects/19/cf2c8a1f688b055774982d5010e6e30a664cc0 +0 -0
- data/spec/template/origin/objects/19/df194219c4296b82a9bfc8923def101c8485f1 +2 -0
- data/spec/template/origin/objects/28/222998cef35ffc5f39aa5c33c410624870e14e +0 -0
- data/spec/template/origin/objects/2b/56d40e3a8cdd99f6dbd02172231af5f44b1a4a +0 -0
- data/spec/template/origin/objects/3a/23df5be628d9dc86c3c201ed90666ca48706e8 +0 -0
- data/spec/template/origin/objects/47/a05bbad3ae0061aa6dcdefd5a2b91ef878e547 +0 -0
- data/spec/template/origin/objects/4a/f7e0cf66fa7ca6f4c64dabaf9cb4a7e75e530a +0 -0
- data/spec/template/origin/objects/51/30b0045082c311949fbe05bebbd7d77d2651fe +0 -0
- data/spec/template/origin/objects/53/348549b5f62b3d7fe308e314450c8dafb24a20 +1 -0
- data/spec/template/origin/objects/53/75f6d2da8e15dc1b93b53b794cc1c1a4b0a562 +2 -0
- data/spec/template/origin/objects/60/49ef3b01a46738c640dad45d5ea27d21ca3148 +2 -0
- data/spec/template/origin/objects/6a/671e28d05e12f0eb3a84b6c0e850acb5baa043 +0 -0
- data/spec/template/origin/objects/73/0d2b5fb7b28275d54672b24a10f9ff416151d9 +0 -0
- data/spec/template/origin/objects/77/8c48ec250d87693b1285518be6d32cd83c0d0e +0 -0
- data/spec/template/origin/objects/7b/2e0052793a417262e6b0a7049e27106e8df6df +0 -0
- data/spec/template/origin/objects/88/e563ba0dd5f822c9edcdb8a5d03f37c0b51efb +0 -0
- data/spec/template/origin/objects/8c/1eed9b8b7a0df74f6e0e4665f87f464b224e4f +1 -0
- data/spec/template/origin/objects/8e/59fbefc7a107f6c489a2ef65705f0b1944f7f0 +0 -0
- data/spec/template/origin/objects/ae/23634551b381284d41cc67060322d082f7cce4 +3 -0
- data/spec/template/origin/objects/b4/2dce0a04152ceb67d8120b5fc06cf583809590 +0 -0
- data/spec/template/origin/objects/c4/305717087f3413b5e50ae0c8037968758bf74d +0 -0
- data/spec/template/origin/objects/ca/9f1a38464dc0a7d099b7199126df2040f1b38b +0 -0
- data/spec/template/origin/objects/ce/66c4be9f1659f4621a0c709d9a87d2e6d464a2 +0 -0
- data/spec/template/origin/objects/d2/33cf6f04743aabaf4ea7ce546aed9a9758620f +1 -0
- data/spec/template/origin/objects/d3/f64a65197f61743cbe291f8c4dbaf09dbfb902 +2 -0
- data/spec/template/origin/objects/dd/1f38ef8037b85682079063d8c577c7b9f402bd +0 -0
- data/spec/template/origin/objects/df/8a312e41f9319aa4e73264d32ff65496867b37 +0 -0
- data/spec/template/origin/objects/e3/240e1f3328432e1708a2181d0b2e92bbb2b20d +1 -0
- data/spec/template/origin/objects/e8/4eaf06f50ed3b33e6846109436606668886c48 +0 -0
- data/spec/template/origin/objects/e8/b14aa4019584baf1ad3ab974503999fd170ce0 +0 -0
- data/spec/template/origin/objects/ed/ff0b85a5f8532df0bd6753d0942f4b7c0ede55 +0 -0
- data/spec/template/origin/refs/notes/reviews/USER/krakens +1 -0
- data/spec/template/origin/refs/notes/reviews/user24601/ninja-basic +1 -0
- 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
|
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
|
-
".
|
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
|
-
}.
|
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
|
85
|
-
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
-
".
|
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
|
data/lib/git_topic/comment.rb
CHANGED
@@ -75,7 +75,7 @@ module GitTopic::Comment
|
|
75
75
|
# Ceterum censeo, Carthaginem esse delendam.
|
76
76
|
#
|
77
77
|
#
|
78
|
-
}.
|
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
|
-
}.
|
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
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
29
|
-
" unless
|
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
|
50
|
-
|
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
|
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
|
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
|
111
|
+
def invoke_git_editor file
|
91
112
|
system "#{git_editor} #{file}"
|
92
113
|
end
|
93
114
|
|
94
|
-
def cmd_redirect_suffix
|
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
|
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
|
104
|
-
|
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
|
-
|
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
|
-
|
118
|
-
|
143
|
+
puts cmd if GitTopic::global_opts[:verbose]
|
144
|
+
debug cmd
|
119
145
|
|
120
|
-
|
121
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
153
|
+
if display_git_output? || opts[:show]
|
154
|
+
puts cmd_output
|
155
|
+
puts cmd_error
|
156
|
+
end
|
126
157
|
|
127
|
-
|
128
|
-
|
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] &&
|
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
|
-
".
|
171
|
+
".unindent
|
135
172
|
end
|
136
173
|
|
137
|
-
|
174
|
+
[ cmd_output, cmd_error, pstatus ]
|
138
175
|
end
|
139
176
|
|
140
177
|
end
|
141
178
|
|
142
|
-
def self.included
|
179
|
+
def self.included base
|
143
180
|
base.extend ClassMethods
|
144
181
|
end
|
145
182
|
end
|