right_develop 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,12 +1,18 @@
1
- source "http://rubygems.org"
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.1.0)
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: http://rubygems.org/
14
+ remote: https://rubygems.org/
13
15
  specs:
14
- activesupport (3.2.11)
15
- i18n (~> 0.6)
16
- multi_json (~> 1.0)
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.1.4)
21
+ builder (3.2.0)
19
22
  columnize (0.3.6)
20
- cucumber (1.2.1)
23
+ cucumber (1.2.3)
21
24
  builder (>= 2.1.2)
22
25
  diff-lcs (>= 1.1.3)
23
- gherkin (~> 2.11.0)
24
- json (>= 1.4.6)
25
- diff-lcs (1.1.3)
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.6)
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.5.0)
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 (3.12.1)
49
+ rdoc (4.0.0)
47
50
  json (~> 1.4)
48
51
  right_support (2.6.17)
49
- rspec (2.12.0)
50
- rspec-core (~> 2.12.0)
51
- rspec-expectations (~> 2.12.0)
52
- rspec-mocks (~> 2.12.0)
53
- rspec-core (2.12.2)
54
- rspec-expectations (2.12.1)
55
- diff-lcs (~> 1.1.3)
56
- rspec-mocks (2.12.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
@@ -1,5 +1,7 @@
1
1
  RightDevelop is a library of reusable, unit- and functional-tested Ruby code that RightScale has found broadly useful.
2
2
 
3
+ Maintained by the RightScale Teal Team
4
+
3
5
  == What Does It Do?
4
6
 
5
7
  === Continuous Integration
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
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, 'right_develop/ci'
28
- autoload :Parsers, 'right_develop/parsers'
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,6 @@
1
+ module RightDevelop
2
+ module Commands
3
+ end
4
+ end
5
+
6
+ require "right_develop/commands/git"
@@ -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