right_develop 2.0.2 → 2.1.0
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.
- data/VERSION +1 -1
- data/lib/right_develop/commands/git.rb +171 -30
- data/lib/right_develop/net.rb +1 -1
- data/lib/right_develop/utility/git.rb +0 -1
- data/lib/right_develop/utility/shell.rb +2 -2
- data/right_develop.gemspec +5 -5
- metadata +8 -6
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0
|
1
|
+
2.1.0
|
@@ -20,17 +20,24 @@
|
|
20
20
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
|
23
|
+
require 'set'
|
24
|
+
require 'uri'
|
25
|
+
|
23
26
|
require 'right_git'
|
27
|
+
require 'right_support'
|
28
|
+
|
24
29
|
require 'right_develop'
|
25
|
-
require "action_view"
|
26
30
|
|
27
31
|
module RightDevelop::Commands
|
28
32
|
class Git
|
33
|
+
include RightSupport::Log::Mixin
|
34
|
+
|
29
35
|
NAME_SPLIT_CHARS = /-|_|\//
|
30
36
|
YES = /(ye?s?)/i
|
31
37
|
NO = /(no?)/i
|
32
|
-
|
33
|
-
|
38
|
+
TASKS = %w(prune tickets)
|
39
|
+
MERGE_COMMENT = /^Merge (?:remote[- ])?(?:tracking )?(?:branch|pull request #[0-9]+ from) ['"]?(.*)['"]?$/i
|
40
|
+
WORD_BOUNDARY = %r{[_ /-]+}
|
34
41
|
|
35
42
|
# Parse command-line options and create a Command object
|
36
43
|
def self.create
|
@@ -48,62 +55,85 @@ Where <task> is one of:
|
|
48
55
|
#{task_list}
|
49
56
|
|
50
57
|
And [options] are selected from:
|
51
|
-
EOS
|
58
|
+
EOS
|
52
59
|
opt :age, "Minimum age to consider",
|
53
|
-
|
54
|
-
opt :only, "Limit to branches matching this prefix",
|
55
|
-
|
60
|
+
:default => "3.months"
|
61
|
+
opt :only, "Limit to branches matching this prefix",
|
62
|
+
:type => :string
|
56
63
|
opt :except, "Ignore branches matching this prefix",
|
57
|
-
|
58
|
-
|
64
|
+
:type => :string,
|
65
|
+
:default => "(release|ve?r?)?[0-9.]+"
|
59
66
|
opt :local, "Limit to local branches"
|
60
67
|
opt :remote, "Limit to remote branches"
|
61
|
-
opt :merged, "Limit to branches that are
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
opt :merged, "Limit to branches that are merged into this branch",
|
69
|
+
:type => :string,
|
70
|
+
:default => "master"
|
71
|
+
opt :since, "Base branch or tag to compare against for determining 'new' commits",
|
72
|
+
:default => "origin/master"
|
73
|
+
opt :link, "Word prefix indicating a link to an external ticketing system",
|
74
|
+
:default => "(?:[#A-Za-z]+)([0-9]+)$"
|
75
|
+
opt :link_to, "URL pattern to generate ticket links, don't forget trailing slash!",
|
76
|
+
:type => :string
|
77
|
+
opt :debug, "Enable verbose debug output",
|
78
|
+
:default => false
|
65
79
|
end
|
66
80
|
|
67
81
|
task = ARGV.shift
|
68
82
|
|
83
|
+
repo = ::RightGit::Git::Repository.new(
|
84
|
+
::Dir.pwd,
|
85
|
+
::RightDevelop::Utility::Git::DEFAULT_REPO_OPTIONS)
|
86
|
+
|
69
87
|
case task
|
70
88
|
when "prune"
|
71
|
-
repo = ::RightGit::Git::Repository.new(
|
72
|
-
::Dir.pwd,
|
73
|
-
::RightDevelop::Utility::Git::DEFAULT_REPO_OPTIONS)
|
74
89
|
self.new(repo, :prune, options)
|
90
|
+
when "tickets"
|
91
|
+
self.new(repo, :tickets, options)
|
75
92
|
else
|
76
93
|
Trollop.die "unknown task #{task}"
|
77
94
|
end
|
78
95
|
end
|
79
96
|
|
97
|
+
# @param [RightGit::Git::Repository] repo the Git repository to operate on
|
98
|
+
# @param [Symbol] task one of :prune or :tickets
|
80
99
|
# @option options [String] :age Ignore branches newer than this time-ago-in-words e.g. "3 months"; default unit is months
|
81
100
|
# @option options [String] :except Ignore branches matching this regular expression
|
82
101
|
# @option options [String] :only Consider only branches matching this regular expression
|
83
|
-
# @option options [
|
84
|
-
# @option options [
|
102
|
+
# @option options [Boolean] :local Consider only local branches
|
103
|
+
# @option options [Boolean] :remote Consider only remote branches
|
85
104
|
# @option options [String] :merged Consider only branches that are fully merged into this branch (e.g. master)
|
105
|
+
# @option options [String] :since the name of a "base branch" representing the previous release
|
106
|
+
# @option options [String] :link word prefix connoting a link to an external ticketing system
|
107
|
+
# @option options [String] :link_to URL prefix to use when generating ticket links
|
86
108
|
def initialize(repo, task, options)
|
109
|
+
logger = Logger.new(STDERR)
|
110
|
+
logger.level = options[:debug] ? Logger::DEBUG : Logger::WARN
|
111
|
+
RightSupport::Log::Mixin.default_logger = logger
|
112
|
+
|
87
113
|
# Post-process "age" option; transform from natural-language expression into a timestamp.
|
88
114
|
if (age = options.delete(:age))
|
89
|
-
|
90
|
-
debugger
|
91
|
-
age = parse_age(age)
|
115
|
+
age = parse_age(age)
|
92
116
|
options[:age] = age
|
93
117
|
end
|
94
118
|
|
95
119
|
# Post-process "except" option; transform into a Regexp.
|
96
120
|
if (except = options.delete(:except))
|
97
|
-
except
|
121
|
+
except = Regexp.new("^(origin/)?(#{except})")
|
98
122
|
options[:except] = except
|
99
123
|
end
|
100
124
|
|
101
125
|
# Post-process "only" option; transform into a Regexp.
|
102
126
|
if (only = options.delete(:only))
|
103
|
-
only
|
127
|
+
only = Regexp.new("^(origin/)?(#{only})")
|
104
128
|
options[:only] = only
|
105
129
|
end
|
106
130
|
|
131
|
+
# Post-process "since" option; transform into a Regexp.
|
132
|
+
if (link = options.delete(:link))
|
133
|
+
link = Regexp.new("^#{link}")
|
134
|
+
options[:link] = link
|
135
|
+
end
|
136
|
+
|
107
137
|
@git = repo
|
108
138
|
@task = task
|
109
139
|
@options = options
|
@@ -115,6 +145,8 @@ EOS
|
|
115
145
|
case @task
|
116
146
|
when :prune
|
117
147
|
prune(@options)
|
148
|
+
when :tickets
|
149
|
+
tickets(@options)
|
118
150
|
else
|
119
151
|
raise StateError, "Invalid @task; check Git.create!"
|
120
152
|
end
|
@@ -127,11 +159,16 @@ EOS
|
|
127
159
|
# @option options [Time] :age Ignore branches whose HEAD commit is newer than this timestamp
|
128
160
|
# @option options [Regexp] :except Ignore branches matching this pattern
|
129
161
|
# @option options [Regexp] :only Consider only branches matching this pattern
|
130
|
-
# @option options [
|
131
|
-
# @option options [
|
162
|
+
# @option options [Boolean] :local Consider only local branches
|
163
|
+
# @option options [Boolean] :remote Consider only remote branches
|
132
164
|
# @option options [String] :merged Consider only branches that are fully merged into this branch (e.g. master)
|
133
165
|
def prune(options={})
|
134
|
-
|
166
|
+
puts describe_prune(options)
|
167
|
+
|
168
|
+
puts "Fetching latest branches and tags from remotes"
|
169
|
+
@git.fetch_all(:prune => true)
|
170
|
+
|
171
|
+
branches = @git.branches(:all => true)
|
135
172
|
|
136
173
|
#Filter by name prefix
|
137
174
|
branches = branches.select { |x| x =~ options[:only] } if options[:only]
|
@@ -148,12 +185,13 @@ EOS
|
|
148
185
|
|
149
186
|
#Filter by merge status
|
150
187
|
if options[:merged]
|
188
|
+
puts "Checking merge status of #{branches.size} branches; please be patient"
|
151
189
|
branches = branches.merged(options[:merged])
|
152
190
|
end
|
153
191
|
|
154
192
|
old = {}
|
155
193
|
branches.each do |branch|
|
156
|
-
latest
|
194
|
+
latest = @git.log(branch, :tail => 1).first
|
157
195
|
timestamp = latest.timestamp
|
158
196
|
if timestamp < options[:age] &&
|
159
197
|
old[branch] = timestamp
|
@@ -161,10 +199,12 @@ EOS
|
|
161
199
|
end
|
162
200
|
|
163
201
|
if old.empty?
|
164
|
-
STDERR.puts "No branches
|
202
|
+
STDERR.puts "No branches found; try different options"
|
165
203
|
exit -2
|
166
204
|
end
|
167
205
|
|
206
|
+
puts
|
207
|
+
|
168
208
|
all_by_prefix = branches.group_by { |b| b.name.split(NAME_SPLIT_CHARS).first }
|
169
209
|
|
170
210
|
all_by_prefix.each_pair do |prefix, branches|
|
@@ -185,11 +225,97 @@ EOS
|
|
185
225
|
|
186
226
|
old.each do |branch, timestamp|
|
187
227
|
branch.delete
|
228
|
+
puts " deleted #{branch}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Produce a report of all the tickets that have been merged into the named branch. This works
|
233
|
+
# by scanning merge commit comments, recognizing words that look like a ticket reference, and
|
234
|
+
# extracting a matched segment as the ticket ID. The user must specify a matching Regexp using
|
235
|
+
# the :link option.
|
236
|
+
#
|
237
|
+
# @example Match Acunote stories
|
238
|
+
# git.tickets(:link=>/acu([0-9]+)/)
|
239
|
+
#
|
240
|
+
# @option options [String] :since the name of a "base branch" representing the previous release
|
241
|
+
# @option options [Regexp] :merged the name of a branch (e.g. master) representing the next release
|
242
|
+
# @option options [Regexp] :link a word prefix that connotes links to an external ticketing system
|
243
|
+
# @option options [Boolean] :local Consider only local branches
|
244
|
+
# @option options [Boolean] :remote Consider only remote branches
|
245
|
+
def tickets(options={})
|
246
|
+
since = options[:since]
|
247
|
+
merged = options[:merged]
|
248
|
+
|
249
|
+
tickets = Set.new
|
250
|
+
link = options[:link]
|
251
|
+
|
252
|
+
@git.log("#{since}..#{merged}", :merges => true).each do |commit|
|
253
|
+
if (match = MERGE_COMMENT.match(commit.comment))
|
254
|
+
words = match[1].split(WORD_BOUNDARY)
|
255
|
+
else
|
256
|
+
words = commit.comment.split(WORD_BOUNDARY)
|
257
|
+
end
|
258
|
+
|
259
|
+
got = words.detect do |w|
|
260
|
+
if match = link.match(w)
|
261
|
+
if match[1]
|
262
|
+
tickets << match[1]
|
263
|
+
else
|
264
|
+
raise ArgumentError, "Regexp '#{link}' lacks capture groups; please use a () somewhere"
|
265
|
+
end
|
266
|
+
else
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
end
|
270
|
+
unless got
|
271
|
+
logger.warn "Couldn't infer a ticket link from '#{commit.comment}'"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
if (link_to = options[:link_to])
|
276
|
+
link_to = link_to + '/' unless link_to =~ %r{/$}
|
277
|
+
tickets.each { |t| puts link_to + t }
|
278
|
+
else
|
279
|
+
tickets.each { |t| puts t }
|
188
280
|
end
|
189
281
|
end
|
190
282
|
|
191
283
|
private
|
192
284
|
|
285
|
+
# Build a plain-English description of a prune command based on the
|
286
|
+
# options given.
|
287
|
+
# @param [Hash] options
|
288
|
+
def describe_prune(options)
|
289
|
+
statement = ['Pruning']
|
290
|
+
|
291
|
+
if options[:remote]
|
292
|
+
statement << 'remote'
|
293
|
+
elsif options[:local]
|
294
|
+
statement << 'local'
|
295
|
+
end
|
296
|
+
|
297
|
+
statement << 'branches'
|
298
|
+
|
299
|
+
if options[:age]
|
300
|
+
statement << "older than #{time_ago_in_words(options[:age])}"
|
301
|
+
end
|
302
|
+
|
303
|
+
if options[:merged]
|
304
|
+
statement << "that are fully merged into #{options[:merged]}"
|
305
|
+
end
|
306
|
+
|
307
|
+
if options[:only]
|
308
|
+
naming = "with a name containing '#{options[:only]}'"
|
309
|
+
if options[:except]
|
310
|
+
naming << " (but not '#{options[:except]}')"
|
311
|
+
end
|
312
|
+
statement << naming
|
313
|
+
end
|
314
|
+
|
315
|
+
statement.join(' ')
|
316
|
+
end
|
317
|
+
|
318
|
+
# Ask the user a yes-or-no question
|
193
319
|
def prompt(p, yes_no=false)
|
194
320
|
puts #newline for newline's sake!
|
195
321
|
|
@@ -217,6 +343,16 @@ EOS
|
|
217
343
|
[1, 'second'],
|
218
344
|
]
|
219
345
|
|
346
|
+
# Workalike for ActiveSupport date-helper method. Given a Time in the past, return
|
347
|
+
# a natural-language English string that describes the duration separating that time from
|
348
|
+
# the present. The duration is very approximate, and will be rounded down to the nearest
|
349
|
+
# appropriate interval (e.g. 2.5 hours becomes 2 hours).
|
350
|
+
#
|
351
|
+
# @example about three days ago
|
352
|
+
# time_ago_in_words(Time.now - 86400*3.1) # => "3 days"
|
353
|
+
#
|
354
|
+
# @param [Time] once_upon_a the long-ago time to compare to Time.now
|
355
|
+
# @return [String] an English time duration
|
220
356
|
def time_ago_in_words(once_upon_a)
|
221
357
|
dt = Time.now.to_f - once_upon_a.to_f
|
222
358
|
|
@@ -225,7 +361,7 @@ EOS
|
|
225
361
|
TIME_INTERVALS.each do |pair|
|
226
362
|
mag, term = pair.first, pair.last
|
227
363
|
if dt >= mag
|
228
|
-
units = dt / mag
|
364
|
+
units = Integer(dt / mag)
|
229
365
|
words = "%d %s%s" % [units, term, units > 1 ? 's' : '']
|
230
366
|
break
|
231
367
|
end
|
@@ -238,9 +374,14 @@ EOS
|
|
238
374
|
end
|
239
375
|
end
|
240
376
|
|
377
|
+
# Given a natural-language English description of a time duration, return a Time in the past,
|
378
|
+
# that is the same duration from Time.now that is expressed in the string.
|
379
|
+
#
|
380
|
+
# @param [String] str an English time duration
|
381
|
+
# @return [Time] a Time object in the past, as described relative to now by str
|
241
382
|
def parse_age(str)
|
242
383
|
ord, word = str.split(/[. ]+/, 2)
|
243
|
-
ord
|
384
|
+
ord = Integer(ord)
|
244
385
|
word.gsub!(/s$/, '')
|
245
386
|
|
246
387
|
ago = nil
|
data/lib/right_develop/net.rb
CHANGED
@@ -75,9 +75,9 @@ module RightDevelop
|
|
75
75
|
NullLoggerSingleton.instance
|
76
76
|
end
|
77
77
|
|
78
|
-
# @return [Logger]
|
78
|
+
# @return [Logger] RightSupport::Log::Mixin.default_logger
|
79
79
|
def default_logger
|
80
|
-
|
80
|
+
RightSupport::Log::Mixin.default_logger
|
81
81
|
end
|
82
82
|
|
83
83
|
# Overrides ::RightGit::Shell::Default#execute
|
data/right_develop.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{right_develop}
|
8
|
-
s.version = "2.0
|
8
|
+
s.version = "2.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tony Spataro"]
|
12
|
-
s.date = %q{2014-01-
|
12
|
+
s.date = %q{2014-01-16}
|
13
13
|
s.default_executable = %q{right_develop}
|
14
14
|
s.description = %q{A toolkit of development tools created by RightScale.}
|
15
15
|
s.email = %q{support@rightscale.com}
|
@@ -66,7 +66,7 @@ Gem::Specification.new do |s|
|
|
66
66
|
s.add_runtime_dependency(%q<rspec>, ["< 3.0", ">= 1.3"])
|
67
67
|
s.add_runtime_dependency(%q<cucumber>, ["< 1.3.3", "~> 1.0"])
|
68
68
|
s.add_runtime_dependency(%q<trollop>, ["< 3.0", ">= 1.0"])
|
69
|
-
s.add_runtime_dependency(%q<right_git>, ["
|
69
|
+
s.add_runtime_dependency(%q<right_git>, ["~> 0.1.0"])
|
70
70
|
s.add_runtime_dependency(%q<right_aws>, [">= 2.1.0"])
|
71
71
|
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
72
72
|
s.add_development_dependency(%q<rdoc>, [">= 2.4.2"])
|
@@ -77,7 +77,7 @@ Gem::Specification.new do |s|
|
|
77
77
|
s.add_dependency(%q<rspec>, ["< 3.0", ">= 1.3"])
|
78
78
|
s.add_dependency(%q<cucumber>, ["< 1.3.3", "~> 1.0"])
|
79
79
|
s.add_dependency(%q<trollop>, ["< 3.0", ">= 1.0"])
|
80
|
-
s.add_dependency(%q<right_git>, ["
|
80
|
+
s.add_dependency(%q<right_git>, ["~> 0.1.0"])
|
81
81
|
s.add_dependency(%q<right_aws>, [">= 2.1.0"])
|
82
82
|
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
83
83
|
s.add_dependency(%q<rdoc>, [">= 2.4.2"])
|
@@ -89,7 +89,7 @@ Gem::Specification.new do |s|
|
|
89
89
|
s.add_dependency(%q<rspec>, ["< 3.0", ">= 1.3"])
|
90
90
|
s.add_dependency(%q<cucumber>, ["< 1.3.3", "~> 1.0"])
|
91
91
|
s.add_dependency(%q<trollop>, ["< 3.0", ">= 1.0"])
|
92
|
-
s.add_dependency(%q<right_git>, ["
|
92
|
+
s.add_dependency(%q<right_git>, ["~> 0.1.0"])
|
93
93
|
s.add_dependency(%q<right_aws>, [">= 2.1.0"])
|
94
94
|
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
95
95
|
s.add_dependency(%q<rdoc>, [">= 2.4.2"])
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 2.0.2
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tony Spataro
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2014-01-
|
18
|
+
date: 2014-01-16 00:00:00 -08:00
|
19
19
|
default_executable: right_develop
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -143,12 +143,14 @@ dependencies:
|
|
143
143
|
version_requirements: &id007 !ruby/object:Gem::Requirement
|
144
144
|
none: false
|
145
145
|
requirements:
|
146
|
-
- -
|
146
|
+
- - ~>
|
147
147
|
- !ruby/object:Gem::Version
|
148
|
-
hash:
|
148
|
+
hash: 27
|
149
149
|
segments:
|
150
150
|
- 0
|
151
|
-
|
151
|
+
- 1
|
152
|
+
- 0
|
153
|
+
version: 0.1.0
|
152
154
|
prerelease: false
|
153
155
|
requirement: *id007
|
154
156
|
name: right_git
|