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 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