ianwhite-garlic 0.1.2
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/CHANGELOG +5 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +206 -0
- data/TODO +2 -0
- data/bin/garlic +74 -0
- data/lib/garlic/configurator.rb +60 -0
- data/lib/garlic/generator.rb +27 -0
- data/lib/garlic/repo.rb +118 -0
- data/lib/garlic/runner.rb +144 -0
- data/lib/garlic/shell.rb +67 -0
- data/lib/garlic/target.rb +138 -0
- data/lib/garlic/tasks.rb +23 -0
- data/lib/garlic.rb +37 -0
- data/spec/garlic/repo_spec.rb +38 -0
- data/templates/default.rb +28 -0
- data/templates/rspec.rb +34 -0
- data/templates/shoulda.rb +30 -0
- metadata +71 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Ian White - ian.w.white@ardes.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
h1. garlic: lightweight continuous integration for rails using git
|
2
|
+
|
3
|
+
This is not a CI server, use cruisecontrol.rb for that. This is a simple set
|
4
|
+
of rake tasks that let you specify a bunch of rails builds to run against, and
|
5
|
+
dependencies to install.
|
6
|
+
|
7
|
+
This is aimed at testing plugins (or apps) against multiple versions of rails,
|
8
|
+
and allows specifying other plugin dependencies (and their versions and any
|
9
|
+
setup requried).
|
10
|
+
|
11
|
+
If you want to run your specs (or whatever) against different versions of gems
|
12
|
+
that you have installed, then check out "ginger":http://github.com/freelancing-god/ginger by "Pat Allen":http://github.com/freelancing-god
|
13
|
+
|
14
|
+
Garlic works by cloning git repos for all your dependencies (so they all must be
|
15
|
+
git repos), and then using git to checkout various tags and branches to build
|
16
|
+
your app against.
|
17
|
+
|
18
|
+
h2. Get up and running quickly
|
19
|
+
|
20
|
+
You have a plugin and you want it tested against different versions of rails?
|
21
|
+
|
22
|
+
* install garlic as a gem (see below)
|
23
|
+
* cd into your (say, rspec tested) plugin directory
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
garlic generate rspec > garlic.rb
|
27
|
+
garlic install_repos
|
28
|
+
garlic
|
29
|
+
</pre>
|
30
|
+
|
31
|
+
* See what happens, edit garlic.rb to change rails versions and other stuff.
|
32
|
+
|
33
|
+
<pre>
|
34
|
+
garlic --help # will probably help
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
h2. Installing
|
38
|
+
|
39
|
+
Install the garlic gem
|
40
|
+
|
41
|
+
sudo gem install ianwhite-garlic --source=http://gems.github.com
|
42
|
+
|
43
|
+
(if it's not installing as a gem from github)
|
44
|
+
|
45
|
+
git clone git://github.com/ianwhite/garlic
|
46
|
+
cd garlic
|
47
|
+
rake package
|
48
|
+
sudo gem install pkg/*.gem
|
49
|
+
|
50
|
+
h2. Example
|
51
|
+
|
52
|
+
To see garlic in action, download response_for, a rails plugin that uses
|
53
|
+
garlic for CI.
|
54
|
+
|
55
|
+
git clone git://github.com/ianwhite/response_for
|
56
|
+
|
57
|
+
run garlic
|
58
|
+
|
59
|
+
garlic all
|
60
|
+
|
61
|
+
This will clone all the required git repos (done only once), set up the target
|
62
|
+
railses (done once), then run the targets.
|
63
|
+
|
64
|
+
h3. Once you've made some changes
|
65
|
+
|
66
|
+
You can prepare and run all the targets again (without fetching remote repos) by doing
|
67
|
+
|
68
|
+
garlic
|
69
|
+
|
70
|
+
This will prepare all the targets, using the current HEAD of the repos, and run the
|
71
|
+
CI task again.
|
72
|
+
|
73
|
+
h3. Specifying particular targets
|
74
|
+
|
75
|
+
If you just want to run against a particular target or targets, you can use the TARGET or TARGETS
|
76
|
+
env var.
|
77
|
+
|
78
|
+
garlic -t edge
|
79
|
+
|
80
|
+
h2. Running Shell commands across multiple targets
|
81
|
+
|
82
|
+
Check dis out
|
83
|
+
|
84
|
+
garlic shell # "Example output":http://gist.github.com/21496
|
85
|
+
|
86
|
+
---
|
87
|
+
|
88
|
+
The following still needs to be updated for the new gem/cmd-line version
|
89
|
+
|
90
|
+
h2. Example workflow
|
91
|
+
|
92
|
+
Let's say I'm patching resources_controller.
|
93
|
+
|
94
|
+
First I grab it, and set up garlic
|
95
|
+
|
96
|
+
git clone git://github.com/ianwhite/resources_controller.git
|
97
|
+
cd resources_controller
|
98
|
+
rake get_garlic
|
99
|
+
cp garlic_example.rb garlic.rb
|
100
|
+
# I could now edit garlic.rb to point the repos at my local copies, for speed
|
101
|
+
|
102
|
+
Now, I download and run the CI suite
|
103
|
+
|
104
|
+
rake garlic:all
|
105
|
+
|
106
|
+
Now, I make some changes
|
107
|
+
|
108
|
+
git checkout -b my_change
|
109
|
+
# ... commit some changes into 'my_change'
|
110
|
+
rake garlic
|
111
|
+
# ... everything is fine, so I can merge these into master, or send a pull request
|
112
|
+
|
113
|
+
h3. How do I run the specs on uncommitted code?
|
114
|
+
|
115
|
+
The best way is to make the changes in one of the 'work' targets. For example:
|
116
|
+
|
117
|
+
# after running rake garlic:all
|
118
|
+
cd garlic/work/edge/vendor/plugins/resources_controller
|
119
|
+
# ... make changes without committing
|
120
|
+
rake spec
|
121
|
+
# ... it passes, so commit
|
122
|
+
git commit -m "My great change"
|
123
|
+
|
124
|
+
Now you can push these changes back upstream to your local 'master' repo
|
125
|
+
|
126
|
+
git push origin my_changes # or you could push to master branch or whatever
|
127
|
+
|
128
|
+
Then cd back up there, and run rake garlic to verify your changes against the other
|
129
|
+
targets. If these all pass, you can push, or send a pull request
|
130
|
+
|
131
|
+
h2.How to add garlic to your repo (example: rails plugin)
|
132
|
+
|
133
|
+
h4. 1. require the garlic tasks into your own Rakefile
|
134
|
+
|
135
|
+
# rescue this just in case the plugin user doesn't have garlic
|
136
|
+
begin
|
137
|
+
require 'garlic'
|
138
|
+
rescue LoadError
|
139
|
+
end
|
140
|
+
|
141
|
+
h4. 2. add a garlic.rb
|
142
|
+
|
143
|
+
An example garlic.rb:
|
144
|
+
|
145
|
+
garlic do
|
146
|
+
# repos
|
147
|
+
repo 'rails', :url => 'git://github.com/rails/rails'
|
148
|
+
repo 'rspec', :url => 'git://github.com/dchelimsky/rspec'
|
149
|
+
repo 'rspec-rails', :url => 'git://github.com/dchelimsky/rspec-rails'
|
150
|
+
repo 'resources_controller', :path => '.'
|
151
|
+
|
152
|
+
# targets
|
153
|
+
target 'edge'
|
154
|
+
target '2.0-stable', :branch => 'origin/2-0-stable'
|
155
|
+
target '2.0.2', :tag => 'v2.0.2'
|
156
|
+
|
157
|
+
all_targets do
|
158
|
+
prepare do
|
159
|
+
plugin 'resources_controller', :clone => true
|
160
|
+
plugin 'rspec'
|
161
|
+
plugin 'rspec-rails' do
|
162
|
+
sh "script/generate rspec -f"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
run do
|
167
|
+
cd "vendor/plugins/resources_controller"
|
168
|
+
sh "rake spec:rcov:verify"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
h4. 3. ignore the garlic artefacts
|
175
|
+
|
176
|
+
Example .gitignore
|
177
|
+
|
178
|
+
.garlic_work
|
179
|
+
|
180
|
+
h4. 4. Run it
|
181
|
+
|
182
|
+
rake garlic:all
|
183
|
+
|
184
|
+
And to run it again, once you've made changes
|
185
|
+
|
186
|
+
rake garlic
|
187
|
+
|
188
|
+
To make sure you're running against the latest repos:
|
189
|
+
|
190
|
+
rake garlic:update_repos
|
191
|
+
|
192
|
+
h2. Lend a hand
|
193
|
+
|
194
|
+
This is an early release, so there is plenty of scope for changes and improvement
|
195
|
+
If you want to lend a hand, get in touch.
|
196
|
+
|
197
|
+
(c) Ian White 2008 - ian.w.white@gmail.com
|
198
|
+
MIT Licence
|
199
|
+
|
200
|
+
h2. Lent a hand
|
201
|
+
|
202
|
+
Thanks very much to:
|
203
|
+
|
204
|
+
* Pat Allan
|
205
|
+
|
206
|
+
|
data/TODO
ADDED
data/bin/garlic
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'getoptlong'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
require 'garlic'
|
7
|
+
|
8
|
+
include Garlic
|
9
|
+
|
10
|
+
USAGE = "USAGE: garlic [options] [command] (try garlic --help)"
|
11
|
+
|
12
|
+
HELP = <<-end_doc
|
13
|
+
garlic - run a command in different versions of rails
|
14
|
+
|
15
|
+
USAGE: garlic [options] [command]
|
16
|
+
|
17
|
+
COMMANDS:
|
18
|
+
#{Garlic::Runner.commands_with_description.map{|method, desc| " %-17s %s" % [method, desc]}.join("\n")}
|
19
|
+
|
20
|
+
The default command is "default"
|
21
|
+
|
22
|
+
OPTIONS:
|
23
|
+
--help -h You're reading it
|
24
|
+
--verbose -v Show work
|
25
|
+
--config CONFIG -c Specify a different location of garlic config
|
26
|
+
--targets TARGETS -t Specify subset of targets, e.g. edge,2.1.0 (default all)
|
27
|
+
--backtrace Show ruby bakctrace on error
|
28
|
+
|
29
|
+
You can generate a sample garlic.rb with
|
30
|
+
garlic generate [TEMPLATE [PLUGIN_NAME]] (Available templates: #{available_templates.join(', ')})
|
31
|
+
|
32
|
+
end_doc
|
33
|
+
|
34
|
+
GetoptLong.new(
|
35
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT],
|
36
|
+
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
|
37
|
+
['--targets', '-t', GetoptLong::REQUIRED_ARGUMENT],
|
38
|
+
['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],
|
39
|
+
['--backtrace', GetoptLong::NO_ARGUMENT]
|
40
|
+
).each do |opt, arg|
|
41
|
+
case opt
|
42
|
+
when '--help' then STDOUT << HELP; exit true
|
43
|
+
when '--verbose' then @verbose = true
|
44
|
+
when '--config' then @config_file = arg
|
45
|
+
when '--targets' then @run_targets = arg
|
46
|
+
when '--backtrace' then @backtrace = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
# generate a config file
|
52
|
+
if ARGV.first == 'generate'
|
53
|
+
generate_config(*ARGV[1..-1])
|
54
|
+
|
55
|
+
# run a garlic command
|
56
|
+
else
|
57
|
+
raise "Unknown command: #{ARGV.first}" unless ARGV.empty? || Garlic::Runner.commands.include?(ARGV.first)
|
58
|
+
|
59
|
+
# configure the garlic runner
|
60
|
+
garlic @config_file do
|
61
|
+
verbose @verbose
|
62
|
+
run_targets @run_targets
|
63
|
+
end
|
64
|
+
|
65
|
+
# run the command
|
66
|
+
ARGV << 'default' if ARGV.empty?
|
67
|
+
garlic.send *ARGV
|
68
|
+
end
|
69
|
+
|
70
|
+
rescue Exception => e
|
71
|
+
STDERR << "\n#{USAGE}\n\nError: #{e.message}\n\n"
|
72
|
+
raise e if @backtrace
|
73
|
+
exit false
|
74
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Garlic
|
2
|
+
# Configures the garlic runner in a decalarative style
|
3
|
+
class Configurator
|
4
|
+
attr_reader :garlic
|
5
|
+
|
6
|
+
def initialize(garlic, &block)
|
7
|
+
@garlic = garlic
|
8
|
+
instance_eval(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def repo(name, options = {})
|
12
|
+
options[:name] = name
|
13
|
+
options[:path] ||= "#{garlic.repo_path}/#{name}"
|
14
|
+
garlic.repos << Repo.new(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def all_targets(options = {}, &block)
|
18
|
+
BlockParser.new(options, [:prepare, :run], &block) if block_given?
|
19
|
+
garlic.all_targets = options
|
20
|
+
end
|
21
|
+
|
22
|
+
def target(name, options = {}, &block)
|
23
|
+
options[:name] = name
|
24
|
+
options[:path] = "#{garlic.work_path}/#{name}"
|
25
|
+
BlockParser.new(options, [:prepare, :run], &block) if block_given?
|
26
|
+
garlic.targets << Target.new(garlic, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to?(method)
|
30
|
+
super || garlic.respond_to?("#{method}=")
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def method_missing(attribute, value)
|
35
|
+
if garlic.respond_to?("#{attribute}=")
|
36
|
+
garlic.send("#{attribute}=", value)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class BlockParser
|
43
|
+
attr_reader :options, :whitelist
|
44
|
+
|
45
|
+
def initialize(options, whitelist = [], &block)
|
46
|
+
@options = options
|
47
|
+
@whitelist = whitelist
|
48
|
+
instance_eval(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(method, *args, &block)
|
52
|
+
if block_given? && args.empty? && (whitelist.empty? || whitelist.include?(method))
|
53
|
+
options[method.to_sym] = block
|
54
|
+
else
|
55
|
+
raise ArgumentError, "Don't know how to parse #{method} #{args.inspect unless args.empty?}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Garlic
|
2
|
+
# generate a garlic config file
|
3
|
+
module Generator
|
4
|
+
include FileUtils
|
5
|
+
|
6
|
+
TEMPLATES_PATH = File.expand_path("~/.garlic/templates")
|
7
|
+
|
8
|
+
def generate_config(template = 'default', plugin = nil)
|
9
|
+
raise "unknown template: #{template}.\nUse one of #{available_templates.join(', ')} or create your own in #{TEMPLATES_PATH}" unless available_templates.include?(template)
|
10
|
+
plugin ||= File.basename(File.expand_path('.'))
|
11
|
+
puts eval("<<-EOD\n" + File.read(File.join(TEMPLATES_PATH, "#{template}.rb")) + "\nEOD")
|
12
|
+
end
|
13
|
+
|
14
|
+
def available_templates
|
15
|
+
copy_templates unless File.exists?(TEMPLATES_PATH)
|
16
|
+
Dir[File.join(TEMPLATES_PATH, '*')].map {|f| File.basename(f.sub(/.rb$/,'')) }
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def copy_templates
|
21
|
+
mkdir_p TEMPLATES_PATH
|
22
|
+
Dir[File.join(File.dirname(__FILE__), '../../templates/*.rb')].each do |file|
|
23
|
+
cp file, File.join(TEMPLATES_PATH, File.basename(file))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/garlic/repo.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module Garlic
|
2
|
+
# This class represents a local git repo
|
3
|
+
class Repo
|
4
|
+
attr_reader :url, :local, :path, :name
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
if @url = options[:url]
|
8
|
+
@url = File.expand_path(@url) unless options[:url] =~ /^\w+(:|@)/
|
9
|
+
end
|
10
|
+
|
11
|
+
@path = options[:path] or raise ArgumentError, "Repo requires a :path"
|
12
|
+
@path = File.expand_path(@path)
|
13
|
+
|
14
|
+
@local = options[:local]
|
15
|
+
@local = File.expand_path(@local) if @local
|
16
|
+
|
17
|
+
@name = options[:name] || File.basename(@path)
|
18
|
+
end
|
19
|
+
|
20
|
+
class<<self
|
21
|
+
def path?(path)
|
22
|
+
File.directory?(File.join(path, '.git'))
|
23
|
+
end
|
24
|
+
|
25
|
+
def tree_ish(options)
|
26
|
+
[:tree_ish, :tree, :branch, :tag, :commit, :sha].each do |key|
|
27
|
+
return options[key] if options[key]
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def head_sha(path)
|
33
|
+
`cd #{path}; git log HEAD -1 --pretty=format:\"%H\"`
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def install
|
38
|
+
if File.exists?(path)
|
39
|
+
puts "\nSkipping #{name}, as it exists"
|
40
|
+
else
|
41
|
+
puts "\nInstalling #{name}"
|
42
|
+
sh "git clone #{url}#{" --reference #{local}" if local} #{path}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def update
|
47
|
+
puts "\nUpdating #{name}..."
|
48
|
+
if Repo.path?(path)
|
49
|
+
if url
|
50
|
+
begin
|
51
|
+
checkout 'master'
|
52
|
+
cd(path) { sh "git pull origin master", :verbose => false }
|
53
|
+
rescue Exception => e
|
54
|
+
puts "\n\nIt seems there was a problem.\nTry running rake garlic:reset_repos\n\n"
|
55
|
+
raise e
|
56
|
+
end
|
57
|
+
else
|
58
|
+
puts "No url for #{name} given, so not updating"
|
59
|
+
end
|
60
|
+
elsif File.exists?(path)
|
61
|
+
raise "\nRepo #{name} (#{path}) is not a git repo.\nRemove it and run rake garlic:install_repos\n\n"
|
62
|
+
else
|
63
|
+
raise "\nRepo #{name} missing from #{path}.\nPlease run rake garlic:install_repos\n\n"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check
|
68
|
+
if !Repo.path?(path)
|
69
|
+
raise "#{name} is missing from #{path}, or is not a git repo"
|
70
|
+
elsif url && !same_url?(origin_url)
|
71
|
+
raise "#{name}'s url has changed from #{url} to #{origin_url}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset
|
76
|
+
cd(path) { sh "git reset --hard master" }
|
77
|
+
checkout('master')
|
78
|
+
end
|
79
|
+
|
80
|
+
def checkout(tree_ish)
|
81
|
+
cd(path) { sh "git checkout -q #{tree_ish}", :verbose => false }
|
82
|
+
end
|
83
|
+
|
84
|
+
def export_to(export_path)
|
85
|
+
rm_rf export_path; mkdir_p export_path
|
86
|
+
cd(path) do
|
87
|
+
sh "git archive --format=tar HEAD > #{File.join(export_path, "#{name}.tar")}", :verbose => false
|
88
|
+
end
|
89
|
+
cd(export_path) do
|
90
|
+
`tar -x -f #{name}.tar`
|
91
|
+
rm "#{name}.tar"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def clone_to(clone_path)
|
96
|
+
mkdir_p File.dirname(clone_path)
|
97
|
+
cd (File.dirname(clone_path)) do
|
98
|
+
sh "git clone #{path} #{clone_path}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def origin_url
|
103
|
+
unless @origin_url
|
104
|
+
match = `cd #{path}; git remote show -n origin`.match(/URL: (.*)\n/)
|
105
|
+
@origin_url = (match && match[1])
|
106
|
+
end
|
107
|
+
@origin_url
|
108
|
+
end
|
109
|
+
|
110
|
+
def same_url?(url)
|
111
|
+
self.url.sub(/\/?(\.git)?$/, '') == url.sub(/\/?(\.git)?$/, '')
|
112
|
+
end
|
113
|
+
|
114
|
+
def head_sha
|
115
|
+
Repo.head_sha(path)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Garlic
|
2
|
+
# this class runs the top level garlic commands
|
3
|
+
class Runner
|
4
|
+
attr_reader :actor, :run_targets
|
5
|
+
attr_accessor :repos, :targets, :all_targets, :repo_path, :work_path, :verbose
|
6
|
+
|
7
|
+
def initialize(actor = nil, &block)
|
8
|
+
@actor = actor
|
9
|
+
self.repos = []
|
10
|
+
self.targets = []
|
11
|
+
self.all_targets = {}
|
12
|
+
self.work_path = ".garlic"
|
13
|
+
self.repo_path = "~/.garlic/repos"
|
14
|
+
configure(&block) if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure(&block)
|
18
|
+
Configurator.new(self, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def repo(name)
|
22
|
+
repos.detect {|r| r.name == name.to_s} or raise "Can't find repo: #{name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# convert a possible string argument into an array
|
26
|
+
def run_targets=(targets)
|
27
|
+
targets = targets.split(',').map{|t| t.strip} if targets.is_a?(String)
|
28
|
+
@run_targets = targets
|
29
|
+
end
|
30
|
+
|
31
|
+
### garlic commands ###
|
32
|
+
|
33
|
+
# meta data about command methods which can be used by both rake and the cli tool
|
34
|
+
@@commands, @@command_descriptions = [], {}
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def define_command(name, desc, &block)
|
38
|
+
@@commands << name
|
39
|
+
@@command_descriptions[name] = desc
|
40
|
+
define_method name, &block
|
41
|
+
end
|
42
|
+
|
43
|
+
def commands_with_description
|
44
|
+
@@commands.map{|m| [m, @@command_descriptions[m]]}
|
45
|
+
end
|
46
|
+
|
47
|
+
def command_description(name)
|
48
|
+
@@command_descriptions[name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def commands
|
52
|
+
@@command_descriptions.keys.map {|c| c.to_s}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
define_command :default, "Check repos, prepare TARGETs, and run TARGETs" do
|
57
|
+
check_repos
|
58
|
+
prepare
|
59
|
+
run
|
60
|
+
end
|
61
|
+
|
62
|
+
define_command :all, "Install and update all repos, prepare and run TARGETs" do
|
63
|
+
install_repos
|
64
|
+
update_repos
|
65
|
+
prepare
|
66
|
+
run
|
67
|
+
end
|
68
|
+
|
69
|
+
define_command :install_repos, "Install required repositories" do
|
70
|
+
repos.each {|repo| repo.install}
|
71
|
+
end
|
72
|
+
|
73
|
+
define_command :update_repos, "Update repositories" do
|
74
|
+
repos.each {|repo| repo.update}
|
75
|
+
end
|
76
|
+
|
77
|
+
define_command :check_repos, "Check that repos are what they are supposed to be" do
|
78
|
+
errors = []
|
79
|
+
repos.each do |repo|
|
80
|
+
begin
|
81
|
+
repo.check
|
82
|
+
rescue Exception => e
|
83
|
+
errors << "- #{e.message}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
errors.length == 0 or raise "\n#{errors.join("\n")}\n\nPlease remove these repos and run garlic install_repos\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
define_command :reset_repos, "Reset all repos to their master branch" do
|
90
|
+
repos.each {|repo| repo.reset}
|
91
|
+
end
|
92
|
+
|
93
|
+
define_command :clean, "Remove the work done by garlic" do
|
94
|
+
rm_rf work_path
|
95
|
+
end
|
96
|
+
|
97
|
+
define_command :prepare, "Prepare each garlic TARGET" do
|
98
|
+
begin
|
99
|
+
determine_targets.each {|target| target.prepare }
|
100
|
+
ensure
|
101
|
+
repo('rails').checkout('master') # we get rails back to master if something goes wrong
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
define_command :shell, "Run shell commands from stdin across specified targets" do |*path|
|
106
|
+
shell = Shell.new(determine_targets)
|
107
|
+
shell.current_path = path.first if path.first
|
108
|
+
shell.run
|
109
|
+
end
|
110
|
+
|
111
|
+
define_command :run, "Run each garlic TARGET" do
|
112
|
+
these_targets = determine_targets
|
113
|
+
target_names, failed_names = these_targets.map{|t| t.name}, []
|
114
|
+
|
115
|
+
puts "\n#{'='*78}\nTargets: #{target_names.join(', ')}\n#{'='*78}\n"
|
116
|
+
these_targets.each do |target|
|
117
|
+
puts "\n#{'-'*78}\nTarget: #{target.name} (commit #{target.rails_sha[0..6]}, run at #{Time.now})\n#{'-'*78}\n"
|
118
|
+
begin
|
119
|
+
target.run
|
120
|
+
puts "\ntarget: #{target.name} PASS"
|
121
|
+
rescue
|
122
|
+
puts "\ntarget: #{target.name} FAIL"
|
123
|
+
failed_names << target.name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
puts "\n#{'='*78}\n"
|
127
|
+
failed_names.length > 0 and raise "The following targets passed: #{(target_names - failed_names).join(', ')}.\n\nThe following targets FAILED: #{failed_names.join(', ')}.\n\n"
|
128
|
+
puts "All specified targets passed: #{target_names.join(', ')}.\n\n"
|
129
|
+
end
|
130
|
+
|
131
|
+
def respond_to?(method)
|
132
|
+
super(method) || (actor && actor.respond_to?(method))
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
def method_missing(method, *args, &block)
|
137
|
+
actor ? actor.send(method, *args, &block) : super
|
138
|
+
end
|
139
|
+
|
140
|
+
def determine_targets
|
141
|
+
run_targets ? targets.select{|t| run_targets.include?(t.name)} : targets
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/garlic/shell.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
begin
|
3
|
+
require 'term/ansicolor'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
module Garlic
|
8
|
+
class Shell
|
9
|
+
include FileUtils
|
10
|
+
|
11
|
+
attr_reader :current_path
|
12
|
+
|
13
|
+
def initialize(targets)
|
14
|
+
@current_path = '.'
|
15
|
+
@targets = targets
|
16
|
+
raise "Garlic::Shell requires at least one target" if @targets.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_path=(path)
|
20
|
+
if path =~ /^(\/|\~)/
|
21
|
+
STDOUT << red << "#{path}: only relative paths allowed\n" << clear
|
22
|
+
else
|
23
|
+
full_path = File.expand_path(File.join(@targets.first.path, current_path, path))
|
24
|
+
if File.exists?(full_path)
|
25
|
+
@current_path = full_path.include?(@targets.first.path) ? full_path.sub(@targets.first.path,'') : '.'
|
26
|
+
else
|
27
|
+
STDOUT << red << "#{path}: no such directory\n" << clear
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
STDOUT << green << "Garlic interactive session: type shell commands\n" << clear << prompt
|
34
|
+
while (command = STDIN.gets) do
|
35
|
+
command.strip!.empty? || process(command)
|
36
|
+
STDOUT << prompt
|
37
|
+
end
|
38
|
+
rescue Interrupt
|
39
|
+
ensure
|
40
|
+
STDOUT << green << "Garlic interactive session ended\n" << clear
|
41
|
+
end
|
42
|
+
|
43
|
+
def process(command)
|
44
|
+
if command =~ /^cd (.*)$/
|
45
|
+
self.current_path = $1
|
46
|
+
else
|
47
|
+
@targets.each do |target|
|
48
|
+
cd File.join(target.path, current_path) do
|
49
|
+
STDOUT << magenta << target.name + ":\n" << clear
|
50
|
+
system(command) || STDOUT << red << "command failed\n" << clear
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
[:red, :green, :magenta, :clear].each do |colour|
|
58
|
+
define_method colour do
|
59
|
+
Term::ANSIColor.send(colour) rescue ""
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def prompt
|
64
|
+
green + "garlic:#{@current_path.sub(/^\.|\//,'')}> " + clear
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'shell'
|
2
|
+
|
3
|
+
module Garlic
|
4
|
+
class Target
|
5
|
+
attr_reader :garlic, :path, :name, :rails_repo_name, :tree_ish
|
6
|
+
|
7
|
+
def initialize(garlic, options = {})
|
8
|
+
@garlic = garlic
|
9
|
+
@tree_ish = Repo.tree_ish(options) || 'master'
|
10
|
+
@rails_repo_name = options[:rails] || 'rails'
|
11
|
+
@path = options[:path] or raise ArgumentError, "Target requires a :path"
|
12
|
+
@path = File.expand_path(@path)
|
13
|
+
@name = options[:name] || File.basename(@path)
|
14
|
+
@prepare = options[:prepare]
|
15
|
+
@run = options[:run]
|
16
|
+
end
|
17
|
+
|
18
|
+
def prepare
|
19
|
+
puts "\nPreparing target #{name} (#{tree_ish})"
|
20
|
+
install_rails
|
21
|
+
runner.run(&garlic.all_targets[:prepare]) if garlic.all_targets[:prepare]
|
22
|
+
runner.run(&@prepare) if @prepare
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(command = nil)
|
26
|
+
runner.run(&garlic.all_targets[:run]) if garlic.all_targets[:run]
|
27
|
+
runner.run(&@run) if @run
|
28
|
+
end
|
29
|
+
|
30
|
+
def rails_sha
|
31
|
+
read_sha('vendor/rails')
|
32
|
+
end
|
33
|
+
|
34
|
+
def shell
|
35
|
+
unless @shell
|
36
|
+
@shell = Shell.new
|
37
|
+
@shell.verbose = false
|
38
|
+
@shell.cd path
|
39
|
+
end
|
40
|
+
@shell
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def runner
|
45
|
+
@runner ||= Target::Runner.new(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_sha(install_path)
|
49
|
+
File.read(File.join(path, install_path, '.git_sha')) rescue nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_sha(install_path, sha)
|
53
|
+
File.open(File.join(path, install_path, '.git_sha'), 'w+') {|f| f << sha}
|
54
|
+
end
|
55
|
+
|
56
|
+
def install_rails
|
57
|
+
rails_repo = garlic.repo(rails_repo_name)
|
58
|
+
rails_repo.checkout(tree_ish)
|
59
|
+
if File.exists?(path)
|
60
|
+
puts "Rails app for #{name} exists"
|
61
|
+
else
|
62
|
+
puts "Creating rails app for #{name}..."
|
63
|
+
sh "ruby #{rails_repo.path}/railties/bin/rails #{path}", :verbose => false
|
64
|
+
end
|
65
|
+
install_dependency(rails_repo, 'vendor/rails') { sh "rake rails:update" }
|
66
|
+
end
|
67
|
+
|
68
|
+
def install_dependency(repo, install_path = ".", options = {}, &block)
|
69
|
+
repo = garlic.repo(repo) unless repo.is_a?(Repo)
|
70
|
+
tree_ish = Repo.tree_ish(options)
|
71
|
+
|
72
|
+
puts tree_ish
|
73
|
+
|
74
|
+
if options[:clone]
|
75
|
+
if Repo.path?(install_path)
|
76
|
+
puts "#{install_path} exists, and is a repo"
|
77
|
+
cd(install_path) { sh "git fetch origin" }
|
78
|
+
else
|
79
|
+
puts "cloning #{repo.name} to #{install_path}"
|
80
|
+
repo.clone_to(File.join(path, install_path))
|
81
|
+
end
|
82
|
+
cd(install_path) { sh "git checkout #{tree_ish || repo.head_sha}" }
|
83
|
+
|
84
|
+
else
|
85
|
+
if read_sha(install_path) == repo.head_sha
|
86
|
+
puts "#{install_path} is up to date"
|
87
|
+
else
|
88
|
+
puts "#{install_path} needs update, exporting archive from #{repo.name}..."
|
89
|
+
if tree_ish
|
90
|
+
puts "Checking out #{tree_ish} of #{repo.name}"
|
91
|
+
old_tree_ish = repo.head_sha
|
92
|
+
repo.checkout(tree_ish) if tree_ish
|
93
|
+
end
|
94
|
+
|
95
|
+
repo.export_to(File.join(path, install_path))
|
96
|
+
cd(path) { garlic.instance_eval(&block) } if block_given?
|
97
|
+
write_sha(install_path, repo.head_sha)
|
98
|
+
|
99
|
+
if tree_ish
|
100
|
+
puts "Checking #{repo.name} back to where it was (#{old_tree_ish})"
|
101
|
+
repo.checkout(old_tree_ish)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class Runner
|
109
|
+
attr_reader :target
|
110
|
+
|
111
|
+
def initialize(target)
|
112
|
+
@target = target
|
113
|
+
end
|
114
|
+
|
115
|
+
def run(&block)
|
116
|
+
cd target.path do
|
117
|
+
instance_eval(&block)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def method_missing(method, *args, &block)
|
122
|
+
target.garlic.send(method, *args, &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def respond_to?(method)
|
126
|
+
super(method) || target.garlic.respond_to?(method)
|
127
|
+
end
|
128
|
+
|
129
|
+
def plugin(plugin, options = {}, &block)
|
130
|
+
target.send(:install_dependency, plugin, "vendor/plugins/#{options[:as] || plugin}", options, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def dependency(repo, dest, options = {}, &block)
|
134
|
+
target.send(:install_dependency, repo, dest, options, &block)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/garlic/tasks.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'garlic'))
|
2
|
+
|
3
|
+
include Garlic
|
4
|
+
|
5
|
+
# configure the garlic runner
|
6
|
+
garlic ENV['CONFIG'] do
|
7
|
+
verbose ['true', 'yes', '1'].include?(ENV['VERBOSE'])
|
8
|
+
run_targets ENV['TARGETS'] || ENV['TARGET']
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Run garlic:default (CONFIG=path, TARGETS=list, VERBOSE=true)"
|
12
|
+
task :garlic do
|
13
|
+
garlic.default
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :garlic do
|
17
|
+
Garlic::Runner.commands_with_description.each do |command, description|
|
18
|
+
desc description
|
19
|
+
task command do
|
20
|
+
garlic.send command
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/garlic.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "garlic/runner"
|
2
|
+
require "garlic/configurator"
|
3
|
+
require "garlic/repo"
|
4
|
+
require "garlic/target"
|
5
|
+
require "garlic/generator"
|
6
|
+
require "garlic/shell"
|
7
|
+
|
8
|
+
module Garlic
|
9
|
+
include Generator
|
10
|
+
|
11
|
+
module Version
|
12
|
+
Major = 0
|
13
|
+
Minor = 1
|
14
|
+
Tiny = 2
|
15
|
+
|
16
|
+
String = [Major, Minor, Tiny].join('.')
|
17
|
+
end
|
18
|
+
|
19
|
+
# return the current garlic runner
|
20
|
+
def garlic(config = nil, &block)
|
21
|
+
@garlic ||= Garlic::Runner.new(self)
|
22
|
+
load_config(config)
|
23
|
+
@garlic.configure(&block) if block_given?
|
24
|
+
@garlic
|
25
|
+
end
|
26
|
+
|
27
|
+
# load config from
|
28
|
+
def load_config(config = nil)
|
29
|
+
unless @garlic_config_file
|
30
|
+
@garlic_config_file = config || "garlic.rb"
|
31
|
+
unless File.exists?(@garlic_config_file)
|
32
|
+
raise "garlic requries a configuration file (can't find #{@garlic_config_file}), try:\n garlic generate [#{available_templates.join('|')}] > garlic.rb"
|
33
|
+
end
|
34
|
+
eval File.read(@garlic_config_file)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
|
2
|
+
|
3
|
+
describe Garlic::Repo do
|
4
|
+
describe "new(:url => 'some/local/repo', :path => 'some/dest')" do
|
5
|
+
before { @repo = Garlic::Repo.new(:url => 'some/local/repo', :path => 'some/dest') }
|
6
|
+
|
7
|
+
it "should expand the path" do
|
8
|
+
@repo.path.should == File.expand_path('some/dest')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should expand the url" do
|
12
|
+
@repo.url.should == File.expand_path('some/local/repo')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have name == 'dest' (basename of path)" do
|
16
|
+
@repo.name.should == 'dest'
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#install' do
|
20
|
+
before do
|
21
|
+
@repo.stub!(:puts) # silence!
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should 'git clone <repo> <dest>" do
|
25
|
+
@repo.should_receive(:sh).with("git clone #{@repo.url} #{@repo.path}")
|
26
|
+
@repo.install
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "new(:url => 'git://remote/repo', :path => 'some/dest')" do
|
32
|
+
before { @repo = Garlic::Repo.new(:url => 'git://remote/repo', :path => 'some/dest') }
|
33
|
+
|
34
|
+
it "should NOT expand the url" do
|
35
|
+
@repo.url == 'git://remote/repo'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typical vanilla garlic configuration
|
2
|
+
|
3
|
+
garlic do
|
4
|
+
# this plugin
|
5
|
+
repo "#{plugin}", :path => '.'
|
6
|
+
|
7
|
+
# other repos
|
8
|
+
repo "rails", :url => "git://github.com/rails/rails"
|
9
|
+
|
10
|
+
# targets
|
11
|
+
target "edge", :branch => 'origin/master'
|
12
|
+
target "2.1", :branch => "origin/2-1-stable"
|
13
|
+
target "2.0", :branch => "origin/2-0-stable"
|
14
|
+
target "1.2", :branch => "origin/1-2-stable"
|
15
|
+
|
16
|
+
# all targets
|
17
|
+
all_targets do
|
18
|
+
prepare do
|
19
|
+
plugin "#{plugin}", :clone => true # so we can work in targets
|
20
|
+
end
|
21
|
+
|
22
|
+
run do
|
23
|
+
cd "vendor/plugins/#{plugin}" do
|
24
|
+
sh "rake"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/templates/rspec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# typical rspec garlic configuration
|
2
|
+
|
3
|
+
garlic do
|
4
|
+
# this plugin
|
5
|
+
repo "#{plugin}", :path => '.'
|
6
|
+
|
7
|
+
# other repos
|
8
|
+
repo "rails", :url => "git://github.com/rails/rails"
|
9
|
+
repo "rspec", :url => "git://github.com/dchelimsky/rspec"
|
10
|
+
repo "rspec-rails", :url => "git://github.com/dchelimsky/rspec-rails"
|
11
|
+
|
12
|
+
# targets
|
13
|
+
target "edge", :branch => 'origin/master'
|
14
|
+
target "2.1", :branch => "origin/2-1-stable"
|
15
|
+
target "2.0", :branch => "origin/2-0-stable"
|
16
|
+
target "1.2", :branch => "origin/1-2-stable"
|
17
|
+
|
18
|
+
# all targets
|
19
|
+
all_targets do
|
20
|
+
prepare do
|
21
|
+
plugin "#{plugin}", :clone => true # so we can work in targets
|
22
|
+
plugin "rspec"
|
23
|
+
plugin "rspec-rails" do
|
24
|
+
sh "script/generate rspec -f"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
run do
|
29
|
+
cd "vendor/plugins/#{plugin}" do
|
30
|
+
sh "rake"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# typical shoulda garlic configuration
|
2
|
+
|
3
|
+
garlic do
|
4
|
+
# this plugin
|
5
|
+
repo "#{plugin}", :path => '.'
|
6
|
+
|
7
|
+
# other repos
|
8
|
+
repo "rails", :url => "git://github.com/rails/rails"
|
9
|
+
repo "shoulda", :url => "git://github.com/thoughtbot/shoulda"
|
10
|
+
|
11
|
+
# targets
|
12
|
+
target "edge", :branch => 'origin/master'
|
13
|
+
target "2.1", :branch => "origin/2-1-stable"
|
14
|
+
target "2.0", :branch => "origin/2-0-stable"
|
15
|
+
target "1.2", :branch => "origin/1-2-stable"
|
16
|
+
|
17
|
+
# all targets
|
18
|
+
all_targets do
|
19
|
+
prepare do
|
20
|
+
plugin "#{plugin}", :clone => true # so we can work in targets
|
21
|
+
plugin "shoulda"
|
22
|
+
end
|
23
|
+
|
24
|
+
run do
|
25
|
+
cd "vendor/plugins/#{plugin}" do
|
26
|
+
sh "rake"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ianwhite-garlic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ian White
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-02 00:00:00 -07:00
|
13
|
+
default_executable: garlic
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Lightweight set of rake tasks to help with CI.
|
17
|
+
email: ian.w.white@gmail.com
|
18
|
+
executables:
|
19
|
+
- garlic
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/garlic/configurator.rb
|
26
|
+
- lib/garlic/generator.rb
|
27
|
+
- lib/garlic/repo.rb
|
28
|
+
- lib/garlic/runner.rb
|
29
|
+
- lib/garlic/shell.rb
|
30
|
+
- lib/garlic/target.rb
|
31
|
+
- lib/garlic/tasks.rb
|
32
|
+
- lib/garlic.rb
|
33
|
+
- templates/default.rb
|
34
|
+
- templates/rspec.rb
|
35
|
+
- templates/shoulda.rb
|
36
|
+
- MIT-LICENSE
|
37
|
+
- README.textile
|
38
|
+
- TODO
|
39
|
+
- CHANGELOG
|
40
|
+
- spec/garlic/repo_spec.rb
|
41
|
+
- bin/garlic
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/ianwhite/garlic/tree
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --title
|
47
|
+
- Garlic
|
48
|
+
- --line-numbers
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.2.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: Lightweight set of rake tasks to help with CI.
|
70
|
+
test_files:
|
71
|
+
- spec/garlic/repo_spec.rb
|