ianwhite-garlic 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ * 0.1.2
2
+ * making use of garlic as a gem
3
+ * changed paths, repos are now stored in ~/.garlic/repos (and this shared across multiple garlic sessions), work is in .garlic_work
4
+
5
+ * 0.1.1 freelancing-god added gem goodness
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
@@ -0,0 +1,2 @@
1
+ * specs
2
+ * rewrite using grit? (research: is grit the defacto standard for ruby controlling git?)
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
@@ -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
@@ -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
@@ -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
@@ -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