garlic_rails3_compatibility 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,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,56 @@
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_rails3_compatibility"
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 = "scott@railsnewbie.com"
20
+ s.homepage = "http://github.com/smtlaissezfaire/garlic"
21
+ s.authors = ["Scott Taylor", "Ian White"]
22
+ end
23
+
24
+ Jeweler::GemcutterTasks.new
25
+
26
+ Jeweler::RubyforgeTasks.new do |rubyforge|
27
+ rubyforge.doc_task = "doc"
28
+ end
29
+
30
+ namespace :release do
31
+ desc "Release current version to github, gemcutter and rubyforge"
32
+ task :all => ['release', 'gemcutter:release', 'rubyforge:release']
33
+ end
34
+
35
+ rescue LoadError
36
+ puts "Jeweler not available for gem tasks. Install it with: sudo gem install jeweler"
37
+ end
38
+
39
+ begin
40
+ require 'hanna/rdoctask'
41
+ rescue LoadError
42
+ end
43
+
44
+ Rake::RDocTask.new(:doc) do |d|
45
+ d.options << '--all'
46
+ d.rdoc_dir = 'doc'
47
+ d.main = 'README.rdoc'
48
+ d.title = "garlic API Docs"
49
+ d.rdoc_files.include('README.rdoc', 'History.txt', 'License.txt', 'Todo.txt', 'VERSION', 'lib/**/*.rb')
50
+ end
51
+ task :rdoc => :doc
52
+
53
+ Spec::Rake::SpecTask.new(:spec) do |t|
54
+ t.warning = true
55
+ end
56
+ 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,81 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
4
+
5
+ require 'getoptlong'
6
+ require 'rake'
7
+ require 'garlic'
8
+
9
+ include Garlic
10
+
11
+ USAGE = "USAGE: garlic [options] [command] (try garlic --help)"
12
+
13
+ HELP = <<-end_doc
14
+ garlic - run a command in different versions of rails
15
+
16
+ USAGE: garlic [options] [command]
17
+
18
+ COMMANDS:
19
+ #{Garlic::Session.commands_with_description.map{|method, desc| " %-17s %s" % [method, desc]}.join("\n")}
20
+
21
+ The default command is "default"
22
+
23
+ OPTIONS:
24
+ --help -h You're reading it
25
+ --verbose -v Show work
26
+ --config CONFIG -c Specify a different location of garlic config
27
+ --backtrace Show ruby backtrace on error
28
+ --targets TARGETS -t Specify subset of targets comma separated or
29
+ regexp part e.g. -t 2-1,2-2
30
+
31
+ You can generate a sample garlic.rb with
32
+ garlic generate [TEMPLATE [PLUGIN_NAME]] (Available templates: #{available_templates.join(', ')})
33
+
34
+ end_doc
35
+
36
+ @verbose = false
37
+
38
+ GetoptLong.new(
39
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
40
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
41
+ ['--targets', '-t', GetoptLong::REQUIRED_ARGUMENT],
42
+ ['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],
43
+ ['--backtrace', GetoptLong::NO_ARGUMENT],
44
+ ['--path', GetoptLong::NO_ARGUMENT],
45
+ ['--version', GetoptLong::NO_ARGUMENT]
46
+ ).each do |opt, arg|
47
+ case opt
48
+ when '--help' then STDOUT << HELP; exit true
49
+ when '--path' then puts File.dirname(File.dirname(File.expand_path(__FILE__))); exit true
50
+ when '--version' then puts Garlic::Version::String; exit true
51
+ when '--verbose' then @verbose = true
52
+ when '--config' then @config_file = arg
53
+ when '--targets' then @run_targets = arg
54
+ when '--backtrace' then @backtrace = true
55
+ end
56
+ end
57
+
58
+ begin
59
+ if ARGV.first == 'generate'
60
+ generate_config(*ARGV[1..-1])
61
+ else
62
+ # run a garlic command
63
+ raise "Unknown command: #{ARGV.first}" unless ARGV.empty? || Garlic::Session.commands.include?(ARGV.first)
64
+
65
+ verbose(@verbose)
66
+
67
+ # configure the garlic runner
68
+ garlic(@config_file) # load up the garlic instance
69
+ garlic.verbose = @verbose
70
+ garlic.run_targets = @run_targets
71
+
72
+ # run the command
73
+ ARGV << 'default' if ARGV.empty?
74
+ garlic.send *ARGV
75
+ end
76
+
77
+ rescue Exception => e
78
+ STDERR << "\n#{USAGE}\n\nError: #{e.message}\n\n"
79
+ raise e if @backtrace
80
+ exit false
81
+ end
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
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_rails3_compatibility}
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 = ["Scott Taylor", "Ian White"]
12
+ s.date = %q{2011-06-03}
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{scott@railsnewbie.com}
16
+ s.executables = ["garlic"]
17
+ s.extra_rdoc_files = [
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ "History.txt",
22
+ "License.txt",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "Todo.txt",
26
+ "VERSION",
27
+ "bin/garlic",
28
+ "garlic_rails3_compatibility.gemspec",
29
+ "lib/garlic.rb",
30
+ "lib/garlic/configurator.rb",
31
+ "lib/garlic/generator.rb",
32
+ "lib/garlic/repo.rb",
33
+ "lib/garlic/session.rb",
34
+ "lib/garlic/shell.rb",
35
+ "lib/garlic/target.rb",
36
+ "lib/garlic/tasks.rb",
37
+ "lib/tabtab_definitions/garlic.rb",
38
+ "sh/garlic.sh",
39
+ "spec/garlic/repo_spec.rb",
40
+ "spec/spec_helper.rb",
41
+ "templates/default.rb",
42
+ "templates/rspec.rb",
43
+ "templates/shoulda.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/smtlaissezfaire/garlic}
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{Test your project across multiple versions of rails/dependencies}
49
+ s.test_files = [
50
+ "spec/garlic/repo_spec.rb",
51
+ "spec/spec_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ else
60
+ end
61
+ else
62
+ end
63
+ end
64
+
@@ -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,140 @@
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
+
62
+ if File.exists?(rails_bin = "#{rails_repo.path}/bin/rails")
63
+ `ruby #{rails_bin} new #{path}`
64
+
65
+ cd path do
66
+ `bundle install`
67
+ end
68
+ else
69
+ `ruby #{rails_repo.path}/railties/bin/rails #{path}`
70
+ end
71
+ end
72
+
73
+ install_dependency(rails_repo, 'vendor/rails') do
74
+ # remove secret token so that rails 3 installer doesn't freeze
75
+ `rm -rf config/initializers/secret_token.rb`
76
+ `rake rails:update`
77
+ end
78
+ end
79
+
80
+ def install_dependency(repo, install_path = ".", options = {}, &block)
81
+ repo = garlic.repo(repo) unless repo.is_a?(Repo)
82
+ tree_ish = Repo.tree_ish(options)
83
+
84
+ if options[:clone]
85
+ if Repo.path?(install_path)
86
+ puts "#{install_path} exists, and is a repo"
87
+ cd(install_path) { `git fetch origin` }
88
+ else
89
+ puts "cloning #{repo.name} to #{install_path}"
90
+ repo.clone_to(File.join(path, install_path))
91
+ end
92
+ cd(install_path) { `git checkout #{tree_ish || repo.head_sha}` }
93
+
94
+ else
95
+ old_tree_ish = repo.head_sha
96
+ repo.checkout(tree_ish) if tree_ish
97
+ if read_sha(install_path) == repo.head_sha
98
+ puts "#{install_path} is up to date at #{tree_ish || repo.head_sha[0..6]}"
99
+ else
100
+ puts "#{install_path} needs update to #{tree_ish || repo.head_sha[0..6]}, exporting archive from #{repo.name}..."
101
+ repo.export_to(File.join(path, install_path))
102
+ cd(path) { garlic.instance_eval(&block) } if block_given?
103
+ write_sha(install_path, repo.head_sha)
104
+ end
105
+ repo.checkout(old_tree_ish) if tree_ish
106
+ end
107
+ end
108
+
109
+
110
+ class Runner
111
+ attr_reader :target
112
+
113
+ def initialize(target)
114
+ @target = target
115
+ end
116
+
117
+ def run(&block)
118
+ cd target.path do
119
+ instance_eval(&block)
120
+ end
121
+ end
122
+
123
+ def method_missing(method, *args, &block)
124
+ target.garlic.send(method, *args, &block)
125
+ end
126
+
127
+ def respond_to?(method)
128
+ super(method) || target.garlic.respond_to?(method)
129
+ end
130
+
131
+ def plugin(plugin, options = {}, &block)
132
+ target.send(:install_dependency, plugin, "vendor/plugins/#{options[:as] || plugin}", options, &block)
133
+ end
134
+
135
+ def dependency(repo, dest, options = {}, &block)
136
+ target.send(:install_dependency, repo, dest, options, &block)
137
+ end
138
+ end
139
+ end
140
+ 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,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garlic_rails3_compatibility
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 10
10
+ version: 0.1.10
11
+ platform: ruby
12
+ authors:
13
+ - Scott Taylor
14
+ - Ian White
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-03 00:00:00 -04:00
20
+ default_executable: garlic
21
+ dependencies: []
22
+
23
+ description: CI tool to test your project across multiple versions of rails/dependencies
24
+ email: scott@railsnewbie.com
25
+ executables:
26
+ - garlic
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.rdoc
31
+ files:
32
+ - History.txt
33
+ - License.txt
34
+ - README.rdoc
35
+ - Rakefile
36
+ - Todo.txt
37
+ - VERSION
38
+ - bin/garlic
39
+ - garlic_rails3_compatibility.gemspec
40
+ - lib/garlic.rb
41
+ - lib/garlic/configurator.rb
42
+ - lib/garlic/generator.rb
43
+ - lib/garlic/repo.rb
44
+ - lib/garlic/session.rb
45
+ - lib/garlic/shell.rb
46
+ - lib/garlic/target.rb
47
+ - lib/garlic/tasks.rb
48
+ - lib/tabtab_definitions/garlic.rb
49
+ - sh/garlic.sh
50
+ - spec/garlic/repo_spec.rb
51
+ - spec/spec_helper.rb
52
+ - templates/default.rb
53
+ - templates/rspec.rb
54
+ - templates/shoulda.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/smtlaissezfaire/garlic
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.3.7
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Test your project across multiple versions of rails/dependencies
89
+ test_files:
90
+ - spec/garlic/repo_spec.rb
91
+ - spec/spec_helper.rb