robot-army-git-deploy 0.0.4
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/LICENSE +13 -0
- data/README.markdown +4 -0
- data/Rakefile +9 -0
- data/lib/robot-army-git-deploy.rb +12 -0
- data/lib/robot-army-git-deploy/git_deployer.rb +251 -0
- data/lib/robot-army-git-deploy/grit_ext.rb +18 -0
- data/spec/git_deployer_spec.rb +230 -0
- data/spec/spec_helper.rb +26 -0
- metadata +124 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2007 Wesabe, Inc.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.markdown
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# external libraries
|
4
|
+
require 'rubygems'
|
5
|
+
require 'grit'
|
6
|
+
require 'robot-army'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'highline'
|
9
|
+
|
10
|
+
# internal files
|
11
|
+
require 'robot-army-git-deploy/git_deployer'
|
12
|
+
require 'robot-army-git-deploy/grit_ext'
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module RobotArmy::GitDeployer
|
2
|
+
def self.included(base)
|
3
|
+
base.const_set(:DEPLOY_COUNT, 5)
|
4
|
+
|
5
|
+
base.class_eval do
|
6
|
+
method_options :target_revision => :string
|
7
|
+
|
8
|
+
desc "check", "Checks the deploy status"
|
9
|
+
def check(opts={})
|
10
|
+
update_server_refs
|
11
|
+
|
12
|
+
run_pager
|
13
|
+
|
14
|
+
say "Deployed Revisions"
|
15
|
+
|
16
|
+
deployed_revisions.each do |host, revision|
|
17
|
+
if revision
|
18
|
+
commit = commit_from_revision_or_abort(revision)
|
19
|
+
puts "%s: %s %s [%s]" % [
|
20
|
+
host,
|
21
|
+
color(commit.id_abbrev, :yellow),
|
22
|
+
commit.message.to_a.first.chomp,
|
23
|
+
commit.author.name]
|
24
|
+
else
|
25
|
+
puts "%s: %s %s" % [
|
26
|
+
host,
|
27
|
+
color('0000000', :yellow),
|
28
|
+
"(no deployed revision)"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
puts
|
33
|
+
|
34
|
+
say "On Deck"
|
35
|
+
|
36
|
+
if oldest_deployed_revision == target_revision
|
37
|
+
puts "Deployed revision is up to date"
|
38
|
+
elsif oldest_deployed_revision
|
39
|
+
shortlog "#{oldest_deployed_revision}..#{target_revision}"
|
40
|
+
diff "#{oldest_deployed_revision}..#{target_revision}"
|
41
|
+
else
|
42
|
+
shortlog target_revision, :root => true
|
43
|
+
diff target_revision, :root => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "archive", "Write HEAD to a tgz file"
|
48
|
+
def archive
|
49
|
+
say "Archiving to #{archive_path}"
|
50
|
+
%x{git archive --format=tar #{target_revision} | gzip >#{archive_path}}
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "stage", "Stages the locally-generated archive on each host"
|
54
|
+
def stage
|
55
|
+
revision = repo.commits.first.id
|
56
|
+
|
57
|
+
# create the destination directory
|
58
|
+
sudo do
|
59
|
+
FileUtils.mkdir_p(deploy_path)
|
60
|
+
FileUtils.chown(user, group, deploy_path)
|
61
|
+
end
|
62
|
+
|
63
|
+
say "Staging #{app} into #{deploy_path}"
|
64
|
+
cptemp(archive_path, :user => user) do |path|
|
65
|
+
%x{tar -xvz -f #{path} -C #{deploy_path}}
|
66
|
+
File.open(File.join(deploy_path, 'REVISION'), 'w') {|f| f << revision}
|
67
|
+
path # just so that we don't try to return a File and generate a warning
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
no_tasks do
|
72
|
+
|
73
|
+
def install
|
74
|
+
say "Installing #{app} into #{current_link}"
|
75
|
+
sudo do
|
76
|
+
FileUtils.rm_f(current_link)
|
77
|
+
FileUtils.ln_sf(deploy_path, current_link)
|
78
|
+
end
|
79
|
+
update_server_refs(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
def cleanup
|
83
|
+
clean_temporary_files
|
84
|
+
clean_old_revisions
|
85
|
+
end
|
86
|
+
|
87
|
+
def clean_temporary_files
|
88
|
+
say "Cleaning up temporary files"
|
89
|
+
FileUtils.rm_f("#{app}-archive.tar.gz")
|
90
|
+
end
|
91
|
+
|
92
|
+
def clean_old_revisions
|
93
|
+
say "Cleaning up old revisions"
|
94
|
+
deploy_count = self.class.const_get(:DEPLOY_COUNT)
|
95
|
+
|
96
|
+
sudo do
|
97
|
+
deploy_paths = Dir.glob(File.join(deploy_root, '*')).sort
|
98
|
+
deploy_paths -= [current_link]
|
99
|
+
FileUtils.rm_rf(deploy_paths.first(deploy_paths.size - deploy_count)) if deploy_paths.size > deploy_count
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "run", "Run a full deploy"
|
104
|
+
def run
|
105
|
+
archive
|
106
|
+
stage
|
107
|
+
install
|
108
|
+
cleanup
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def repo
|
118
|
+
@repo ||= Grit::Repo.new(Dir.pwd)
|
119
|
+
end
|
120
|
+
|
121
|
+
def git
|
122
|
+
repo.git
|
123
|
+
end
|
124
|
+
|
125
|
+
def revfile
|
126
|
+
File.join(current_link, 'REVISION')
|
127
|
+
end
|
128
|
+
|
129
|
+
def deployed_revisions
|
130
|
+
@deployed_revisions ||= hosts.zip(Array(remote { File.read(revfile).chomp if File.exist?(revfile) }))
|
131
|
+
end
|
132
|
+
|
133
|
+
def clear_deployed_revisions_cache
|
134
|
+
@deployed_revisions = nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def deployed_revisions_equal?
|
138
|
+
deployed_revisions.map{|host, revision| revision}.uniq.size == 1
|
139
|
+
end
|
140
|
+
|
141
|
+
def target_revision
|
142
|
+
options[:target_revision] || repo.head.commit
|
143
|
+
end
|
144
|
+
|
145
|
+
def oldest_deployed_revision
|
146
|
+
oldest_commit = deployed_revisions.
|
147
|
+
map {|host, revision| repo.commit(revision) if revision}.
|
148
|
+
compact.
|
149
|
+
sort.
|
150
|
+
first
|
151
|
+
return oldest_commit.id if oldest_commit
|
152
|
+
end
|
153
|
+
|
154
|
+
def update_server_refs(refresh=false)
|
155
|
+
clear_deployed_revisions_cache if refresh
|
156
|
+
|
157
|
+
deployed_revisions.each do |host, revision|
|
158
|
+
# thanks to doener in #git for this idea
|
159
|
+
git.update_ref({}, "refs/servers/#{host}", revision) if revision
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def clear_deployed_refs
|
164
|
+
pairs = git.for_each_ref({:format => "%(objectname) %(refname)"}, 'refs/servers').to_a
|
165
|
+
pairs.map!{|line| line.chomp.split(' ')}
|
166
|
+
pairs.each do |objectname, refname|
|
167
|
+
git.update_ref({:d => true}, refname, objectname)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def archive_path
|
172
|
+
"#{app}-archive.tar.gz"
|
173
|
+
end
|
174
|
+
|
175
|
+
def deploy_root
|
176
|
+
File.expand_path(File.join(root, app))
|
177
|
+
end
|
178
|
+
|
179
|
+
def deploy_path
|
180
|
+
File.join(deploy_root, timestamp)
|
181
|
+
end
|
182
|
+
|
183
|
+
def current_link
|
184
|
+
File.join(deploy_root, 'current')
|
185
|
+
end
|
186
|
+
|
187
|
+
def color(*args)
|
188
|
+
(@highline ||= HighLine.new).color(*args)
|
189
|
+
end
|
190
|
+
|
191
|
+
def log(what, options={})
|
192
|
+
puts git.log({:pretty=>'oneline', :'abbrev-commit'=>true, :color=>true}.merge(options), what)
|
193
|
+
end
|
194
|
+
|
195
|
+
def shortlog(what, options={})
|
196
|
+
puts git.shortlog({:color=>true}.merge(options), what)
|
197
|
+
end
|
198
|
+
|
199
|
+
def diff(what, options={})
|
200
|
+
# dumb, dumb, dumb
|
201
|
+
puts git.send(:method_missing, :diff, {:stat => true, :color => true}.merge(options), what)
|
202
|
+
end
|
203
|
+
|
204
|
+
def deployed_revision
|
205
|
+
deployed_revisions.find{|host, revision| revision}.last
|
206
|
+
end
|
207
|
+
|
208
|
+
def commit_from_revision_or_abort(revision)
|
209
|
+
if commit = repo.commits(revision, 1).first
|
210
|
+
return commit
|
211
|
+
else
|
212
|
+
$stderr.puts "#{color('ERROR', :red)}: The deployed revision (#{color(revision, :yellow)}) was not found in your local repository. Perhaps you need to update first?"
|
213
|
+
exit(1)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def revfile
|
218
|
+
File.join(current_link, 'REVISION')
|
219
|
+
end
|
220
|
+
|
221
|
+
def timestamp
|
222
|
+
@timestamp ||= Time.now.utc.strftime("%Y-%m-%d-%H-%M-%S")
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def run_pager
|
227
|
+
return if PLATFORM =~ /win32/
|
228
|
+
return unless STDOUT.tty?
|
229
|
+
|
230
|
+
read, write = IO.pipe
|
231
|
+
|
232
|
+
unless Kernel.fork # Child process
|
233
|
+
STDOUT.reopen(write)
|
234
|
+
STDERR.reopen(write) if STDERR.tty?
|
235
|
+
read.close
|
236
|
+
write.close
|
237
|
+
return
|
238
|
+
end
|
239
|
+
|
240
|
+
# Parent process, become pager
|
241
|
+
STDIN.reopen(read)
|
242
|
+
read.close
|
243
|
+
write.close
|
244
|
+
|
245
|
+
ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
|
246
|
+
|
247
|
+
Kernel.select [STDIN] # Wait until we have input before we start the pager
|
248
|
+
pager = ENV['PAGER'] || 'less'
|
249
|
+
exec pager rescue exec "/bin/sh", "-c", pager
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Grit::Commit
|
2
|
+
include Comparable
|
3
|
+
|
4
|
+
def <=>(other)
|
5
|
+
raise ArgumentError unless other.is_a?(Grit::Commit)
|
6
|
+
|
7
|
+
if id == other.id
|
8
|
+
return 0
|
9
|
+
elsif not @repo.commits("#{id}..#{other.id}").empty?
|
10
|
+
return -1
|
11
|
+
elsif not @repo.commits("#{other.id}..#{id}").empty?
|
12
|
+
return 1
|
13
|
+
else
|
14
|
+
raise ArgumentError,
|
15
|
+
"#{other.inspect} is not an ancestor of #{self.inspect} or vice-versa, and are therefor not comparable"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Deploy < RobotArmy::TaskMaster
|
4
|
+
include RobotArmy::GitDeployer
|
5
|
+
|
6
|
+
hosts %w[test1 test2 test3]
|
7
|
+
|
8
|
+
def remote(hosts=self.class.hosts)
|
9
|
+
hosts.size == 1 ? yield : hosts.map { yield }
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :sudo, :remote
|
13
|
+
|
14
|
+
def root
|
15
|
+
"/opt"
|
16
|
+
end
|
17
|
+
|
18
|
+
def app
|
19
|
+
"test"
|
20
|
+
end
|
21
|
+
|
22
|
+
def user
|
23
|
+
"nobody"
|
24
|
+
end
|
25
|
+
|
26
|
+
def group
|
27
|
+
"nobody"
|
28
|
+
end
|
29
|
+
|
30
|
+
def color(str, color)
|
31
|
+
str
|
32
|
+
end
|
33
|
+
|
34
|
+
# make all methods public so we can test 'em easily
|
35
|
+
private_instance_methods.each { |m| public m }
|
36
|
+
end
|
37
|
+
|
38
|
+
class FakeCommit
|
39
|
+
attr_reader :id
|
40
|
+
include Comparable
|
41
|
+
|
42
|
+
def initialize(id)
|
43
|
+
@id = id
|
44
|
+
end
|
45
|
+
|
46
|
+
def <=>(other)
|
47
|
+
id <=> other.id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe RobotArmy::GitDeployer do
|
52
|
+
before do
|
53
|
+
@deploy = Deploy.new
|
54
|
+
@deploy.options = (@options = {})
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "tasks" do
|
58
|
+
it "defines a 'check' task" do
|
59
|
+
Deploy.tasks['check'].must be_an_instance_of(Thor::Task)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "defines a 'run' task" do
|
63
|
+
Deploy.tasks['run'].must be_an_instance_of(Thor::Task)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "defines a 'cleanup' task" do
|
67
|
+
Deploy.tasks['cleanup'].must be_an_instance_of(Thor::Task)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "defines a 'install' task" do
|
71
|
+
Deploy.tasks['install'].must be_an_instance_of(Thor::Task)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "defines a 'archive' task" do
|
75
|
+
Deploy.tasks['archive'].must be_an_instance_of(Thor::Task)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "defines a 'stage' task" do
|
79
|
+
Deploy.tasks['stage'].must be_an_instance_of(Thor::Task)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "plumbing" do
|
84
|
+
describe "when the revfile exists on all hosts" do
|
85
|
+
before do
|
86
|
+
File.stub!(:exist?).with(@deploy.revfile).any_number_of_times.and_return(true)
|
87
|
+
File.stub!(:read).with(@deploy.revfile).any_number_of_times.and_return("abcde\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
it "can get the list of deployed revisions" do
|
91
|
+
@deploy.deployed_revisions.must == [%w[test1 abcde], %w[test2 abcde], %w[test3 abcde]]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "when the revfile doesn't exist on a host" do
|
97
|
+
before do
|
98
|
+
File.stub!(:exist?).with(@deploy.revfile).and_return(true, true, false)
|
99
|
+
File.stub!(:read).with(@deploy.revfile).twice.and_return("abcde\n")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns nil for that host's revision" do
|
103
|
+
@deploy.deployed_revisions.must == [%w[test1 abcde], %w[test2 abcde], ['test3', nil]]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "oldest_deployed_revision" do
|
108
|
+
describe "when all revisions are equal" do
|
109
|
+
before do
|
110
|
+
@deploy.stub!(:repo).and_return(stub(:repo, :commit => FakeCommit.new('abcde')))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "sorts commits and returns the first (oldest) one" do
|
114
|
+
@deploy.
|
115
|
+
stub!(:deployed_revisions).
|
116
|
+
and_return([%w[test1 abcde], %w[test2 abcde], %w[test3 abcde]])
|
117
|
+
|
118
|
+
@deploy.oldest_deployed_revision.must == 'abcde'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "when not all revisions are equal" do
|
123
|
+
before do
|
124
|
+
@repo = stub(:repo)
|
125
|
+
@repo.
|
126
|
+
stub!(:commit).
|
127
|
+
and_return(FakeCommit.new('cdeab'), FakeCommit.new('abcde'), FakeCommit.new('deabc'))
|
128
|
+
|
129
|
+
@deploy.
|
130
|
+
stub!(:repo).
|
131
|
+
and_return(@repo)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "sorts commits and returns the first (oldest) one" do
|
135
|
+
@deploy.
|
136
|
+
stub!(:deployed_revisions).
|
137
|
+
and_return([%w[test1 cdeab], %w[test2 abcde], %w[test3 deabc]])
|
138
|
+
|
139
|
+
@deploy.oldest_deployed_revision.must == 'abcde'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "target_revision" do
|
145
|
+
it "defaults to the current HEAD" do
|
146
|
+
@deploy.stub!(:repo).and_return(stub(:repo, :commits => [FakeCommit.new('abcde')]))
|
147
|
+
@deploy.target_revision.must == 'abcde'
|
148
|
+
end
|
149
|
+
|
150
|
+
it "can be set as an option" do
|
151
|
+
@options[:target_revision] = 'production'
|
152
|
+
@deploy.target_revision.must == 'production'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "commit_from_revision_or_abort" do
|
157
|
+
describe "given a revision in the repository" do
|
158
|
+
before do
|
159
|
+
@commit = FakeCommit.new('abcde')
|
160
|
+
@deploy.stub!(:repo).and_return(stub(:repo, :commits => [@commit]))
|
161
|
+
end
|
162
|
+
|
163
|
+
it "returns the commit for that revision" do
|
164
|
+
@deploy.commit_from_revision_or_abort('abcde').must == @commit
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "given a revision not in the repository" do
|
169
|
+
before do
|
170
|
+
@deploy.stub!(:repo).and_return(stub(:repo, :commits => []))
|
171
|
+
end
|
172
|
+
|
173
|
+
it "warns about the missing commit and exits" do
|
174
|
+
@deploy.should_receive(:exit).with(1)
|
175
|
+
capture(:stderr) { @deploy.commit_from_revision_or_abort('abcde') }.
|
176
|
+
must == "ERROR: The deployed revision (abcde) was not found in your local repository. Perhaps you need to update first?\n"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "clean_old_revisions" do
|
182
|
+
def deployed_revision_paths_for_range(range, include_current=true)
|
183
|
+
(include_current ? [@deploy.current_link] : []) + range.map {|i| File.join(@deploy.deploy_root, "deploy-#{i}")}
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "when there are no deployed revisions" do
|
187
|
+
before do
|
188
|
+
Dir.stub!(:glob).with(File.join(@deploy.deploy_root, '*')).and_return(deployed_revision_paths_for_range(1...1))
|
189
|
+
end
|
190
|
+
|
191
|
+
it "does not remove anything" do
|
192
|
+
FileUtils.should_not_receive(:rm_rf)
|
193
|
+
silence(:stdout) { @deploy.clean_old_revisions }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "when there are less than DEPLOY_COUNT deployed revisions" do
|
198
|
+
before do
|
199
|
+
Dir.stub!(:glob).with(File.join(@deploy.deploy_root, '*')).and_return(deployed_revision_paths_for_range(1...Deploy::DEPLOY_COUNT))
|
200
|
+
end
|
201
|
+
|
202
|
+
it "does not remove anything" do
|
203
|
+
FileUtils.should_not_receive(:rm_rf)
|
204
|
+
silence(:stdout) { @deploy.clean_old_revisions }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "when there are exactly DEPLOY_COUNT deployed revisions" do
|
209
|
+
before do
|
210
|
+
Dir.stub!(:glob).with(File.join(@deploy.deploy_root, '*')).and_return(deployed_revision_paths_for_range(1..Deploy::DEPLOY_COUNT))
|
211
|
+
end
|
212
|
+
|
213
|
+
it "does not remove anything" do
|
214
|
+
FileUtils.should_not_receive(:rm_rf)
|
215
|
+
silence(:stdout) { @deploy.clean_old_revisions }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "when there are more than DEPLOY_COUNT deployed revisions" do
|
220
|
+
before do
|
221
|
+
Dir.stub!(:glob).with(File.join(@deploy.deploy_root, '*')).and_return(deployed_revision_paths_for_range(1..(Deploy::DEPLOY_COUNT+2)))
|
222
|
+
end
|
223
|
+
|
224
|
+
it "removes enough of the oldest deployed revisions to get the count down to DEPLOY_COUNT" do
|
225
|
+
FileUtils.should_receive(:rm_rf).with(deployed_revision_paths_for_range(1..2, false)).exactly(@deploy.hosts.size).times
|
226
|
+
silence(:stdout) { @deploy.clean_old_revisions }
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
load File.join(File.dirname(__FILE__), '..', 'lib', 'robot-army-git-deploy.rb')
|
3
|
+
|
4
|
+
module Spec::Expectations::ObjectExpectations
|
5
|
+
alias_method :must, :should
|
6
|
+
alias_method :must_not, :should_not
|
7
|
+
undef_method :should
|
8
|
+
undef_method :should_not
|
9
|
+
end
|
10
|
+
|
11
|
+
Spec::Runner.configure do |config|
|
12
|
+
def capture(stream)
|
13
|
+
begin
|
14
|
+
stream = stream.to_s
|
15
|
+
eval "$#{stream} = StringIO.new"
|
16
|
+
yield
|
17
|
+
result = eval("$#{stream}").string
|
18
|
+
ensure
|
19
|
+
eval("$#{stream} = #{stream.upcase}")
|
20
|
+
end
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
alias silence capture
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: robot-army-git-deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Brian Donovan
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2009-09-23 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: robot-army
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 1
|
30
|
+
- 7
|
31
|
+
version: 0.1.7
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: thor
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 11
|
44
|
+
- 3
|
45
|
+
version: 0.11.3
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: grit
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
- 0
|
58
|
+
- 0
|
59
|
+
version: 0.0.0
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: highline
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
- 0
|
72
|
+
- 0
|
73
|
+
version: 0.0.0
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Robot Army deployment with git repositories
|
77
|
+
email: brian@wesabe.com
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files:
|
83
|
+
- LICENSE
|
84
|
+
- README.markdown
|
85
|
+
files:
|
86
|
+
- LICENSE
|
87
|
+
- README.markdown
|
88
|
+
- Rakefile
|
89
|
+
- lib/robot-army-git-deploy.rb
|
90
|
+
- lib/robot-army-git-deploy/git_deployer.rb
|
91
|
+
- lib/robot-army-git-deploy/grit_ext.rb
|
92
|
+
has_rdoc: true
|
93
|
+
homepage: http://github.com/wesabe/robot-army-git-deploy
|
94
|
+
licenses: []
|
95
|
+
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options:
|
98
|
+
- --charset=UTF-8
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project: robot-army-git-deploy
|
118
|
+
rubygems_version: 1.3.6
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: Robot Army deployment with git repositories
|
122
|
+
test_files:
|
123
|
+
- spec/git_deployer_spec.rb
|
124
|
+
- spec/spec_helper.rb
|