ianwhite-garlic 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|