garlic 0.1.10

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.
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ garlic-*.gem
3
+ doc
@@ -0,0 +1,64 @@
1
+ == 0.1.10
2
+
3
+ * 2 minor enhancements
4
+ * updated gemspec for gemcutter and rubyforge
5
+ * templates now include rails 2.3
6
+
7
+
8
+ == 0.1.9
9
+
10
+ * 1 major enhancement
11
+ * added garlic shell commands (sh/garlic.sh) for easing the garlic workflow
12
+ to use them, add this to your ~/.profile
13
+
14
+ source `garlic --path`/sh/garlic.sh
15
+
16
+ == 0.1.8
17
+
18
+ * 1 bugfix
19
+ * fix problem where dependencies weren't checking out repo tags or branches properly
20
+
21
+ == 0.1.7
22
+
23
+ * 2 minor enhancement
24
+ * Fix problem whereby garlic update_repos wasn't updating repos correctly
25
+ * utilise Garlic::Generator in tabtab definition
26
+
27
+ == 0.1.6 2008-11-27
28
+
29
+ * 3 minor enhancements
30
+ * Updated Readme/TODO
31
+ * Some crufty output is now suppressed/removed
32
+ * garlic all command cleans the work path
33
+
34
+ * 1 bugfix
35
+ * --targets, -t option now actually works
36
+
37
+ == 0.1.5 2008-11-25
38
+
39
+ * 4 minor enhancements
40
+ * Removed 'all_targets' - just use ruby to DRY up garlic.rb
41
+ * Changed templates wrt above
42
+ * Better tabtab completions
43
+ * Updated TODO
44
+
45
+ == 0.1.4 2008-11-20
46
+
47
+ * 1 minor enhancement
48
+ * Added tabtab definitions that are in the right place
49
+
50
+ == 0.1.3 2008-11-20
51
+
52
+ * 1 minor enhancement
53
+ * Added tabtab definitions
54
+
55
+ == 0.1.2
56
+
57
+ * 2 major enhancements
58
+ * garlic CLI
59
+ * repos are now stored in ~/.garlic/repos (and this shared across multiple garlic sessions), work is in ./.garlic
60
+
61
+ == 0.1.1
62
+
63
+ * 1 major enhancement:
64
+ * freelancing-god added gem goodness
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 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.
@@ -0,0 +1,143 @@
1
+ = garlic
2
+
3
+ <b>lightweight continuous integration for rails using git</b>
4
+
5
+ This is not a CI server, use cruisecontrol.rb or integrity for that. This is a simple set
6
+ of commands (or rake tasks) that let you specify a bunch of rails builds to run against, and
7
+ dependencies to install.
8
+
9
+ This is aimed at testing plugins (or apps) against multiple versions of rails,
10
+ and allows specifying other plugin dependencies (and their versions and any
11
+ setup requried).
12
+
13
+ If you want to run your specs (or whatever) against different versions of gems
14
+ that you have installed, then check out {ginger}[http://github.com/freelancing-god/ginger] by {Pat Allen}[http://github.com/freelancing-god]
15
+
16
+ Garlic works by cloning git repos for all your dependencies (so they all must be
17
+ git repos), and then using git to checkout various tags and branches to build
18
+ your app against.
19
+
20
+ Here's an example of running a plugin against 3 different rails verisons and 3 different rspec versions: {gist 28786}[http://gist.github.com/28786]
21
+
22
+ == It's still new, and not shiny yet
23
+
24
+ Please feel free to make it shinier. I'm successfully using it on most of my plugins, and I test
25
+ with rspec and cucumber.
26
+
27
+ Check out the {TODO LIST}[http://github.com/ianwhite/garlic/tree/master/Todo.txt]
28
+
29
+ == Get up and running quickly
30
+
31
+ You have a plugin and you want it tested against different versions of rails?
32
+
33
+ <b>1.</b> install garlic as a gem (see below)
34
+
35
+ <b>2.</b> cd into your (say, rspec tested) plugin directory
36
+
37
+ garlic generate rspec > garlic.rb
38
+ garlic install_repos
39
+ garlic
40
+
41
+ <b>3.</b> See what happens, edit garlic.rb to change rails versions and other stuff.
42
+
43
+ garlic --help # will probably help
44
+
45
+ == Installing
46
+
47
+ Install the garlic gem
48
+
49
+ # from rubyforge or gemcutter
50
+ sudo gem install garlic
51
+
52
+ # from github
53
+ sudo gem install ianwhite-garlic --source=http://gems.github.com
54
+
55
+ (if you want the very latest version)
56
+
57
+ git clone git://github.com/ianwhite/garlic
58
+ cd garlic
59
+ rake package
60
+ sudo gem install pkg/garlic-<code>*</code>.gem
61
+
62
+ == Example
63
+
64
+ To see garlic in action, download resources_controller_, a rails plugin that uses garlic for CI.
65
+
66
+ git clone git://github.com/ianwhite/resources_controller
67
+
68
+
69
+ run garlic
70
+
71
+ garlic all
72
+
73
+ This will clone all the required git repos (done only once), set up the target railses (done once), then run the targets.
74
+
75
+ === Once you've committed some changes
76
+
77
+ You can prepare and run all the targets again (without fetching remote repos) by doing
78
+
79
+ garlic
80
+
81
+ This will prepare all the targets, using the current HEAD of the repos, and run the
82
+ CI task again.
83
+
84
+ === Specifying particular targets
85
+
86
+ If you just want to run against a particular target or targets, you can use the -t option,
87
+ or if using Rake, the TARGET or TARGETS env var.
88
+
89
+ garlic -t edge
90
+
91
+ == Running Shell commands across multiple targets
92
+
93
+ Check dis out
94
+
95
+ garlic shell # {Example output}[http://gist.github.com/28795]
96
+
97
+ You can pipe any thing into garlic shell and it will execute across all of your garlic targets
98
+
99
+ == Rake tasks
100
+
101
+ If you prefer to use garlic via rake tasks, then just require 'garlic/tasks' and you'll get a bunch of em.
102
+ Once required, do rake -T to see descriptions.
103
+
104
+ == garlic workflow shell commands
105
+
106
+ If you add the following line to your .profile
107
+
108
+ source `garlic --path`/sh/garlic.sh
109
+
110
+ Then you'll get these 4 new shell commands:
111
+
112
+ gcd [target] cds into the specified target working repo
113
+ gcdp [target] cds into the specified target plugin in the working repo
114
+ gup cds back up to the garlic'd repo from within a working path
115
+ gpush [branch] from within a working repo, pushes changes back to the local garlic target, and resets
116
+ local changes in that target to HEAD.
117
+
118
+ This means you might have a workflow as follows (example is for a plugin):
119
+
120
+ # run garlic, see probs in '2-2-stable'
121
+
122
+ gcdp 2-2 # => takes you into the working repo in the '2-2-stable' target
123
+
124
+ # fix the changes, make some commits
125
+
126
+ gpush # => pushes the changes back to the enclosing garlic'd repo
127
+ gup # => go back up there
128
+ garlic # => rerun garlic to see how the changes affect the other targets
129
+
130
+ == Lend a hand
131
+
132
+ This is an early release, so there is plenty of scope for changes and improvement
133
+ If you want to lend a hand, get in touch.
134
+
135
+ (c) Ian White 2008-2009 - ian.w.white@gmail.com
136
+ MIT Licence
137
+
138
+ == Lent a hand
139
+
140
+ Thanks very much to:
141
+
142
+ * Pat Allan
143
+ * Dr Nic Williams (API suggestions)
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+ require 'rake/gempackagetask'
6
+
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
8
+
9
+ require 'garlic'
10
+
11
+ begin
12
+ require 'jeweler'
13
+
14
+ Jeweler::Tasks.new do |s|
15
+ s.name = "garlic"
16
+ s.version = Garlic::Version::String
17
+ s.summary = "Test your project across multiple versions of rails/dependencies"
18
+ s.description = "CI tool to test your project across multiple versions of rails/dependencies"
19
+ s.email = "ian.w.white@gmail.com"
20
+ s.homepage = "http://github.com/ianwhite/garlic"
21
+ s.authors = ["Ian White"]
22
+ s.rubyforge_project = 'garlic'
23
+ end
24
+
25
+ Jeweler::GemcutterTasks.new
26
+
27
+ Jeweler::RubyforgeTasks.new do |rubyforge|
28
+ rubyforge.doc_task = "doc"
29
+ end
30
+
31
+ namespace :release do
32
+ desc "Release current version to github, gemcutter and rubyforge"
33
+ task :all => ['release', 'gemcutter:release', 'rubyforge:release']
34
+ end
35
+
36
+ rescue LoadError
37
+ puts "Jeweler not available for gem tasks. Install it with: sudo gem install jeweler"
38
+ end
39
+
40
+ begin
41
+ require 'hanna/rdoctask'
42
+ rescue LoadError
43
+ end
44
+
45
+ Rake::RDocTask.new(:doc) do |d|
46
+ d.options << '--all'
47
+ d.rdoc_dir = 'doc'
48
+ d.main = 'README.rdoc'
49
+ d.title = "garlic API Docs"
50
+ d.rdoc_files.include('README.rdoc', 'History.txt', 'License.txt', 'Todo.txt', 'VERSION', 'lib/**/*.rb')
51
+ end
52
+ task :rdoc => :doc
53
+
54
+ Spec::Rake::SpecTask.new(:spec) do |t|
55
+ t.warning = true
56
+ end
57
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ * add 'gem' command - should it freeze the gem in to vendor/gems?
2
+ * BETTER OUTPUT - even less uninteresting cruft in the output please
3
+ * specs!
4
+ * cucumber features!
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.10
@@ -0,0 +1,80 @@
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::Session.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
+ --backtrace Show ruby backtrace on error
27
+ --targets TARGETS -t Specify subset of targets comma separated or
28
+ regexp part e.g. -t 2-1,2-2
29
+
30
+ You can generate a sample garlic.rb with
31
+ garlic generate [TEMPLATE [PLUGIN_NAME]] (Available templates: #{available_templates.join(', ')})
32
+
33
+ end_doc
34
+
35
+ @verbose = false
36
+
37
+ GetoptLong.new(
38
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
39
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
40
+ ['--targets', '-t', GetoptLong::REQUIRED_ARGUMENT],
41
+ ['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],
42
+ ['--backtrace', GetoptLong::NO_ARGUMENT],
43
+ ['--path', GetoptLong::NO_ARGUMENT],
44
+ ['--version', GetoptLong::NO_ARGUMENT]
45
+ ).each do |opt, arg|
46
+ case opt
47
+ when '--help' then STDOUT << HELP; exit true
48
+ when '--path' then puts File.dirname(File.dirname(File.expand_path(__FILE__))); exit true
49
+ when '--version' then puts Garlic::Version::String; exit true
50
+ when '--verbose' then @verbose = true
51
+ when '--config' then @config_file = arg
52
+ when '--targets' then @run_targets = arg
53
+ when '--backtrace' then @backtrace = true
54
+ end
55
+ end
56
+
57
+ begin
58
+ if ARGV.first == 'generate'
59
+ generate_config(*ARGV[1..-1])
60
+ else
61
+ # run a garlic command
62
+ raise "Unknown command: #{ARGV.first}" unless ARGV.empty? || Garlic::Session.commands.include?(ARGV.first)
63
+
64
+ verbose(@verbose)
65
+
66
+ # configure the garlic runner
67
+ garlic(@config_file) # load up the garlic instance
68
+ garlic.verbose = @verbose
69
+ garlic.run_targets = @run_targets
70
+
71
+ # run the command
72
+ ARGV << 'default' if ARGV.empty?
73
+ garlic.send *ARGV
74
+ end
75
+
76
+ rescue Exception => e
77
+ STDERR << "\n#{USAGE}\n\nError: #{e.message}\n\n"
78
+ raise e if @backtrace
79
+ exit false
80
+ end
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{garlic}
8
+ s.version = "0.1.10"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ian White"]
12
+ s.date = %q{2009-10-05}
13
+ s.default_executable = %q{garlic}
14
+ s.description = %q{CI tool to test your project across multiple versions of rails/dependencies}
15
+ s.email = %q{ian.w.white@gmail.com}
16
+ s.executables = ["garlic"]
17
+ s.extra_rdoc_files = [
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "History.txt",
23
+ "License.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "Todo.txt",
27
+ "VERSION",
28
+ "bin/garlic",
29
+ "garlic.gemspec",
30
+ "lib/garlic.rb",
31
+ "lib/garlic/configurator.rb",
32
+ "lib/garlic/generator.rb",
33
+ "lib/garlic/repo.rb",
34
+ "lib/garlic/session.rb",
35
+ "lib/garlic/shell.rb",
36
+ "lib/garlic/target.rb",
37
+ "lib/garlic/tasks.rb",
38
+ "lib/tabtab_definitions/garlic.rb",
39
+ "sh/garlic.sh",
40
+ "spec/garlic/repo_spec.rb",
41
+ "spec/spec_helper.rb",
42
+ "templates/default.rb",
43
+ "templates/rspec.rb",
44
+ "templates/shoulda.rb"
45
+ ]
46
+ s.homepage = %q{http://github.com/ianwhite/garlic}
47
+ s.rdoc_options = ["--charset=UTF-8"]
48
+ s.require_paths = ["lib"]
49
+ s.rubyforge_project = %q{garlic}
50
+ s.rubygems_version = %q{1.3.4}
51
+ s.summary = %q{Test your project across multiple versions of rails/dependencies}
52
+ s.test_files = [
53
+ "spec/garlic/repo_spec.rb",
54
+ "spec/spec_helper.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
62
+ else
63
+ end
64
+ else
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ require "garlic/session"
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
+ String = File.read(File.dirname(File.dirname(__FILE__)) + '/VERSION').strip
13
+ Major, Minor, Patch = String.split('.').map{|i| i.to_i}
14
+ end
15
+
16
+ # return the current garlic session
17
+ def garlic(config = nil, &block)
18
+ @garlic ||= Garlic::Session.new(self)
19
+ load_config(config)
20
+ @garlic.configure(&block) if block_given?
21
+ @garlic
22
+ end
23
+
24
+ # load config from
25
+ def load_config(config = nil)
26
+ unless @garlic_config_file
27
+ @garlic_config_file = config || "garlic.rb"
28
+ unless File.exists?(@garlic_config_file)
29
+ raise "garlic requries a configuration file (can't find #{@garlic_config_file}), try:\n garlic generate [#{available_templates.join('|')}] > garlic.rb"
30
+ end
31
+ eval File.read(@garlic_config_file)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
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 target(name, options = {}, &block)
18
+ options[:name] = name
19
+ options[:path] = "#{garlic.work_path}/#{name_to_path(name)}"
20
+ BlockParser.new(options, [:prepare, :run], &block) if block_given?
21
+ garlic.targets << Target.new(garlic, options)
22
+ end
23
+
24
+ def respond_to?(method)
25
+ super || garlic.respond_to?("#{method}=")
26
+ end
27
+
28
+ protected
29
+ def name_to_path(name)
30
+ name.gsub(/[^\w\d_.-]/,'_').downcase
31
+ end
32
+
33
+ def method_missing(attribute, value)
34
+ if garlic.respond_to?("#{attribute}=")
35
+ garlic.send("#{attribute}=", value)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ class BlockParser
42
+ attr_reader :options, :whitelist
43
+
44
+ def initialize(options, whitelist = [], &block)
45
+ @options = options
46
+ @whitelist = whitelist
47
+ instance_eval(&block)
48
+ end
49
+
50
+ def method_missing(method, *args, &block)
51
+ if block_given? && args.empty? && (whitelist.empty? || whitelist.include?(method))
52
+ options[method.to_sym] = block
53
+ else
54
+ raise ArgumentError, "Don't know how to parse #{method} #{args.inspect unless args.empty?}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ require 'fileutils'
2
+
3
+ module Garlic
4
+ # generate a garlic config file
5
+ module Generator
6
+ include FileUtils
7
+
8
+ TEMPLATES_PATH = File.expand_path("~/.garlic/templates")
9
+
10
+ def generate_config(template = 'default', plugin = nil)
11
+ raise "unknown template: #{template}.\nUse one of #{available_templates.join(', ')} or create your own in #{TEMPLATES_PATH}" unless available_templates.include?(template)
12
+ plugin ||= File.basename(File.expand_path('.'))
13
+ puts eval("<<-EOD\n" + File.read(File.join(TEMPLATES_PATH, "#{template}.rb")) + "\nEOD")
14
+ end
15
+
16
+ def available_templates
17
+ copy_templates unless File.exists?(TEMPLATES_PATH)
18
+ Dir[File.join(TEMPLATES_PATH, '*')].map {|f| File.basename(f.sub(/.rb$/,'')) }
19
+ end
20
+
21
+ protected
22
+ def copy_templates
23
+ mkdir_p TEMPLATES_PATH, :verbose => false
24
+ Dir[File.join(File.dirname(__FILE__), '../../templates/*.rb')].each do |file|
25
+ cp file, File.join(TEMPLATES_PATH, File.basename(file)), :verbose => false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,121 @@
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) do
53
+ sh "git fetch origin", :verbose => false
54
+ sh "git pull", :verbose => false
55
+ end
56
+ rescue Exception => e
57
+ puts "\n\nIt seems there was a problem.\nTry running rake garlic:reset_repos\n\n"
58
+ raise e
59
+ end
60
+ else
61
+ puts "No url for #{name} given, so not updating"
62
+ end
63
+ elsif File.exists?(path)
64
+ raise "\nRepo #{name} (#{path}) is not a git repo.\nRemove it and run rake garlic:install_repos\n\n"
65
+ else
66
+ raise "\nRepo #{name} missing from #{path}.\nPlease run rake garlic:install_repos\n\n"
67
+ end
68
+ end
69
+
70
+ def check
71
+ if !Repo.path?(path)
72
+ raise "#{name} is missing from #{path}, or is not a git repo"
73
+ elsif url && !same_url?(origin_url)
74
+ raise "#{name}'s url has changed from #{url} to #{origin_url}"
75
+ end
76
+ end
77
+
78
+ def reset
79
+ cd(path) { sh "git reset --hard master" }
80
+ checkout('master')
81
+ end
82
+
83
+ def checkout(tree_ish)
84
+ cd(path) { sh "git checkout -q #{tree_ish}", :verbose => false }
85
+ end
86
+
87
+ def export_to(export_path)
88
+ rm_rf export_path; mkdir_p export_path
89
+ cd(path) do
90
+ sh "git archive --format=tar HEAD > #{File.join(export_path, "#{name}.tar")}", :verbose => false
91
+ end
92
+ cd(export_path) do
93
+ `tar -x -f #{name}.tar`
94
+ rm "#{name}.tar"
95
+ end
96
+ end
97
+
98
+ def clone_to(clone_path)
99
+ mkdir_p File.dirname(clone_path)
100
+ cd (File.dirname(clone_path)) do
101
+ sh "git clone #{path} #{clone_path}"
102
+ end
103
+ end
104
+
105
+ def origin_url
106
+ unless @origin_url
107
+ match = `cd #{path}; git remote show -n origin`.match(/URL: (.*)\n/)
108
+ @origin_url = (match && match[1])
109
+ end
110
+ @origin_url
111
+ end
112
+
113
+ def same_url?(url)
114
+ self.url.sub(/\/?(\.git)?$/, '') == url.sub(/\/?(\.git)?$/, '')
115
+ end
116
+
117
+ def head_sha
118
+ Repo.head_sha(path)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,145 @@
1
+ module Garlic
2
+ # this class runs the top level garlic commands
3
+ class Session
4
+ attr_reader :actor, :run_targets
5
+ attr_accessor :repos, :targets, :repo_path, :work_path, :verbose
6
+
7
+ def initialize(actor = nil, &block)
8
+ @actor = actor
9
+ self.repos = []
10
+ self.targets = []
11
+ self.work_path = ".garlic"
12
+ self.repo_path = "~/.garlic/repos"
13
+ configure(&block) if block_given?
14
+ end
15
+
16
+ def configure(&block)
17
+ Configurator.new(self, &block)
18
+ end
19
+
20
+ def repo(name)
21
+ repos.detect {|r| r.name == name.to_s} or raise "Can't find repo: #{name}"
22
+ end
23
+
24
+ # convert a possible string argument into an array
25
+ def run_targets=(targets)
26
+ targets = targets.split(',').map{|t| t.strip} if targets.is_a?(String)
27
+ @run_targets = targets
28
+ end
29
+
30
+ ### garlic commands ###
31
+
32
+ # meta data about command methods which can be used by both rake and the cli tool
33
+ @@commands, @@command_descriptions = [], {}
34
+
35
+ class << self
36
+ def define_command(name, desc, &block)
37
+ @@commands << name
38
+ @@command_descriptions[name] = desc
39
+ define_method name, &block
40
+ end
41
+
42
+ def commands_with_description
43
+ @@commands.map{|m| [m, @@command_descriptions[m]]}
44
+ end
45
+
46
+ def command_description(name)
47
+ @@command_descriptions[name]
48
+ end
49
+
50
+ def commands
51
+ @@command_descriptions.keys.map {|c| c.to_s}
52
+ end
53
+ end
54
+
55
+ define_command :default, "Check repos, prepare TARGETs, and run TARGETs" do
56
+ check_repos
57
+ prepare
58
+ run
59
+ end
60
+
61
+ define_command :all, "Install and update all repos, prepare and run TARGETs" do
62
+ install_repos
63
+ update_repos
64
+ clean
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
+ determine_targets.each {|target| target.prepare }
99
+ end
100
+
101
+ define_command :shell, "Run shell commands from stdin across specified targets" do |*path|
102
+ shell = Shell.new(determine_targets)
103
+ shell.current_path = path.first if path.first
104
+ shell.run
105
+ end
106
+
107
+ define_command :path, "return the work dir for the specified target" do |*path_target|
108
+ self.run_targets = path_target.first if path_target.any?
109
+ puts determine_targets.first.path
110
+ end
111
+
112
+ define_command :run, "Run each garlic TARGET" do
113
+ these_targets = determine_targets
114
+ target_names, failed_names = these_targets.map{|t| t.name}, []
115
+
116
+ puts "\n#{'='*78}\nTargets: #{target_names.join(', ')}\n#{'='*78}\n"
117
+ these_targets.each do |target|
118
+ puts "\n#{'-'*78}\nTarget: #{target.name} (commit #{target.rails_sha[0..6]}, run at #{Time.now})\n#{'-'*78}\n"
119
+ begin
120
+ target.run
121
+ puts "\ntarget: #{target.name} PASS"
122
+ rescue
123
+ puts "\ntarget: #{target.name} FAIL"
124
+ failed_names << target.name
125
+ end
126
+ end
127
+ puts "\n#{'='*78}\n"
128
+ 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"
129
+ puts "All specified targets passed: #{target_names.join(', ')}.\n\n"
130
+ end
131
+
132
+ def respond_to?(method)
133
+ super(method) || (actor && actor.respond_to?(method))
134
+ end
135
+
136
+ protected
137
+ def method_missing(method, *args, &block)
138
+ actor ? actor.send(method, *args, &block) : super
139
+ end
140
+
141
+ def determine_targets
142
+ run_targets ? targets.select{|t| t.name =~ /(?:#{run_targets.join("|")})/i} : targets
143
+ end
144
+ end
145
+ 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,126 @@
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) || 'origin/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(&@prepare) if @prepare
22
+ end
23
+
24
+ def run
25
+ runner.run(&@run) if @run
26
+ end
27
+
28
+ def rails_sha
29
+ read_sha('vendor/rails')
30
+ end
31
+
32
+ def shell
33
+ unless @shell
34
+ @shell = Shell.new
35
+ @shell.verbose = false
36
+ @shell.cd path
37
+ end
38
+ @shell
39
+ end
40
+
41
+ private
42
+ def runner
43
+ @runner ||= Target::Runner.new(self)
44
+ end
45
+
46
+ def read_sha(install_path)
47
+ File.read(File.join(path, install_path, '.git_sha')) rescue nil
48
+ end
49
+
50
+ def write_sha(install_path, sha)
51
+ File.open(File.join(path, install_path, '.git_sha'), 'w+') {|f| f << sha}
52
+ end
53
+
54
+ def install_rails
55
+ rails_repo = garlic.repo(rails_repo_name)
56
+ rails_repo.checkout(tree_ish)
57
+ if File.exists?(path)
58
+ puts "Rails app for #{name} exists"
59
+ else
60
+ puts "Creating rails app for #{name}..."
61
+ `ruby #{rails_repo.path}/railties/bin/rails #{path}`
62
+ end
63
+ install_dependency(rails_repo, 'vendor/rails') { `rake rails:update` }
64
+ end
65
+
66
+ def install_dependency(repo, install_path = ".", options = {}, &block)
67
+ repo = garlic.repo(repo) unless repo.is_a?(Repo)
68
+ tree_ish = Repo.tree_ish(options)
69
+
70
+ if options[:clone]
71
+ if Repo.path?(install_path)
72
+ puts "#{install_path} exists, and is a repo"
73
+ cd(install_path) { `git fetch origin` }
74
+ else
75
+ puts "cloning #{repo.name} to #{install_path}"
76
+ repo.clone_to(File.join(path, install_path))
77
+ end
78
+ cd(install_path) { `git checkout #{tree_ish || repo.head_sha}` }
79
+
80
+ else
81
+ old_tree_ish = repo.head_sha
82
+ repo.checkout(tree_ish) if tree_ish
83
+ if read_sha(install_path) == repo.head_sha
84
+ puts "#{install_path} is up to date at #{tree_ish || repo.head_sha[0..6]}"
85
+ else
86
+ puts "#{install_path} needs update to #{tree_ish || repo.head_sha[0..6]}, exporting archive from #{repo.name}..."
87
+ repo.export_to(File.join(path, install_path))
88
+ cd(path) { garlic.instance_eval(&block) } if block_given?
89
+ write_sha(install_path, repo.head_sha)
90
+ end
91
+ repo.checkout(old_tree_ish) if tree_ish
92
+ end
93
+ end
94
+
95
+
96
+ class Runner
97
+ attr_reader :target
98
+
99
+ def initialize(target)
100
+ @target = target
101
+ end
102
+
103
+ def run(&block)
104
+ cd target.path do
105
+ instance_eval(&block)
106
+ end
107
+ end
108
+
109
+ def method_missing(method, *args, &block)
110
+ target.garlic.send(method, *args, &block)
111
+ end
112
+
113
+ def respond_to?(method)
114
+ super(method) || target.garlic.respond_to?(method)
115
+ end
116
+
117
+ def plugin(plugin, options = {}, &block)
118
+ target.send(:install_dependency, plugin, "vendor/plugins/#{options[:as] || plugin}", options, &block)
119
+ end
120
+
121
+ def dependency(repo, dest, options = {}, &block)
122
+ target.send(:install_dependency, repo, dest, options, &block)
123
+ end
124
+ end
125
+ end
126
+ 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::Session.commands_with_description.each do |command, description|
18
+ desc description
19
+ task command do
20
+ garlic.send command
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require "garlic/session"
2
+ require "garlic/generator"
3
+
4
+ TabTab::Definition.register('garlic', :import => '--help') do |g|
5
+ Garlic::Session.commands.each do |c|
6
+ g.command c
7
+ end
8
+
9
+ g.command :generate do
10
+ class << self
11
+ include Garlic::Generator
12
+ end
13
+ available_templates
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # garlic shell helpers
2
+
3
+ # cd into the work path of a garlic target
4
+ gcd ()
5
+ {
6
+ cd `garlic path $1`
7
+ }
8
+
9
+ # cd into probable plugin dir of a garlic target
10
+ gcdp ()
11
+ {
12
+ here=`pwd | sed 's/.*\///'`
13
+ cd `garlic path $1`/vendor/plugins/$here
14
+ }
15
+
16
+ # cd back up to enclosing garlic project
17
+ gup ()
18
+ {
19
+ cd `pwd | sed 's/\.garlic.*//'`
20
+ }
21
+
22
+ # push changes back to local garlic origin, resetting the origin
23
+ gpush ()
24
+ {
25
+ if [ `pwd | sed 's/\.garlic//'` == `pwd` ]; then
26
+ echo "gpush can only be used in a garlic work repo";
27
+ else
28
+ git push origin $1 2>&1 | grep -v warning;
29
+ here=`pwd`;
30
+ gup;
31
+ git reset --hard;
32
+ cd $here;
33
+ fi
34
+ }
@@ -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,2 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'garlic'
@@ -0,0 +1,26 @@
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
+ # target railses
11
+ ['master', '2-3-stable', '2-2-stable', '2-1-stable', '2-0-stable'].each do |rails|
12
+
13
+ # declare how to prepare, and run each CI target
14
+ target rails, :tree_ish => "origin/#{rails}" do
15
+ prepare do
16
+ plugin "#{plugin}", :clone => true # so we can work in targets
17
+ end
18
+
19
+ run do
20
+ cd "vendor/plugins/#{plugin}" do
21
+ sh "rake"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
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
+ # target railses
13
+ ['master', '2-3-stable', '2-2-stable', '2-1-stable', '2-0-stable'].each do |rails|
14
+
15
+ # declare how to prepare, and run each CI target
16
+ target rails, :tree_ish => "origin/#{rails}" do
17
+ prepare do
18
+ plugin "#{plugin}", :clone => true # so we can work in targets
19
+ plugin "rspec"
20
+ plugin "rspec-rails" do
21
+ `script/generate rspec -f`
22
+ end
23
+ end
24
+
25
+ run do
26
+ cd "vendor/plugins/#{plugin}" do
27
+ sh "rake"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
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
+ # target railses
12
+ ['master', '2-3-stable', '2-2-stable', '2-1-stable', '2-0-stable'].each do |rails|
13
+
14
+ # declare how to prepare, and run each CI target
15
+ target rails, :tree_ish => "origin/#{rails}" do
16
+ prepare do
17
+ plugin "#{plugin}", :clone => true # so we can work in targets
18
+ plugin "shoulda"
19
+ end
20
+
21
+ run do
22
+ cd "vendor/plugins/#{plugin}" do
23
+ sh "rake"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garlic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.10
5
+ platform: ruby
6
+ authors:
7
+ - Ian White
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-05 00:00:00 -04:00
13
+ default_executable: garlic
14
+ dependencies: []
15
+
16
+ description: CI tool to test your project across multiple versions of rails/dependencies
17
+ email: ian.w.white@gmail.com
18
+ executables:
19
+ - garlic
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - History.txt
27
+ - License.txt
28
+ - README.rdoc
29
+ - Rakefile
30
+ - Todo.txt
31
+ - VERSION
32
+ - bin/garlic
33
+ - garlic.gemspec
34
+ - lib/garlic.rb
35
+ - lib/garlic/configurator.rb
36
+ - lib/garlic/generator.rb
37
+ - lib/garlic/repo.rb
38
+ - lib/garlic/session.rb
39
+ - lib/garlic/shell.rb
40
+ - lib/garlic/target.rb
41
+ - lib/garlic/tasks.rb
42
+ - lib/tabtab_definitions/garlic.rb
43
+ - sh/garlic.sh
44
+ - spec/garlic/repo_spec.rb
45
+ - spec/spec_helper.rb
46
+ - templates/default.rb
47
+ - templates/rspec.rb
48
+ - templates/shoulda.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/ianwhite/garlic
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project: garlic
73
+ rubygems_version: 1.3.4
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Test your project across multiple versions of rails/dependencies
77
+ test_files:
78
+ - spec/garlic/repo_spec.rb
79
+ - spec/spec_helper.rb