buildmeister 0.8.6 → 1.0.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/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
|