garlic 0.1.10

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