right_develop 1.1.0 → 1.2.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/Gemfile +7 -1
- data/Gemfile.lock +28 -22
- data/README.rdoc +2 -0
- data/VERSION +1 -1
- data/bin/right_develop +41 -0
- data/lib/right_develop.rb +4 -2
- data/lib/right_develop/ci/java_spec_formatter.rb +1 -1
- data/lib/right_develop/commands.rb +6 -0
- data/lib/right_develop/commands/git.rb +190 -0
- data/lib/right_develop/git.rb +22 -0
- data/lib/right_develop/git/branch.rb +74 -0
- data/lib/right_develop/git/branch_collection.rb +72 -0
- data/lib/right_develop/git/commit.rb +30 -0
- data/lib/right_develop/git/repository.rb +57 -0
- data/right_develop.gemspec +171 -8
- metadata +920 -102
data/Gemfile
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
3
|
# Runtime dependencies of RightDevelop
|
4
|
+
|
5
|
+
# Gems used by the CI harness
|
4
6
|
gem "rake", [">= 0.8.7", "< 0.10"]
|
5
7
|
gem "right_support", "~> 2.0"
|
6
8
|
gem "builder", "~> 3.0"
|
7
9
|
gem "rspec", [">= 1.3", "< 3.0"]
|
8
10
|
gem "cucumber", "~> 1.0"
|
9
11
|
|
12
|
+
# Gems used by the command-line Git tools
|
13
|
+
gem "trollop", [">= 1.0", "< 3.0"]
|
14
|
+
gem "actionpack", [">= 2.3.0", "< 4.0"]
|
15
|
+
|
10
16
|
# Gems used during RightDevelop development that should be called out in the gemspec
|
11
17
|
group :development do
|
12
18
|
gem "jeweler", "~> 1.8.3"
|
data/Gemfile.lock
CHANGED
@@ -1,59 +1,62 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
right_develop (1.
|
4
|
+
right_develop (1.2.0)
|
5
|
+
actionpack (>= 2.3.0, < 4.0)
|
5
6
|
builder (~> 3.0)
|
6
7
|
cucumber (~> 1.0)
|
7
8
|
rake (>= 0.8.7, < 0.10)
|
8
9
|
right_support (~> 2.0)
|
9
10
|
rspec (>= 1.3, < 3.0)
|
11
|
+
trollop (>= 1.0, < 3.0)
|
10
12
|
|
11
13
|
GEM
|
12
|
-
remote:
|
14
|
+
remote: https://rubygems.org/
|
13
15
|
specs:
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
actionpack (2.3.17)
|
17
|
+
activesupport (= 2.3.17)
|
18
|
+
rack (~> 1.1.0)
|
19
|
+
activesupport (2.3.17)
|
17
20
|
archive-tar-minitar (0.5.2)
|
18
|
-
builder (3.
|
21
|
+
builder (3.2.0)
|
19
22
|
columnize (0.3.6)
|
20
|
-
cucumber (1.2.
|
23
|
+
cucumber (1.2.3)
|
21
24
|
builder (>= 2.1.2)
|
22
25
|
diff-lcs (>= 1.1.3)
|
23
|
-
gherkin (~> 2.11.
|
24
|
-
|
25
|
-
diff-lcs (1.1
|
26
|
+
gherkin (~> 2.11.6)
|
27
|
+
multi_json (~> 1.3)
|
28
|
+
diff-lcs (1.2.1)
|
26
29
|
flexmock (0.8.11)
|
27
30
|
gherkin (2.11.6)
|
28
31
|
json (>= 1.7.6)
|
29
32
|
git (1.2.5)
|
30
|
-
i18n (0.6.1)
|
31
33
|
jeweler (1.8.4)
|
32
34
|
bundler (~> 1.0)
|
33
35
|
git (>= 1.2.5)
|
34
36
|
rake
|
35
37
|
rdoc
|
36
|
-
json (1.7.
|
38
|
+
json (1.7.7)
|
37
39
|
libxml-ruby (2.4.0)
|
38
40
|
linecache (0.46)
|
39
41
|
rbx-require-relative (> 0.0.4)
|
40
42
|
linecache19 (0.5.12)
|
41
43
|
ruby_core_source (>= 0.1.4)
|
42
|
-
multi_json (1.
|
44
|
+
multi_json (1.6.1)
|
43
45
|
nokogiri (1.5.6)
|
46
|
+
rack (1.1.6)
|
44
47
|
rake (0.9.6)
|
45
48
|
rbx-require-relative (0.0.9)
|
46
|
-
rdoc (
|
49
|
+
rdoc (4.0.0)
|
47
50
|
json (~> 1.4)
|
48
51
|
right_support (2.6.17)
|
49
|
-
rspec (2.
|
50
|
-
rspec-core (~> 2.
|
51
|
-
rspec-expectations (~> 2.
|
52
|
-
rspec-mocks (~> 2.
|
53
|
-
rspec-core (2.
|
54
|
-
rspec-expectations (2.
|
55
|
-
diff-lcs (
|
56
|
-
rspec-mocks (2.
|
52
|
+
rspec (2.13.0)
|
53
|
+
rspec-core (~> 2.13.0)
|
54
|
+
rspec-expectations (~> 2.13.0)
|
55
|
+
rspec-mocks (~> 2.13.0)
|
56
|
+
rspec-core (2.13.1)
|
57
|
+
rspec-expectations (2.13.0)
|
58
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
59
|
+
rspec-mocks (2.13.0)
|
57
60
|
ruby-debug (0.10.4)
|
58
61
|
columnize (>= 0.1)
|
59
62
|
ruby-debug-base (~> 0.10.4.0)
|
@@ -70,11 +73,13 @@ GEM
|
|
70
73
|
ruby_core_source (0.1.5)
|
71
74
|
archive-tar-minitar (>= 0.5.2)
|
72
75
|
syntax (1.0.0)
|
76
|
+
trollop (2.0)
|
73
77
|
|
74
78
|
PLATFORMS
|
75
79
|
ruby
|
76
80
|
|
77
81
|
DEPENDENCIES
|
82
|
+
actionpack (>= 2.3.0, < 4.0)
|
78
83
|
activesupport
|
79
84
|
builder (~> 3.0)
|
80
85
|
cucumber (~> 1.0)
|
@@ -90,3 +95,4 @@ DEPENDENCIES
|
|
90
95
|
ruby-debug (>= 0.10)
|
91
96
|
ruby-debug19 (>= 0.11.6)
|
92
97
|
syntax (~> 1.0.0)
|
98
|
+
trollop (>= 1.0, < 3.0)
|
data/README.rdoc
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/bin/right_develop
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "trollop"
|
4
|
+
|
5
|
+
require "right_develop"
|
6
|
+
|
7
|
+
gemspec = Gem.latest_spec_for("right_develop")
|
8
|
+
|
9
|
+
commands = {}
|
10
|
+
RightDevelop::Commands.constants.each do |konst|
|
11
|
+
name = konst.to_s.downcase
|
12
|
+
commands[name] = RightDevelop::Commands.const_get(konst.to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Use a Trollop parser for help/banner display, but do not actually parse anything
|
16
|
+
# just yet.
|
17
|
+
command_list = commands.keys.map { |c| " * #{c}" }.join("\n")
|
18
|
+
p = Trollop::Parser.new do
|
19
|
+
version "right_develop #{gemspec.version} (c) 2013- RightScale, Inc."
|
20
|
+
banner <<-EOS
|
21
|
+
A command-line interface to various tools offered by the right_develop gem.
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
right_develop <command> [options]
|
25
|
+
|
26
|
+
Where <command> is one of:
|
27
|
+
#{command_list}
|
28
|
+
|
29
|
+
To get help on a command:
|
30
|
+
right_develop <command> --help
|
31
|
+
EOS
|
32
|
+
|
33
|
+
stop_on commands.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
opts = Trollop::with_standard_exception_handling p do
|
37
|
+
raise Trollop::HelpNeeded if ARGV.empty?
|
38
|
+
p.parse ARGV
|
39
|
+
cmd = ARGV.shift
|
40
|
+
commands[cmd].create.run
|
41
|
+
end
|
data/lib/right_develop.rb
CHANGED
@@ -24,8 +24,10 @@ require 'right_support'
|
|
24
24
|
|
25
25
|
# Autoload everything possible
|
26
26
|
module RightDevelop
|
27
|
-
autoload :CI,
|
28
|
-
autoload :
|
27
|
+
autoload :CI, 'right_develop/ci'
|
28
|
+
autoload :Commands, 'right_develop/commands'
|
29
|
+
autoload :Git, 'right_develop/git'
|
30
|
+
autoload :Parsers, 'right_develop/parsers'
|
29
31
|
end
|
30
32
|
|
31
33
|
# Automatically include RightSupport networking extensions
|
@@ -25,7 +25,7 @@ require 'time'
|
|
25
25
|
require 'builder'
|
26
26
|
|
27
27
|
# Try to load RSpec 2.x - 1.x formatters
|
28
|
-
['rspec/core/formatters', 'spec/runner/formatter/base_text_formatter'].each do |f|
|
28
|
+
['rspec/core', 'spec', 'rspec/core/formatters/base_text_formatter', 'spec/runner/formatter/base_text_formatter'].each do |f|
|
29
29
|
begin
|
30
30
|
require f
|
31
31
|
rescue LoadError
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module RightDevelop::Commands
|
4
|
+
class Git
|
5
|
+
include ActionView::Helpers::DateHelper
|
6
|
+
|
7
|
+
NAME_SPLIT_CHARS = /-|_|\//
|
8
|
+
YES = /(ye?s?)/i
|
9
|
+
NO = /(no?)/i
|
10
|
+
|
11
|
+
TASKS = %w(prune)
|
12
|
+
|
13
|
+
# Parse command-line options and create a Command object
|
14
|
+
def self.create
|
15
|
+
task_list = TASKS.map { |c| " * #{c}" }.join("\n")
|
16
|
+
|
17
|
+
options = Trollop.options do
|
18
|
+
banner <<-EOS
|
19
|
+
The 'git' command automates various repository management tasks. All tasks
|
20
|
+
accept the same options, although not every option applies to every command.
|
21
|
+
|
22
|
+
Usage:
|
23
|
+
right_develop git <task> [options]
|
24
|
+
|
25
|
+
Where <task> is one of:
|
26
|
+
#{task_list}
|
27
|
+
|
28
|
+
And [options] are selected from:
|
29
|
+
EOS
|
30
|
+
opt :age, "Minimum age to consider",
|
31
|
+
:default => "3.months"
|
32
|
+
opt :only, "Limit to branches matching this prefix",
|
33
|
+
:type=>:string
|
34
|
+
opt :except, "Ignore branches matching this prefix",
|
35
|
+
:type=>:string,
|
36
|
+
:default => "^(release|v?[0-9.]+|)"
|
37
|
+
opt :local, "Limit to local branches"
|
38
|
+
opt :remote, "Limit to remote branches"
|
39
|
+
opt :merged, "Limit to branches that are fully merged into the named branch",
|
40
|
+
:type=>:string,
|
41
|
+
:default => "master"
|
42
|
+
stop_on TASKS
|
43
|
+
end
|
44
|
+
|
45
|
+
task = ARGV.shift
|
46
|
+
|
47
|
+
case task
|
48
|
+
when "prune"
|
49
|
+
git = RightDevelop::Git::Repository.new(Dir.pwd)
|
50
|
+
self.new(git, :prune, options)
|
51
|
+
else
|
52
|
+
Trollop.die "unknown task #{task}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @option options [String] :age Ignore branches newer than this time-ago-in-words e.g. "3 months"; default unit is months
|
57
|
+
# @option options [String] :except Ignore branches matching this regular expression
|
58
|
+
# @option options [String] :only Consider only branches matching this regular expression
|
59
|
+
# @option options [true|false] :local Consider local branches
|
60
|
+
# @option options [true|false] :remote Consider remote branches
|
61
|
+
# @option options [String] :merged Consider only branches that are fully merged into this branch (e.g. master)
|
62
|
+
def initialize(repo, task, options)
|
63
|
+
# Post-process "age" option; transform from natural-language expression into a timestamp.
|
64
|
+
if (age = options.delete(:age))
|
65
|
+
age = age.gsub(/\s+/, ".")
|
66
|
+
|
67
|
+
if age =~ /^[0-9]+\.?(hours|days|weeks|months|years)$/
|
68
|
+
age = eval(age).ago
|
69
|
+
elsif age =~ /^[0-9]+$/
|
70
|
+
age = age.to_i.months.ago
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Can't parse age of '#{age}'"
|
73
|
+
end
|
74
|
+
options[:age] = age
|
75
|
+
end
|
76
|
+
|
77
|
+
# Post-process "except" option; transform into a Regexp.
|
78
|
+
if (except = options.delete(:except))
|
79
|
+
except = Regexp.new("^(origin/)?(#{except})")
|
80
|
+
options[:except] = except
|
81
|
+
end
|
82
|
+
|
83
|
+
# Post-process "only" option; transform into a Regexp.
|
84
|
+
if (only = options.delete(:only))
|
85
|
+
only = Regexp.new("^(origin/)?(#{only})")
|
86
|
+
options[:only] = only
|
87
|
+
end
|
88
|
+
|
89
|
+
@git = repo
|
90
|
+
@task = task
|
91
|
+
@options = options
|
92
|
+
end
|
93
|
+
|
94
|
+
# Run the task that was specified when this object was instantiated. This
|
95
|
+
# method does no work; it just delegates to a task method.
|
96
|
+
def run
|
97
|
+
case @task
|
98
|
+
when :prune
|
99
|
+
prune(@options)
|
100
|
+
else
|
101
|
+
raise StateError, "Invalid @task; check Git.create!"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
# Prune dead branches from the repository.
|
108
|
+
#
|
109
|
+
# @option options [Time] :age Ignore branches whose HEAD commit is newer than this timestamp
|
110
|
+
# @option options [Regexp] :except Ignore branches matching this pattern
|
111
|
+
# @option options [Regexp] :only Consider only branches matching this pattern
|
112
|
+
# @option options [true|false] :local Consider local branches
|
113
|
+
# @option options [true|false] :remote Consider remote branches
|
114
|
+
# @option options [String] :merged Consider only branches that are fully merged into this branch (e.g. master)
|
115
|
+
def prune(options={})
|
116
|
+
branches = @git.branches
|
117
|
+
|
118
|
+
#Filter by name prefix
|
119
|
+
branches = branches.select { |x| x =~ options[:only] } if options[:only]
|
120
|
+
branches = branches.reject { |x| x =~ options[:except] } if options[:except]
|
121
|
+
|
122
|
+
#Filter by location (local/remote)
|
123
|
+
if options[:local] && !options[:remote]
|
124
|
+
branches = branches.local
|
125
|
+
elsif options[:remote] && !options[:local]
|
126
|
+
branches = branches.remote
|
127
|
+
elsif options[:remote] && options[:local]
|
128
|
+
raise ArgumentError, "Cannot specify both --local and --remote!"
|
129
|
+
end
|
130
|
+
|
131
|
+
#Filter by merge status
|
132
|
+
if options[:merged]
|
133
|
+
branches = branches.merged(options[:merged])
|
134
|
+
end
|
135
|
+
|
136
|
+
old = {}
|
137
|
+
branches.each do |branch|
|
138
|
+
latest = @git.log(branch, :tail=>1).first
|
139
|
+
timestamp = latest.timestamp
|
140
|
+
if timestamp < options[:age] &&
|
141
|
+
old[branch] = timestamp
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
if old.empty?
|
146
|
+
STDERR.puts "No branches older than #{time_ago_in_words(options[:age])} found; do you need to specify --remote?"
|
147
|
+
exit -2
|
148
|
+
end
|
149
|
+
|
150
|
+
all_by_prefix = branches.group_by { |b| b.name.split(NAME_SPLIT_CHARS).first }
|
151
|
+
|
152
|
+
all_by_prefix.each_pair do |prefix, branches|
|
153
|
+
old_in_group = branches.select { |b| old.key?(b) }
|
154
|
+
next if old_in_group.empty?
|
155
|
+
old_in_group = old_in_group.sort { |a, b| old[a] <=> old[b] }
|
156
|
+
puts prefix
|
157
|
+
puts '-' * prefix.length
|
158
|
+
old_in_group.each do |b|
|
159
|
+
puts "\t" + b.display(40) + "\t" + time_ago_in_words(old[b])
|
160
|
+
end
|
161
|
+
puts
|
162
|
+
end
|
163
|
+
|
164
|
+
unless options[:force]
|
165
|
+
return unless prompt("Delete all #{old.size} branches above?", true)
|
166
|
+
end
|
167
|
+
|
168
|
+
old.each do |branch, timestamp|
|
169
|
+
branch.delete
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def prompt(p, yes_no=false)
|
176
|
+
puts #newline for newline's sake!
|
177
|
+
|
178
|
+
loop do
|
179
|
+
print p, ' '
|
180
|
+
line = STDIN.readline.strip
|
181
|
+
if yes_no
|
182
|
+
return true if line =~ YES
|
183
|
+
return false if line =~ NO
|
184
|
+
else
|
185
|
+
return line
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RightDevelop
|
2
|
+
module Git
|
3
|
+
# A Git command failed unexpectedly.
|
4
|
+
class CommandError < StandardError
|
5
|
+
attr_reader :output
|
6
|
+
|
7
|
+
def initialize(message)
|
8
|
+
@output = message
|
9
|
+
lines = message.split("\n").map { |l| l.strip }.reject { |l| l.empty? }
|
10
|
+
super(lines.last || @output)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# A Git command's output did not match with expected output.
|
15
|
+
class FormatError < StandardError; end
|
16
|
+
end
|
17
|
+
|
18
|
+
require "right_develop/git/branch"
|
19
|
+
require "right_develop/git/branch_collection"
|
20
|
+
require "right_develop/git/commit"
|
21
|
+
require "right_develop/git/repository"
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RightDevelop::Git
|
2
|
+
# A branch in a Git repository. Has some proxy methods that make it act a bit like
|
3
|
+
# a string, whose value is the name of the branch. This allows branches to be sorted,
|
4
|
+
# matched against Regexp, and certain other string-y operations.
|
5
|
+
class Branch
|
6
|
+
BRANCH_NAME = '[#A-Za-z0-9._\/-]+'
|
7
|
+
BRANCH_INFO = /(\* | )?(#{BRANCH_NAME})( -> )?(#{BRANCH_NAME})?/
|
8
|
+
BRANCH_FULLNAME = /(remotes\/)?(#{BRANCH_NAME})/
|
9
|
+
|
10
|
+
def initialize(repo, line)
|
11
|
+
match = BRANCH_INFO.match(line)
|
12
|
+
if match && (fullname = match[2])
|
13
|
+
match = BRANCH_FULLNAME.match(fullname)
|
14
|
+
if match
|
15
|
+
@fullname = match[2]
|
16
|
+
@remote = !!match[1]
|
17
|
+
@repo = repo
|
18
|
+
else
|
19
|
+
raise FormatError, "Unrecognized branch name '#{line}'"
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise FormatError, "Unrecognized branch info '#{line}'"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@fullname
|
28
|
+
end
|
29
|
+
alias inspect to_s
|
30
|
+
|
31
|
+
def =~(other)
|
32
|
+
@fullname =~ other
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
self.to_s == other.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def <=>(other)
|
40
|
+
self.to_s <=> other.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def remote?
|
44
|
+
@remote
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
if remote?
|
49
|
+
#remove the initial remote-name in the branch (origin/master --> master)
|
50
|
+
bits = @fullname.split('/')
|
51
|
+
bits.shift
|
52
|
+
bits.join('/')
|
53
|
+
else
|
54
|
+
@fullname
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def display(width=40)
|
59
|
+
if @fullname.length >= width
|
60
|
+
(@fullname[0..(width-5)] + "...").ljust(width)
|
61
|
+
else
|
62
|
+
@fullname.ljust(width)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete
|
67
|
+
if self.remote?
|
68
|
+
@repo.shell("git push origin :#{self.name}")
|
69
|
+
else
|
70
|
+
@repo.shell("git branch -D #{@fullname}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|