git-topic 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.gvimrc +23 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.vimproject +26 -0
- data/.vimrc +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +63 -0
- data/History.txt +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +192 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/bin/git-topic +143 -0
- data/git-topic.gemspec +127 -0
- data/lib/git-topic.rb +375 -0
- data/lib/util.rb +34 -0
- data/spec/git-topic_spec.rb +378 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/template/origin/HEAD +1 -0
- data/spec/template/origin/config +7 -0
- data/spec/template/origin/description +1 -0
- data/spec/template/origin/hooks/applypatch-msg.sample +15 -0
- data/spec/template/origin/hooks/commit-msg.sample +24 -0
- data/spec/template/origin/hooks/post-commit.sample +8 -0
- data/spec/template/origin/hooks/post-receive.sample +15 -0
- data/spec/template/origin/hooks/post-update.sample +8 -0
- data/spec/template/origin/hooks/pre-applypatch.sample +14 -0
- data/spec/template/origin/hooks/pre-commit.sample +46 -0
- data/spec/template/origin/hooks/pre-rebase.sample +169 -0
- data/spec/template/origin/hooks/prepare-commit-msg.sample +36 -0
- data/spec/template/origin/hooks/update.sample +128 -0
- data/spec/template/origin/info/exclude +6 -0
- data/spec/template/origin/objects/0a/da6d051b94cd0df50f5a0b7229aec26f0d2cdf +0 -0
- data/spec/template/origin/objects/0c/e06c616769768f09f5e629cfcc68eabe3dee81 +0 -0
- data/spec/template/origin/objects/20/049991cdafdce826f5a3c01e10ffa84d6997ec +0 -0
- data/spec/template/origin/objects/33/1d827fd47fb234af54e3a4bbf8c6705e9116cc +3 -0
- data/spec/template/origin/objects/41/51899b742fd6b1c873b177b9d13451682089bc +0 -0
- data/spec/template/origin/objects/44/ffd9c9c8b52b201659e3ad318cdad6ec836b46 +0 -0
- data/spec/template/origin/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 +0 -0
- data/spec/template/origin/objects/55/eeb01bdf874d1a35870bcf24a970c475c63344 +0 -0
- data/spec/template/origin/objects/8d/09f9b8d80ce282218125cb0cbf53cccf022203 +0 -0
- data/spec/template/origin/objects/b4/8e68d5cac189af36abe48e893d11c24b7b2a19 +0 -0
- data/spec/template/origin/objects/c0/838ed2ee8f2e83c8bda859fc5e332b92f0a5a3 +1 -0
- data/spec/template/origin/objects/cd/f7b9dbc4911a0d1404db54cde2ed448f6a6afd +0 -0
- data/spec/template/origin/objects/d2/6b33daea1ed9823a189992bba38fbc913483c1 +0 -0
- data/spec/template/origin/objects/fe/4e254557e19f338f40ccfdc00a7517771db880 +0 -0
- data/spec/template/origin/refs/heads/master +1 -0
- data/spec/template/origin/refs/heads/rejected/davidjh/krakens +1 -0
- data/spec/template/origin/refs/heads/review/davidjh/pirates +1 -0
- data/spec/template/origin/refs/heads/review/user24601/ninja-basic +1 -0
- data/spec/template/origin/refs/heads/review/user24601/zombie-basic +1 -0
- metadata +158 -0
data/git-topic.gemspec
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{git-topic}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["David J. Hamilton"]
|
12
|
+
s.date = %q{2010-07-08}
|
13
|
+
s.default_executable = %q{git-topic}
|
14
|
+
s.description = %q{
|
15
|
+
gem command around reviewed topic branches. Supports workflow of the form:
|
16
|
+
|
17
|
+
# alexander:
|
18
|
+
git work-on <topic>
|
19
|
+
git done
|
20
|
+
|
21
|
+
# bismarck:
|
22
|
+
git status # notice a review branch
|
23
|
+
git review <topic>
|
24
|
+
# happy, merge into master, push and cleanup
|
25
|
+
git accept
|
26
|
+
|
27
|
+
git review <topic2>
|
28
|
+
# unhappy
|
29
|
+
git reject
|
30
|
+
|
31
|
+
# alexander:
|
32
|
+
git status # notice rejected topic
|
33
|
+
git work-on <topic>
|
34
|
+
|
35
|
+
see README.rdoc for more (any) details.
|
36
|
+
}
|
37
|
+
s.email = %q{git-topic@hjdivad.com}
|
38
|
+
s.executables = ["git-topic"]
|
39
|
+
s.extra_rdoc_files = [
|
40
|
+
"LICENSE",
|
41
|
+
"README.rdoc"
|
42
|
+
]
|
43
|
+
s.files = [
|
44
|
+
".gitignore",
|
45
|
+
".gvimrc",
|
46
|
+
".rspec",
|
47
|
+
".rvmrc",
|
48
|
+
".vimproject",
|
49
|
+
".vimrc",
|
50
|
+
"Gemfile",
|
51
|
+
"Gemfile.lock",
|
52
|
+
"History.txt",
|
53
|
+
"LICENSE",
|
54
|
+
"README.rdoc",
|
55
|
+
"Rakefile",
|
56
|
+
"VERSION",
|
57
|
+
"autotest/discover.rb",
|
58
|
+
"bin/git-topic",
|
59
|
+
"git-topic.gemspec",
|
60
|
+
"lib/git-topic.rb",
|
61
|
+
"lib/util.rb",
|
62
|
+
"spec/git-topic_spec.rb",
|
63
|
+
"spec/spec_helper.rb",
|
64
|
+
"spec/template/origin/HEAD",
|
65
|
+
"spec/template/origin/config",
|
66
|
+
"spec/template/origin/description",
|
67
|
+
"spec/template/origin/hooks/applypatch-msg.sample",
|
68
|
+
"spec/template/origin/hooks/commit-msg.sample",
|
69
|
+
"spec/template/origin/hooks/post-commit.sample",
|
70
|
+
"spec/template/origin/hooks/post-receive.sample",
|
71
|
+
"spec/template/origin/hooks/post-update.sample",
|
72
|
+
"spec/template/origin/hooks/pre-applypatch.sample",
|
73
|
+
"spec/template/origin/hooks/pre-commit.sample",
|
74
|
+
"spec/template/origin/hooks/pre-rebase.sample",
|
75
|
+
"spec/template/origin/hooks/prepare-commit-msg.sample",
|
76
|
+
"spec/template/origin/hooks/update.sample",
|
77
|
+
"spec/template/origin/info/exclude",
|
78
|
+
"spec/template/origin/objects/0a/da6d051b94cd0df50f5a0b7229aec26f0d2cdf",
|
79
|
+
"spec/template/origin/objects/0c/e06c616769768f09f5e629cfcc68eabe3dee81",
|
80
|
+
"spec/template/origin/objects/20/049991cdafdce826f5a3c01e10ffa84d6997ec",
|
81
|
+
"spec/template/origin/objects/33/1d827fd47fb234af54e3a4bbf8c6705e9116cc",
|
82
|
+
"spec/template/origin/objects/41/51899b742fd6b1c873b177b9d13451682089bc",
|
83
|
+
"spec/template/origin/objects/44/ffd9c9c8b52b201659e3ad318cdad6ec836b46",
|
84
|
+
"spec/template/origin/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904",
|
85
|
+
"spec/template/origin/objects/55/eeb01bdf874d1a35870bcf24a970c475c63344",
|
86
|
+
"spec/template/origin/objects/8d/09f9b8d80ce282218125cb0cbf53cccf022203",
|
87
|
+
"spec/template/origin/objects/b4/8e68d5cac189af36abe48e893d11c24b7b2a19",
|
88
|
+
"spec/template/origin/objects/c0/838ed2ee8f2e83c8bda859fc5e332b92f0a5a3",
|
89
|
+
"spec/template/origin/objects/cd/f7b9dbc4911a0d1404db54cde2ed448f6a6afd",
|
90
|
+
"spec/template/origin/objects/d2/6b33daea1ed9823a189992bba38fbc913483c1",
|
91
|
+
"spec/template/origin/objects/fe/4e254557e19f338f40ccfdc00a7517771db880",
|
92
|
+
"spec/template/origin/refs/heads/master",
|
93
|
+
"spec/template/origin/refs/heads/rejected/davidjh/krakens",
|
94
|
+
"spec/template/origin/refs/heads/review/davidjh/pirates",
|
95
|
+
"spec/template/origin/refs/heads/review/user24601/ninja-basic",
|
96
|
+
"spec/template/origin/refs/heads/review/user24601/zombie-basic"
|
97
|
+
]
|
98
|
+
s.homepage = %q{http://github.com/hjdivad/git-topic}
|
99
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
100
|
+
s.require_paths = ["lib"]
|
101
|
+
s.rubygems_version = %q{1.3.7}
|
102
|
+
s.summary = %q{git command around reviewed topic branches}
|
103
|
+
s.test_files = [
|
104
|
+
"spec/spec_helper.rb",
|
105
|
+
"spec/git-topic_spec.rb"
|
106
|
+
]
|
107
|
+
|
108
|
+
if s.respond_to? :specification_version then
|
109
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
110
|
+
s.specification_version = 3
|
111
|
+
|
112
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
113
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
114
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
115
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
116
|
+
else
|
117
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
118
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
119
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
120
|
+
end
|
121
|
+
else
|
122
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
123
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
124
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
data/lib/git-topic.rb
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext/hash/keys'
|
6
|
+
|
7
|
+
require 'util'
|
8
|
+
|
9
|
+
|
10
|
+
module GitTopic
|
11
|
+
GlobalOptKeys = [ :verbose, :help, :verbose_given ]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Switch to a branch for the given topic.
|
16
|
+
def work_on( topic, opts={} )
|
17
|
+
raise "Topic must be specified" if topic.nil?
|
18
|
+
|
19
|
+
# setup a remote branch, if necessary
|
20
|
+
wb = wip_branch( topic )
|
21
|
+
git(
|
22
|
+
"push origin HEAD:#{wb}"
|
23
|
+
) unless remote_branches.include? "origin/#{wb}"
|
24
|
+
# switch to the new branch
|
25
|
+
git [ switch_to_branch( wb, "origin/#{wb}" )]
|
26
|
+
|
27
|
+
# Check for rejected branch
|
28
|
+
rej_branch = rejected_branch( topic )
|
29
|
+
if remote_branches.include? "origin/#{rej_branch}"
|
30
|
+
git [
|
31
|
+
"reset --hard origin/#{rej_branch}",
|
32
|
+
"push origin :#{rej_branch} HEAD:#{wb}",
|
33
|
+
]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Done with the given topic. If none is specified, then topic is assumed to
|
38
|
+
# be the current branch (if it's a topic branch).
|
39
|
+
def done( topic=nil, opts={} )
|
40
|
+
raise(
|
41
|
+
"Branch must be a topic branch"
|
42
|
+
) unless current_branch =~ %r{^wip/}
|
43
|
+
raise(
|
44
|
+
"Working tree must be clean"
|
45
|
+
) unless working_tree_clean?
|
46
|
+
|
47
|
+
topic = current_topic if topic.nil?
|
48
|
+
|
49
|
+
wb = wip_branch( topic )
|
50
|
+
rb = review_branch( topic )
|
51
|
+
git [
|
52
|
+
"push origin #{wb}:#{rb} :#{wb}",
|
53
|
+
"checkout master",
|
54
|
+
"branch -D #{wip_branch( topic )}"
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Produce status like
|
59
|
+
#
|
60
|
+
# # There are 2 topics you can review.
|
61
|
+
# #
|
62
|
+
# # from davidjh:
|
63
|
+
# # zombies
|
64
|
+
# # pirates
|
65
|
+
# # from king-julian:
|
66
|
+
# # fish
|
67
|
+
# # whales
|
68
|
+
# #
|
69
|
+
# # 2 of your topics were rejected.
|
70
|
+
# # dragons
|
71
|
+
# # liches
|
72
|
+
def status( opts={} )
|
73
|
+
opts.assert_valid_keys :prepended, :prepended_given, *GlobalOptKeys
|
74
|
+
|
75
|
+
sb = ''
|
76
|
+
rb = remote_branches_organized
|
77
|
+
review_ut = rb[:review]
|
78
|
+
rejected_ut = rb[:rejected]
|
79
|
+
|
80
|
+
unless review_ut.empty?
|
81
|
+
prep = review_ut.size == 1 ? "is 1" : "are #{review_ut.size}"
|
82
|
+
sb << "# There #{prep} #{'topic'.pluralize( review_ut.size )} you can review.\n\n"
|
83
|
+
|
84
|
+
sb << review_ut.map do |user, topics|
|
85
|
+
sb2 = " from #{user}:\n"
|
86
|
+
sb2 << topics.map{|t| " #{t}"}.join( "\n" )
|
87
|
+
sb2
|
88
|
+
end.join( "\n" )
|
89
|
+
end
|
90
|
+
|
91
|
+
rejected_topics = rejected_ut[ user ] || []
|
92
|
+
unless rejected_topics.empty?
|
93
|
+
sb << "\n" unless review_ut.empty?
|
94
|
+
verb = rejected_topics.size == 1 ? 'is' : 'are'
|
95
|
+
sb << "\n#{rejected_topics.size} of your topics #{verb} rejected.\n "
|
96
|
+
sb << rejected_topics.join( "\n " )
|
97
|
+
end
|
98
|
+
|
99
|
+
sb.gsub! "\n", "\n# "
|
100
|
+
sb << "\n" unless sb.empty?
|
101
|
+
print sb
|
102
|
+
|
103
|
+
if opts[ :prepended ]
|
104
|
+
print "#\n" unless sb.empty?
|
105
|
+
git "status", :show => true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Switch to a review branch to check somebody else's code.
|
110
|
+
def review( spec=nil, opts={} )
|
111
|
+
rb = remote_branches_organized
|
112
|
+
review_branches = rb[:review]
|
113
|
+
|
114
|
+
if spec.nil?
|
115
|
+
# select the oldest (by HEAD) topic, if any exist
|
116
|
+
if review_branches.empty?
|
117
|
+
puts "nothing to review."
|
118
|
+
return
|
119
|
+
end
|
120
|
+
|
121
|
+
user, topic = oldest_review_user_topic
|
122
|
+
else
|
123
|
+
user, topic = spec.split( '/' )
|
124
|
+
end
|
125
|
+
|
126
|
+
if remote_topic_branch = find_remote_review_branch( topic )
|
127
|
+
git [
|
128
|
+
switch_to_branch(
|
129
|
+
review_branch( topic, user ),
|
130
|
+
remote_topic_branch )]
|
131
|
+
else
|
132
|
+
raise "No review topic found matching ‘#{spec}’"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Accept the branch currently being reviewed.
|
137
|
+
def accept( topic=nil, opts={} )
|
138
|
+
raise "Must be on a review branch." unless on_review_branch?
|
139
|
+
|
140
|
+
# switch to master
|
141
|
+
# merge review branch, assuming FF
|
142
|
+
# push master, destroy remote
|
143
|
+
# destroy local
|
144
|
+
user, topic = user_topic_name( current_branch )
|
145
|
+
|
146
|
+
local_review_branch = current_branch
|
147
|
+
ff_merge = git [
|
148
|
+
"checkout master",
|
149
|
+
"merge --ff-only #{local_review_branch}",
|
150
|
+
]
|
151
|
+
|
152
|
+
unless ff_merge
|
153
|
+
git "checkout #{local_review_branch}"
|
154
|
+
raise "
|
155
|
+
review branch is not up to date: merge not a fast-forward. Either
|
156
|
+
rebase or reject this branch.
|
157
|
+
".cleanup
|
158
|
+
end
|
159
|
+
|
160
|
+
rem_review_branch = find_remote_review_branch( topic ).gsub( %r{^origin/}, '' )
|
161
|
+
git [
|
162
|
+
"push origin :#{rem_review_branch}",
|
163
|
+
"branch -d #{local_review_branch}"
|
164
|
+
]
|
165
|
+
end
|
166
|
+
|
167
|
+
# Reject the branch currently being reviewed.
|
168
|
+
def reject( topic=nil, opts={} )
|
169
|
+
raise "Must be on a review branch." unless on_review_branch?
|
170
|
+
|
171
|
+
# switch to master
|
172
|
+
# push to rejected, destroy remote
|
173
|
+
# destroy local
|
174
|
+
user, topic = user_topic_name( current_branch )
|
175
|
+
|
176
|
+
rem_review_branch = find_remote_review_branch( topic ).gsub( %r{^origin/}, '' )
|
177
|
+
rem_rej_branch = remote_rejected_branch( topic, user )
|
178
|
+
git [
|
179
|
+
"checkout master",
|
180
|
+
"push origin #{current_branch}:#{rem_rej_branch} :#{rem_review_branch}",
|
181
|
+
"branch -D #{current_branch}"
|
182
|
+
]
|
183
|
+
end
|
184
|
+
|
185
|
+
def install_aliases( opts={} )
|
186
|
+
opts.assert_valid_keys :local, :local_given, *GlobalOptKeys
|
187
|
+
|
188
|
+
flags = "--global" unless opts[:local]
|
189
|
+
|
190
|
+
git [
|
191
|
+
"config #{flags} alias.work-on 'topic work-on'",
|
192
|
+
"config #{flags} alias.done 'topic done'",
|
193
|
+
"config #{flags} alias.review 'topic review'",
|
194
|
+
"config #{flags} alias.accept 'topic accept'",
|
195
|
+
"config #{flags} alias.reject 'topic reject'",
|
196
|
+
|
197
|
+
"config #{flags} alias.w 'topic work-on'",
|
198
|
+
"config #{flags} alias.r 'topic review'",
|
199
|
+
"config #{flags} alias.st 'topic status --prepended'",
|
200
|
+
]
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
|
207
|
+
def backup_branch( topic )
|
208
|
+
"backup/#{user}/#{topic}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def wip_branch( topic )
|
212
|
+
"wip/#{user}/#{topic}"
|
213
|
+
end
|
214
|
+
|
215
|
+
def rejected_branch( topic )
|
216
|
+
"rejected/#{user}/#{topic}"
|
217
|
+
end
|
218
|
+
|
219
|
+
def review_branch( topic, user=user )
|
220
|
+
"review/#{user}/#{topic}"
|
221
|
+
end
|
222
|
+
|
223
|
+
def remote_rejected_branch( topic, user=user )
|
224
|
+
"rejected/#{user}/#{topic}"
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
def find_remote_review_branch( topic )
|
229
|
+
others_review_branches.find{|b| b.index topic}
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
def user_topic_name( branch )
|
234
|
+
if branch =~ %r{^origin}
|
235
|
+
branch =~ %r{^\S*?/\S*?/(\S*?)/(\S*)}
|
236
|
+
[$1, $2]
|
237
|
+
else
|
238
|
+
branch =~ %r{^\S*?/(\S*?)/(\S*)}
|
239
|
+
[$1, $2]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def user
|
245
|
+
@@user ||= (ENV['USER'] || `whoami`)
|
246
|
+
end
|
247
|
+
|
248
|
+
def current_topic
|
249
|
+
current_branch =~ %r{wip/\S*?/(\S*)}
|
250
|
+
$1
|
251
|
+
end
|
252
|
+
|
253
|
+
def current_branch
|
254
|
+
@@current_branch ||= capture_git( "branch --no-color" ).split( "\n" ).find do |b|
|
255
|
+
b =~ %r{^\*}
|
256
|
+
end[ 2..-1 ]
|
257
|
+
end
|
258
|
+
|
259
|
+
def branches
|
260
|
+
@@branches ||= capture_git( "branch --no-color" ).split( "\n" ).map{|b| b[2..-1]}
|
261
|
+
end
|
262
|
+
|
263
|
+
def remote_branches
|
264
|
+
@@remote_branches ||= capture_git( "branch -r --no-color" ).split( "\n" ).map{|b| b[2..-1]}
|
265
|
+
end
|
266
|
+
|
267
|
+
def others_review_branches
|
268
|
+
remote_branches.select do
|
269
|
+
|b| b =~ %r{/review/}
|
270
|
+
end.reject do |b|
|
271
|
+
b =~ %r{/#{user}/}
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def remote_branches_organized
|
276
|
+
@@remote_branches_organized ||= (
|
277
|
+
rb = remote_branches.dup
|
278
|
+
# Convert a bunch of remote branch names, like
|
279
|
+
# origin/HEAD -> origin/masterr
|
280
|
+
# origin/master
|
281
|
+
# origin/review/user1/topic1
|
282
|
+
# origin/something-else
|
283
|
+
# origin/rejected/user2/topic2
|
284
|
+
#
|
285
|
+
# Into a hash with keys 'review' and 'rejected' pointing to hashes of
|
286
|
+
# user-topic(s) pairs.
|
287
|
+
rb.map!{|s| s.gsub( /->.*/, '')}
|
288
|
+
rb.map!{|s| s.strip.split( '/' )}
|
289
|
+
namespace_ut = rb.group_by{|remote, namespace, user, topic| namespace if topic}
|
290
|
+
namespace_ut.reject!{|k,v| not %w(rejected review).include? k}
|
291
|
+
|
292
|
+
namespace_ut.each do |k,v|
|
293
|
+
v.each{|a| a.shift( 2 )}
|
294
|
+
v = namespace_ut[k] = v.group_by{|user, topic| user if topic}
|
295
|
+
v.each{|kk,vv| vv.each(&:shift); vv.flatten!}
|
296
|
+
end
|
297
|
+
|
298
|
+
namespace_ut.symbolize_keys!
|
299
|
+
namespace_ut[:review] ||= {}
|
300
|
+
namespace_ut[:rejected] ||= {}
|
301
|
+
|
302
|
+
namespace_ut[:review].reject!{|k,v| k == user}
|
303
|
+
namespace_ut
|
304
|
+
)
|
305
|
+
end
|
306
|
+
|
307
|
+
def oldest_review_branch
|
308
|
+
return nil if others_review_branches.empty?
|
309
|
+
|
310
|
+
commits_by_age = capture_git([
|
311
|
+
"log --date-order --reverse --pretty=format:%d",
|
312
|
+
"^origin/master #{others_review_branches.join( ' ' )}",
|
313
|
+
].join( " " )).split( "\n" )
|
314
|
+
|
315
|
+
commits_by_age.find do |ref|
|
316
|
+
# no ‘,’, i.e. only one ref matches the commit
|
317
|
+
ref.index( ',' ).nil?
|
318
|
+
end.strip[ 1..-2 ] # chomp the leading and trailing parenthesis
|
319
|
+
end
|
320
|
+
|
321
|
+
def oldest_review_user_topic
|
322
|
+
user_topic_name( oldest_review_branch )
|
323
|
+
end
|
324
|
+
|
325
|
+
def on_review_branch?
|
326
|
+
current_branch =~ %r{^review/}
|
327
|
+
end
|
328
|
+
|
329
|
+
def working_tree_clean?
|
330
|
+
git [ "diff --quiet", "diff --quiet --cached" ]
|
331
|
+
$?.success?
|
332
|
+
end
|
333
|
+
|
334
|
+
def working_tree_dirty?
|
335
|
+
not working_tree_clean?
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
def display_git_output?
|
340
|
+
@@display_git_output ||= false
|
341
|
+
end
|
342
|
+
|
343
|
+
def display_git_output!
|
344
|
+
@@display_git_output = true
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
def switch_to_branch( branch, tracking=nil )
|
349
|
+
if branches.include?( branch )
|
350
|
+
"checkout #{branch}"
|
351
|
+
else
|
352
|
+
"checkout -b #{branch} #{tracking}"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def cmd_redirect_suffix( opts )
|
357
|
+
if !opts[:show] && !display_git_output?
|
358
|
+
"> /dev/null 2> /dev/null"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def git( cmds=[], opts={} )
|
363
|
+
cmds = [cmds] if cmds.is_a? String
|
364
|
+
redir = cmd_redirect_suffix( opts )
|
365
|
+
system cmds.map{|c| "git #{c} #{redir}"}.join( " && " )
|
366
|
+
end
|
367
|
+
|
368
|
+
def capture_git( cmds=[] )
|
369
|
+
cmds = [cmds] if cmds.is_a? String
|
370
|
+
redir = "2> /dev/null" unless display_git_output?
|
371
|
+
`#{cmds.map{|c| "git #{c} #{redir}"}.join( " && " )}`
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
data/lib/util.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'active_support/core_ext/module/aliasing'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
7
|
+
|
8
|
+
|
9
|
+
class String
|
10
|
+
def cleanup
|
11
|
+
indent = (index /^([ \t]+)/; $1) || ''
|
12
|
+
regex = /^#{Regexp::escape( indent )}/
|
13
|
+
strip.gsub regex, ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def oneline
|
17
|
+
strip.gsub( /\n\s+/, '' )
|
18
|
+
end
|
19
|
+
|
20
|
+
# Annoyingly, the useful version of pluralize in texthelpers isn't in the
|
21
|
+
# string core extensions.
|
22
|
+
def pluralize_with_count( count )
|
23
|
+
count > 1 ? pluralize_without_count : singularize
|
24
|
+
end
|
25
|
+
alias_method_chain :pluralize, :count
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class Object
|
30
|
+
# Deep duplicate via remarshaling. Not always applicable.
|
31
|
+
def ddup
|
32
|
+
Marshal.load( Marshal.dump( self ))
|
33
|
+
end
|
34
|
+
end
|