buildmeister 0.8.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +14 -8
- data/VERSION +1 -0
- data/bin/buildmeister +7 -3
- data/lib/buildmeister.rb +8 -298
- data/lib/buildmeister/base.rb +146 -0
- data/lib/buildmeister/bin.rb +35 -0
- data/lib/buildmeister/finder.rb +7 -0
- data/lib/buildmeister/git_utils.rb +65 -0
- data/lib/buildmeister/notifier.rb +7 -0
- data/lib/buildmeister/project.rb +45 -0
- data/lib/buildmeister/string_utils.rb +7 -0
- data/lib/git_cleanup.rb +1 -1
- data/spec/buildmeister/base_spec.rb +119 -0
- data/spec/buildmeister/bin_spec.rb +105 -0
- data/spec/buildmeister/project_spec.rb +84 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +14 -0
- metadata +29 -49
- data/Manifest.txt +0 -10
- data/config/buildmeister_config.sample.yml +0 -16
- data/test/test_buildmeister.rb +0 -0
data/Rakefile
CHANGED
@@ -4,14 +4,20 @@ require 'rubygems'
|
|
4
4
|
require 'hoe'
|
5
5
|
require './lib/buildmeister.rb'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
begin
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |s|
|
10
|
+
s.name = "buildmeister"
|
11
|
+
s.executables = ["buildmeister", "git_cleanup"]
|
12
|
+
s.summary = "Dead simple tools for managing Lighthouse and Git deployment workflow"
|
13
|
+
s.email = "lcaplan@onehub.com"
|
14
|
+
s.homepage = "http://github.com/onehub/buildmeister"
|
15
|
+
s.description = "Dead simple tools for managing Lighthouse and Git deployment workflow"
|
16
|
+
s.authors = ["Leigh Caplan"]
|
17
|
+
s.files = FileList["[A-Z]*", "{bin,generators,lib,spec}/**/*"]
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
15
21
|
end
|
16
22
|
|
17
23
|
# vim: syntax=Ruby
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/buildmeister
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
require File.expand_path(File.dirname(__FILE__) + "/../lib/buildmeister")
|
4
4
|
|
5
5
|
begin
|
6
|
-
|
6
|
+
command = ARGV.shift
|
7
|
+
args = ARGV.dup
|
8
|
+
ARGV.clear
|
9
|
+
|
10
|
+
Buildmeister::Base.new(*args).send command
|
7
11
|
rescue Interrupt => i
|
8
|
-
Buildmeister.
|
12
|
+
Buildmeister::Notifier.post("Buildmeister Shut Down", "Goodbye!")
|
9
13
|
puts "\rThank you for using Buildmeister!"
|
10
14
|
rescue Exception => e
|
11
|
-
Buildmeister.
|
15
|
+
Buildmeister::Notifier.post("Buildmeister Error: #{e.class}", e.message)
|
12
16
|
puts "Quitting Buildmeister due to error: #{e.message}"
|
13
17
|
puts e.backtrace
|
14
18
|
end
|
data/lib/buildmeister.rb
CHANGED
@@ -1,303 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'lighthouse'
|
3
3
|
require 'activesupport'
|
4
|
-
require 'optparse'
|
5
4
|
|
6
|
-
|
7
|
-
attr_accessor :project, :project_name, :bin_groups, :notification_interval
|
8
|
-
|
9
|
-
RETRY_COUNT = 5
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@options = {}
|
13
|
-
OptionParser.new do |opts|
|
14
|
-
opts.banner = "Usage: buildmeister notify"
|
5
|
+
$: << File.dirname(__FILE__)
|
15
6
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
opts.on('-v', '--verbose', 'Verbose') do |t|
|
25
|
-
@options[:verbose] = true
|
26
|
-
end
|
27
|
-
end.parse!
|
28
|
-
|
29
|
-
@config = Buildmeister.load_config
|
30
|
-
Lighthouse.account = @config['account']
|
31
|
-
Lighthouse.token = @config['token']
|
32
|
-
|
33
|
-
self.project_name = @config['project_name']
|
34
|
-
self.get_project
|
35
|
-
self.bin_groups = []
|
36
|
-
|
37
|
-
self.notification_interval = @config['notification_interval']
|
38
|
-
|
39
|
-
@config['bin_groups'].each do |bin_group|
|
40
|
-
self.bin_groups << {
|
41
|
-
:name => bin_group.keys.first,
|
42
|
-
:bin_names => bin_group.values.first.map
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
self.bin_groups.each do |bin_group|
|
47
|
-
bin_group[:bin_names].each do |bin_name|
|
48
|
-
class << bin_name
|
49
|
-
def normalize
|
50
|
-
Buildmeister.normalize_bin_name(self)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
attr_accessor_init = <<-eos
|
55
|
-
class << self
|
56
|
-
attr_accessor :"#{bin_name.normalize}", :"last_#{bin_name.normalize}"
|
57
|
-
end
|
58
|
-
eos
|
59
|
-
|
60
|
-
eval attr_accessor_init
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
load_project
|
65
|
-
end
|
66
|
-
|
67
|
-
def normalize(bin_name)
|
68
|
-
Buildmeister.normalize_bin_name(bin_name)
|
69
|
-
end
|
70
|
-
|
71
|
-
def new_hotfix
|
72
|
-
generate_timed_branch('hotfix')
|
73
|
-
end
|
74
|
-
|
75
|
-
def new_experimental
|
76
|
-
generate_timed_branch('experimental')
|
77
|
-
end
|
78
|
-
|
79
|
-
def generate_timed_branch(prefix)
|
80
|
-
branches = local_branches
|
81
|
-
now = Time.now
|
82
|
-
count = 1
|
83
|
-
|
84
|
-
loop do
|
85
|
-
new_branch_name = "#{prefix}-#{now.year}-#{now.month.to_s.rjust 2, '0'}-#{now.day.to_s.rjust 2, '0'}-#{count.to_s.rjust 3, '0'}"
|
86
|
-
unless branches.include? new_branch_name
|
87
|
-
`git checkout -b #{new_branch_name}`
|
88
|
-
puts "Created #{new_branch_name}"
|
89
|
-
return true
|
90
|
-
end
|
91
|
-
|
92
|
-
count += 1
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def pull_bin(bin_name = ARGV.shift)
|
97
|
-
bin_name = normalize(bin_name)
|
98
|
-
existing_bin_names = bin_names.map { |b| b.normalize }
|
99
|
-
|
100
|
-
raise ArgumentError, "#{bin_name} is not a valid bin! Must be in #{bin_names.join(', ')}" unless existing_bin_names.include?(bin_name)
|
101
|
-
|
102
|
-
`git fetch origin`
|
103
|
-
|
104
|
-
branches = remote_branches
|
105
|
-
ticket_numbers = send(normalize(bin_name)).tickets.map { |tkt| tkt.id.to_s }
|
106
|
-
|
107
|
-
branches_to_pull = branches.select do |branch_name|
|
108
|
-
ticket_numbers.map { |tkt_number| branch_name =~ /#{tkt_number}/ }.any?
|
109
|
-
end
|
110
|
-
|
111
|
-
branches_to_pull.each do |branch|
|
112
|
-
result = `git pull origin #{branch.gsub("origin/", "")}`
|
113
|
-
puts result
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def local_branches
|
118
|
-
`git branch`.split.reject { |name| name == "*" }
|
119
|
-
end
|
120
|
-
|
121
|
-
def remote_branches
|
122
|
-
`git branch -r`.split.reject { |name| name == "*" }
|
123
|
-
end
|
124
|
-
|
125
|
-
def current_branch
|
126
|
-
branches = `git branch`.split
|
127
|
-
i = branches.index "*"
|
128
|
-
branches[i + 1]
|
129
|
-
end
|
130
|
-
|
131
|
-
def move_all
|
132
|
-
bin_name = normalize @options[:move_from]
|
133
|
-
self.send(bin_name).tickets.each do |ticket|
|
134
|
-
ticket.state = @options[:to_state]
|
135
|
-
ticket.save
|
136
|
-
end
|
137
|
-
|
138
|
-
puts "All tickets from bin #{@options[:move_from]} have been moved to #{@options[:to_state]}"
|
139
|
-
end
|
140
|
-
|
141
|
-
def bin_group_report(bin_group_name = normalize(ARGV.shift))
|
142
|
-
bin_group = bin_groups.find { |group| group[:name] == bin_group_name }
|
143
|
-
bin_names = bin_group[:bin_names].map &:normalize
|
144
|
-
|
145
|
-
ticket_numbers = bin_names.map do |bin_name|
|
146
|
-
send(normalize(bin_name)).tickets.map &:id
|
147
|
-
end.flatten
|
148
|
-
|
149
|
-
# Pluck the relevant branch names using git...
|
150
|
-
relevant_branches =
|
151
|
-
remote_branches.select do |branch_name|
|
152
|
-
ticket_numbers.map { |tkt_number| branch_name =~ /#{tkt_number}/ }.any?
|
153
|
-
end.map { |b| b.gsub('origin/', '') }
|
154
|
-
|
155
|
-
output = ""
|
156
|
-
output << "#{current_branch}\n"
|
157
|
-
relevant_branches.each do |branch|
|
158
|
-
output << "\n#{branch}"
|
159
|
-
end
|
160
|
-
|
161
|
-
puts output
|
162
|
-
end
|
163
|
-
|
164
|
-
def resolve_verified
|
165
|
-
self.verified.tickets.each do |ticket|
|
166
|
-
ticket.state = 'resolved'
|
167
|
-
ticket.save
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def stage_all
|
172
|
-
self.ready.tickets.each do |ticket|
|
173
|
-
ticket.state = 'staged'
|
174
|
-
ticket.save
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def load_project
|
179
|
-
bins = self.get_project.bins
|
180
|
-
|
181
|
-
self.bin_groups.each do |bin_group|
|
182
|
-
bin_group[:bin_names].each do |bin_name|
|
183
|
-
self.send("#{bin_name.normalize}=", bins.find { |bin| bin.name == bin_name })
|
184
|
-
|
185
|
-
class << self.send("#{bin_name.normalize}")
|
186
|
-
attr_accessor :display_value
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def reload_info
|
193
|
-
bin_names.each do |bin_name|
|
194
|
-
send("last_#{bin_name.normalize}=", display_value(bin_name))
|
195
|
-
end
|
196
|
-
|
197
|
-
self.load_project
|
198
|
-
end
|
199
|
-
|
200
|
-
def bin_names
|
201
|
-
bin_groups.map do |bin_group|
|
202
|
-
bin_group[:bin_names].map do |bin_name|
|
203
|
-
bin_name
|
204
|
-
end
|
205
|
-
end.flatten
|
206
|
-
end
|
207
|
-
|
208
|
-
def changed?
|
209
|
-
bin_names.map { |bin_name| display_value(bin_name) } != bin_names.map { |bin_name| send("last_#{bin_name.normalize}") }
|
210
|
-
end
|
211
|
-
|
212
|
-
def get_project
|
213
|
-
self.project ||= Lighthouse::Project.find(:all).find {|pr| pr.name == project_name}
|
214
|
-
end
|
215
|
-
|
216
|
-
def notify
|
217
|
-
puts "Starting BuildMeister Notify..."
|
218
|
-
|
219
|
-
retry_count = RETRY_COUNT
|
220
|
-
|
221
|
-
loop do
|
222
|
-
begin
|
223
|
-
title = "BuildMeister: #{Time.now.strftime("%m/%d %I:%M %p")}"
|
224
|
-
|
225
|
-
body = ''
|
226
|
-
|
227
|
-
bin_groups.each do |bin_group|
|
228
|
-
body += "#{bin_group[:name].titleize}\n"
|
229
|
-
body += "---------\n"
|
230
|
-
|
231
|
-
bin_group[:bin_names].each do |bin_name|
|
232
|
-
body += "#{bin_name}: #{display_value(bin_name)}\n"
|
233
|
-
end
|
234
|
-
|
235
|
-
body += "\n"
|
236
|
-
end
|
237
|
-
|
238
|
-
puts "Updated notification at #{Time.now.strftime("%m/%d %I:%M %p")}"
|
239
|
-
|
240
|
-
if changed?
|
241
|
-
Buildmeister.post_notification(title, body)
|
242
|
-
end
|
243
|
-
|
244
|
-
sleep notification_interval.minutes.to_i
|
245
|
-
|
246
|
-
reload_info
|
247
|
-
|
248
|
-
# Reset the retry count, since we successfully completed this iteration
|
249
|
-
retry_count = RETRY_COUNT
|
250
|
-
|
251
|
-
rescue StandardError => e
|
252
|
-
if retry_count < 1
|
253
|
-
puts "Retried #{RETRY_COUNT} times... I give up!"
|
254
|
-
raise e
|
255
|
-
else
|
256
|
-
# Exponential falloff...
|
257
|
-
sleep_time = (50.0 * (1 / (retry_count / 2.0))).to_i
|
258
|
-
|
259
|
-
puts "Caught error: #{e.class.name}: #{e.message}"
|
260
|
-
puts "#{retry_count} more tries... sleeping #{sleep_time} seconds..."
|
261
|
-
|
262
|
-
sleep sleep_time
|
263
|
-
|
264
|
-
retry_count -= 1
|
265
|
-
|
266
|
-
retry
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
def display_value(bin_name)
|
273
|
-
# We're memoizing the display value on the bin objects
|
274
|
-
# so that when it comes time to reload, the previous value
|
275
|
-
# is kept.
|
276
|
-
send(bin_name.normalize).display_value ||=
|
277
|
-
if @options[:verbose]
|
278
|
-
send(bin_name.normalize).tickets.map(&:id).join(", ")
|
279
|
-
else
|
280
|
-
send(bin_name.normalize).tickets_count
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def git_cleanup
|
285
|
-
|
286
|
-
end
|
287
|
-
|
288
|
-
# -----------------------------------------
|
289
|
-
# Class Methods
|
290
|
-
# -----------------------------------------
|
291
|
-
|
292
|
-
def self.post_notification(title, body)
|
293
|
-
`growlnotify -H localhost -s -n "Buildmeister" -d "Buildmeister" -t #{title} -m "#{body}"`
|
294
|
-
end
|
295
|
-
|
296
|
-
def self.normalize_bin_name(bin_name)
|
297
|
-
bin_name.squeeze(' ').gsub(' ', '_').gsub(/\W/, '').downcase
|
298
|
-
end
|
299
|
-
|
300
|
-
def self.load_config
|
301
|
-
YAML.load_file(File.expand_path('~/.buildmeister_config.yml'))
|
302
|
-
end
|
303
|
-
end
|
7
|
+
require 'buildmeister/string_utils'
|
8
|
+
require 'buildmeister/git_utils'
|
9
|
+
require 'buildmeister/finder'
|
10
|
+
require 'buildmeister/base'
|
11
|
+
require 'buildmeister/notifier'
|
12
|
+
require 'buildmeister/project'
|
13
|
+
require 'buildmeister/bin'
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Buildmeister
|
4
|
+
class Base
|
5
|
+
include StringUtils
|
6
|
+
include GitUtils
|
7
|
+
|
8
|
+
RETRY_COUNT = 5
|
9
|
+
|
10
|
+
attr_accessor :projects, :notification_interval
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
@options = {:mode => :verbose}
|
14
|
+
|
15
|
+
OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: buildmeister notify"
|
17
|
+
|
18
|
+
opts.on('-q', '--quiet', 'Quiet') do
|
19
|
+
@options[:mode] = :quiet
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-f', '--from-bin BIN_NAME', 'Move From Bin') do |f|
|
23
|
+
@options[:move_from] = f
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on('-t', '--to-state STATE', 'Move to State') do |t|
|
27
|
+
@options[:to_state] = t
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-p', '--project PROJECT', 'Use Project') do |p|
|
31
|
+
@options[:project] = p
|
32
|
+
end
|
33
|
+
end.parse!(args)
|
34
|
+
|
35
|
+
# Lighthouse setup
|
36
|
+
@config = Buildmeister::Base.load_config
|
37
|
+
|
38
|
+
Lighthouse.account = @config['account']
|
39
|
+
Lighthouse.token = @config['token']
|
40
|
+
|
41
|
+
self.projects = []
|
42
|
+
|
43
|
+
projects.extend Finder
|
44
|
+
|
45
|
+
if @options[:project]
|
46
|
+
@config['projects'] = @config['projects'].select { |p| p['name'] == @options[:project] }
|
47
|
+
raise "#{@options[:project]} did not match any projects in the config file" if @config['projects'].blank?
|
48
|
+
end
|
49
|
+
|
50
|
+
@config['projects'].each do |project|
|
51
|
+
self.projects << Buildmeister::Project.new(project, @options)
|
52
|
+
end
|
53
|
+
|
54
|
+
self.notification_interval = @config['notification_interval']
|
55
|
+
end
|
56
|
+
|
57
|
+
def changed?
|
58
|
+
projects.any? &:changed?
|
59
|
+
end
|
60
|
+
|
61
|
+
def title
|
62
|
+
"Buildmeister: #{Time.now.strftime("%m/%d %I:%M %p")}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def notify
|
66
|
+
puts "Starting Buildmeister Notify..."
|
67
|
+
|
68
|
+
retry_count = RETRY_COUNT
|
69
|
+
|
70
|
+
loop do
|
71
|
+
begin
|
72
|
+
body = ''
|
73
|
+
|
74
|
+
body << projects.map do |project|
|
75
|
+
"#{project.display}\n"
|
76
|
+
end.join("\n")
|
77
|
+
|
78
|
+
puts "Updated notification at #{Time.now.strftime("%m/%d %I:%M %p")}"
|
79
|
+
|
80
|
+
if changed?
|
81
|
+
Buildmeister::Notifier.post(title, body)
|
82
|
+
end
|
83
|
+
|
84
|
+
sleep notification_interval.minutes.to_i
|
85
|
+
|
86
|
+
refresh!
|
87
|
+
|
88
|
+
# Reset the retry count, since we successfully completed this iteration
|
89
|
+
retry_count = RETRY_COUNT
|
90
|
+
|
91
|
+
rescue StandardError => e
|
92
|
+
if retry_count < 1
|
93
|
+
puts "Retried #{RETRY_COUNT} times... I give up!"
|
94
|
+
raise e
|
95
|
+
else
|
96
|
+
# Exponential falloff...
|
97
|
+
sleep_time = (50.0 * (1 / (retry_count / 2.0))).to_i
|
98
|
+
|
99
|
+
puts "Caught error: #{e.class.name}: #{e.message}"
|
100
|
+
puts "#{retry_count} more tries... sleeping #{sleep_time} seconds..."
|
101
|
+
|
102
|
+
sleep sleep_time
|
103
|
+
|
104
|
+
retry_count -= 1
|
105
|
+
|
106
|
+
retry
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def move_all
|
113
|
+
bin_name = @options[:move_from]
|
114
|
+
|
115
|
+
if projects.size > 1
|
116
|
+
puts "Two projects are loaded (#{projects.map(&:name).join(', ')})"
|
117
|
+
puts "Do you really want to move tickets in all projects? [y/n]"
|
118
|
+
|
119
|
+
choice = gets
|
120
|
+
|
121
|
+
if choice.downcase.strip == 'n'
|
122
|
+
puts "aborting..."
|
123
|
+
return
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
projects.each do |project|
|
128
|
+
project.bins.named(bin_name).tickets.each do |ticket|
|
129
|
+
puts "processing #{project.name}: #{ticket.id}"
|
130
|
+
ticket.state = @options[:to_state]
|
131
|
+
ticket.save
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
puts "All tickets from bin #{@options[:move_from]} have been moved to #{@options[:to_state]}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def refresh!
|
139
|
+
projects.each &:refresh!
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.load_config
|
143
|
+
YAML.load_file(File.expand_path('~/.buildmeister_config.yml'))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Wraps Lighthouse::Bin and keeps track of state
|
2
|
+
|
3
|
+
module Buildmeister
|
4
|
+
class Bin
|
5
|
+
attr_accessor :bin, :mode, :value, :last_value
|
6
|
+
|
7
|
+
delegate :name, :tickets, :to => :bin
|
8
|
+
|
9
|
+
def initialize(lighthouse_bin, mode = :verbose)
|
10
|
+
self.bin = lighthouse_bin
|
11
|
+
self.mode = mode
|
12
|
+
|
13
|
+
refresh!
|
14
|
+
end
|
15
|
+
|
16
|
+
def refresh!
|
17
|
+
self.last_value = value
|
18
|
+
|
19
|
+
case mode
|
20
|
+
when :verbose
|
21
|
+
self.value = bin.tickets.map(&:id).join(', ')
|
22
|
+
when :quiet
|
23
|
+
self.value = bin.tickets_count
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def display
|
28
|
+
"#{name}: #{value}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def changed?
|
32
|
+
value != last_value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Buildmeister
|
2
|
+
module GitUtils
|
3
|
+
def new_hotfix
|
4
|
+
generate_timed_branch('hotfix')
|
5
|
+
end
|
6
|
+
|
7
|
+
def new_experimental
|
8
|
+
generate_timed_branch('experimental')
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate_timed_branch(prefix)
|
12
|
+
branches = local_branches
|
13
|
+
now = Time.now
|
14
|
+
count = 1
|
15
|
+
|
16
|
+
loop do
|
17
|
+
new_branch_name = "#{prefix}-#{now.year}-#{now.month.to_s.rjust 2, '0'}-#{now.day.to_s.rjust 2, '0'}-#{count.to_s.rjust 3, '0'}"
|
18
|
+
unless branches.include? new_branch_name
|
19
|
+
`git checkout -b #{new_branch_name}`
|
20
|
+
puts "Created #{new_branch_name}"
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
count += 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def pull_bin(bin_name = ARGV.shift)
|
29
|
+
raise "Multiple projects are loaded. Please use the -p flag to select one project." if projects.size > 1
|
30
|
+
|
31
|
+
project = projects.first
|
32
|
+
bin = project.bins.named(bin_name)
|
33
|
+
|
34
|
+
raise ArgumentError, "#{bin_name} is not a valid bin! Must be in #{project.bins.map(&:name).join(', ')}" unless bin
|
35
|
+
|
36
|
+
`git fetch origin`
|
37
|
+
|
38
|
+
branches = remote_branches
|
39
|
+
ticket_numbers = bin.tickets.map { |tkt| tkt.id.to_s }
|
40
|
+
|
41
|
+
branches_to_pull = branches.select do |branch_name|
|
42
|
+
ticket_numbers.map { |tkt_number| branch_name =~ /#{tkt_number}/ }.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
branches_to_pull.each do |branch|
|
46
|
+
result = `git pull origin #{branch.gsub("origin/", "")}`
|
47
|
+
puts result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def local_branches
|
52
|
+
`git branch`.split.reject { |name| name == "*" }
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_branches
|
56
|
+
`git branch -r`.split.reject { |name| name == "*" }
|
57
|
+
end
|
58
|
+
|
59
|
+
def current_branch
|
60
|
+
branches = `git branch`.split
|
61
|
+
i = branches.index "*"
|
62
|
+
branches[i + 1]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Buildmeister
|
2
|
+
class Project
|
3
|
+
include StringUtils
|
4
|
+
|
5
|
+
attr_accessor :project, :name, :bins
|
6
|
+
|
7
|
+
def initialize(config, options = {})
|
8
|
+
self.name = config['name']
|
9
|
+
self.bins = []
|
10
|
+
|
11
|
+
bins.extend Finder
|
12
|
+
|
13
|
+
self.project = Lighthouse::Project.find(:all).find { |p| p.name == self.name }
|
14
|
+
|
15
|
+
project_bins = project.bins
|
16
|
+
|
17
|
+
config['bins'].each do |bin_name|
|
18
|
+
bin = project_bins.find { |b| b.name == bin_name }
|
19
|
+
raise "No bin named #{bin_name}" unless bin
|
20
|
+
|
21
|
+
bins << Buildmeister::Bin.new(bin, options[:mode])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def display
|
26
|
+
out = ''
|
27
|
+
out << name + "\n"
|
28
|
+
out << "#{divider}\n"
|
29
|
+
|
30
|
+
bins.each do |bin|
|
31
|
+
out << bin.display + "\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
out
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh!
|
38
|
+
bins.each &:refresh!
|
39
|
+
end
|
40
|
+
|
41
|
+
def changed?
|
42
|
+
bins.any? &:changed?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/git_cleanup.rb
CHANGED
@@ -7,7 +7,7 @@ require File.expand_path(File.dirname(__FILE__) + "/../lib/buildmeister")
|
|
7
7
|
class GitCleanup
|
8
8
|
|
9
9
|
def initialize
|
10
|
-
@config
|
10
|
+
@config = Buildmeister::Base.load_config
|
11
11
|
Lighthouse.token = @config['token']
|
12
12
|
Lighthouse.account = @config['account']
|
13
13
|
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buildmeister::Base do
|
4
|
+
before(:each) do
|
5
|
+
Buildmeister::Base.stubs(:load_config).returns(load_test_config)
|
6
|
+
@project_stub = stub(:name => 'Project', :bins => [stub(), stub()])
|
7
|
+
Buildmeister::Project.stubs(:new).returns(@project_stub)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#new' do
|
11
|
+
it "should create a new instance" do
|
12
|
+
b = Buildmeister::Base.new
|
13
|
+
b.should be_an_instance_of(Buildmeister::Base)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set quiet mode if passed -q" do
|
17
|
+
b = Buildmeister::Base.new('-q')
|
18
|
+
b.instance_variable_get(:@options)[:mode].should == :quiet
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set move_from if passed -f" do
|
22
|
+
b = Buildmeister::Base.new('-f', 'Cool Bin')
|
23
|
+
b.instance_variable_get(:@options)[:move_from].should == 'Cool Bin'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set to_state if passed -t" do
|
27
|
+
b = Buildmeister::Base.new('-t', 'staged')
|
28
|
+
b.instance_variable_get(:@options)[:to_state].should == 'staged'
|
29
|
+
end
|
30
|
+
|
31
|
+
context "passed the -p flag" do
|
32
|
+
before(:each) do
|
33
|
+
@b = Buildmeister::Base.new('-p', 'Macchiato')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set the project" do
|
37
|
+
@b.instance_variable_get(:@options)[:project].should == 'Macchiato'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should only load the specified project" do
|
41
|
+
@b.projects.size.should == 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it "should set the Lighthouse account from config" do
|
47
|
+
config = load_test_config
|
48
|
+
b = Buildmeister::Base.new
|
49
|
+
Lighthouse.account.should == config['account']
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the Lighthouse token from config" do
|
53
|
+
config = load_test_config
|
54
|
+
b = Buildmeister::Base.new
|
55
|
+
Lighthouse.token.should == config['token']
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should set up the projects" do
|
59
|
+
b = Buildmeister::Base.new
|
60
|
+
b.projects.should == [@project_stub, @project_stub]
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should set the notification interval" do
|
64
|
+
b = Buildmeister::Base.new
|
65
|
+
b.notification_interval.should == 3
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#changed?" do
|
70
|
+
context "with changed bins" do
|
71
|
+
before(:each) do
|
72
|
+
@project_stub = stub(:changed? => true)
|
73
|
+
Buildmeister::Project.stubs(:new).returns(@project_stub)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be true" do
|
77
|
+
b = Buildmeister::Base.new
|
78
|
+
b.changed?.should be_true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#projects" do
|
84
|
+
before(:each) do
|
85
|
+
@project_stub = stub(:name => 'Project', :bins => [stub(), stub()])
|
86
|
+
Buildmeister::Project.stubs(:new).returns(@project_stub)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should search using named" do
|
90
|
+
b = Buildmeister::Base.new
|
91
|
+
b.projects.named('Project').should == @project_stub
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "title" do
|
96
|
+
it "should look like this" do
|
97
|
+
Timecop.freeze(Time.parse('1/1/2010 9am')) do
|
98
|
+
b = Buildmeister::Base.new
|
99
|
+
b.title.should == 'Buildmeister: 01/01 09:00 AM'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#divider" do
|
105
|
+
before(:each) do
|
106
|
+
@b = Buildmeister::Base.new
|
107
|
+
end
|
108
|
+
|
109
|
+
it "looks like this by default" do
|
110
|
+
@b.divider.should == "----------"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "accepts character and size arguments" do
|
114
|
+
@b.divider("=", 5).should == '====='
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buildmeister::Bin do
|
4
|
+
def ticket_stubs
|
5
|
+
[stub('ticket_1', :id => 1), stub('ticket_2', :id => 2)]
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
Lighthouse::Bin.any_instance.stubs(:tickets).returns(ticket_stubs)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#new" do
|
13
|
+
it "should create a new instance" do
|
14
|
+
b = Buildmeister::Bin.new(Lighthouse::Bin.new)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should default to verbose mode" do
|
18
|
+
b = Buildmeister::Bin.new(Lighthouse::Bin.new)
|
19
|
+
b.mode.should == :verbose
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should refresh" do
|
23
|
+
Buildmeister::Bin.any_instance.expects(:refresh!).once
|
24
|
+
b = Buildmeister::Bin.new(Lighthouse::Bin.new)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#refresh!" do
|
29
|
+
before(:each) do
|
30
|
+
@b = Buildmeister::Bin.new(Lighthouse::Bin.new)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "in verbose mode" do
|
34
|
+
it "should call tickets" do
|
35
|
+
@b.bin.expects(:tickets).once.returns(ticket_stubs)
|
36
|
+
@b.refresh!
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should set value" do
|
40
|
+
@b.refresh!
|
41
|
+
@b.value.should == '1, 2'
|
42
|
+
end
|
43
|
+
|
44
|
+
context "after the first refresh" do
|
45
|
+
before(:each) do
|
46
|
+
@b.value = '1, 2'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should set the last value" do
|
50
|
+
@b.refresh!
|
51
|
+
@b.last_value.should == '1, 2'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "in quiet mode" do
|
57
|
+
before(:each) do
|
58
|
+
@b.mode = :quiet
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should call tickets_count" do
|
62
|
+
@b.bin.expects(:tickets_count).once.returns(2)
|
63
|
+
@b.refresh!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#changed?" do
|
69
|
+
before(:each) do
|
70
|
+
@b = Buildmeister::Bin.new(Lighthouse::Bin.new)
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with identical values" do
|
74
|
+
before(:each) do
|
75
|
+
@b.value = '1, 2'
|
76
|
+
@b.last_value = '1, 2'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be false" do
|
80
|
+
@b.changed?.should be_false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with differing values" do
|
85
|
+
before(:each) do
|
86
|
+
@b.value = '1'
|
87
|
+
@b.last_value = '1, 2'
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should be true" do
|
91
|
+
@b.changed?.should be_true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#display" do
|
97
|
+
before(:each) do
|
98
|
+
@b = Buildmeister::Bin.new(stub(:name => 'The Bin', :tickets => ticket_stubs))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "displays the ticket" do
|
102
|
+
@b.display.should == 'The Bin: 1, 2'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Buildmeister::Project' do
|
4
|
+
def valid_config
|
5
|
+
@valid_config ||= load_test_config['projects'].first
|
6
|
+
end
|
7
|
+
|
8
|
+
def project_stub
|
9
|
+
stub(:name => valid_config['name'], :bins => valid_config['bins'].map { |b| stub({:name => b}) })
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
Lighthouse::Project.stubs(:find).returns([project_stub])
|
14
|
+
@p = Buildmeister::Project.new(valid_config)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#new" do
|
18
|
+
it "should create new given a valid config" do
|
19
|
+
@p.should be_an_instance_of(Buildmeister::Project)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set the name" do
|
23
|
+
@p.name.should == valid_config['name']
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the project" do
|
27
|
+
@p.project
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set up the appropriate number of bins" do
|
31
|
+
@p.should have(valid_config['bins'].size).bins
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should set up Buildmeister::Bin objects" do
|
35
|
+
@p.bins.all? { |b| b.is_a?(Buildmeister::Bin) }.should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should keep bins in order" do
|
39
|
+
@p.bins.map(&:name).should == ['Ready', 'Staged', 'Verified', 'Ready (Experimental)', 'Staged (Experimental)']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#changed?" do
|
44
|
+
context "with changed bins" do
|
45
|
+
before(:each) do
|
46
|
+
@p.bins.each { |b| b.stubs(:changed?).returns(true) }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be true" do
|
50
|
+
@p.changed?.should be_true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with no changed bins" do
|
55
|
+
before(:each) do
|
56
|
+
@p.bins.each { |b| b.stubs(:changed?).returns(false) }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be false" do
|
60
|
+
@p.changed?.should be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#bins" do
|
66
|
+
it "should search using named" do
|
67
|
+
@p.bins.named('Ready').should be_an_instance_of(Buildmeister::Bin)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#display" do
|
72
|
+
it "should display the bins" do
|
73
|
+
@p.display.should == <<-STRING_CHEESE
|
74
|
+
Macchiato
|
75
|
+
----------
|
76
|
+
Ready:
|
77
|
+
Staged:
|
78
|
+
Verified:
|
79
|
+
Ready (Experimental):
|
80
|
+
Staged (Experimental):
|
81
|
+
STRING_CHEESE
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec/autorun'
|
2
|
+
require 'buildmeister'
|
3
|
+
require 'fakeweb'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
FakeWeb.allow_net_connect = false
|
7
|
+
|
8
|
+
Spec::Runner.configure do |config|
|
9
|
+
config.mock_with :mocha
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_test_config
|
13
|
+
@test_config ||= YAML.load_file(File.dirname(__FILE__) + '/../config/buildmeister_config.sample.yml')
|
14
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: buildmeister
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leigh Caplan
|
@@ -9,70 +9,47 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
13
|
-
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
version_requirement:
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 2.0.0
|
24
|
-
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: texel-lighthouse-api
|
27
|
-
type: :runtime
|
28
|
-
version_requirement:
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.0.1
|
34
|
-
version:
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: hoe
|
37
|
-
type: :development
|
38
|
-
version_requirement:
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
40
|
-
requirements:
|
41
|
-
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: 1.8.3
|
44
|
-
version:
|
45
|
-
description: FIX (describe your package)
|
46
|
-
email:
|
47
|
-
- lcaplan@onehub.com
|
12
|
+
date: 2009-12-10 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Dead simple tools for managing Lighthouse and Git deployment workflow
|
17
|
+
email: lcaplan@onehub.com
|
48
18
|
executables:
|
49
19
|
- buildmeister
|
50
20
|
- git_cleanup
|
51
21
|
extensions: []
|
52
22
|
|
53
23
|
extra_rdoc_files:
|
54
|
-
- History.txt
|
55
|
-
- Manifest.txt
|
56
24
|
- README.rdoc
|
57
25
|
files:
|
58
26
|
- History.txt
|
59
|
-
- Manifest.txt
|
60
27
|
- README.rdoc
|
61
28
|
- Rakefile
|
29
|
+
- VERSION
|
62
30
|
- bin/buildmeister
|
63
31
|
- bin/git_cleanup
|
64
|
-
- config/buildmeister_config.sample.yml
|
65
32
|
- lib/buildmeister.rb
|
33
|
+
- lib/buildmeister/base.rb
|
34
|
+
- lib/buildmeister/bin.rb
|
35
|
+
- lib/buildmeister/finder.rb
|
36
|
+
- lib/buildmeister/git_utils.rb
|
37
|
+
- lib/buildmeister/notifier.rb
|
38
|
+
- lib/buildmeister/project.rb
|
39
|
+
- lib/buildmeister/string_utils.rb
|
66
40
|
- lib/git_cleanup.rb
|
67
|
-
-
|
41
|
+
- spec/buildmeister/base_spec.rb
|
42
|
+
- spec/buildmeister/bin_spec.rb
|
43
|
+
- spec/buildmeister/project_spec.rb
|
44
|
+
- spec/spec.opts
|
45
|
+
- spec/spec_helper.rb
|
68
46
|
has_rdoc: true
|
69
|
-
homepage: http://github.com/
|
47
|
+
homepage: http://github.com/onehub/buildmeister
|
70
48
|
licenses: []
|
71
49
|
|
72
50
|
post_install_message:
|
73
51
|
rdoc_options:
|
74
|
-
- --
|
75
|
-
- README.txt
|
52
|
+
- --charset=UTF-8
|
76
53
|
require_paths:
|
77
54
|
- lib
|
78
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -89,10 +66,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
66
|
version:
|
90
67
|
requirements: []
|
91
68
|
|
92
|
-
rubyforge_project:
|
69
|
+
rubyforge_project:
|
93
70
|
rubygems_version: 1.3.5
|
94
71
|
signing_key:
|
95
|
-
specification_version:
|
96
|
-
summary:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Dead simple tools for managing Lighthouse and Git deployment workflow
|
97
74
|
test_files:
|
98
|
-
-
|
75
|
+
- spec/buildmeister/base_spec.rb
|
76
|
+
- spec/buildmeister/bin_spec.rb
|
77
|
+
- spec/buildmeister/project_spec.rb
|
78
|
+
- spec/spec_helper.rb
|
data/Manifest.txt
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
token: Your Lighthouse token
|
2
|
-
account: Your Lighthouse account name
|
3
|
-
project_name: Your Lighthouse project name
|
4
|
-
|
5
|
-
bin_groups:
|
6
|
-
- staging:
|
7
|
-
- Ready
|
8
|
-
- Staged
|
9
|
-
- Verified
|
10
|
-
|
11
|
-
- master:
|
12
|
-
- Ready (Experimental)
|
13
|
-
- Staged (Experimental)
|
14
|
-
- Verified (Experimental)
|
15
|
-
|
16
|
-
notification_interval: 5
|
data/test/test_buildmeister.rb
DELETED
File without changes
|