vendorificator 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG.md +40 -0
  3. data/Gemfile +2 -2
  4. data/README.md +18 -17
  5. data/Rakefile +15 -1
  6. data/features/edgecases.feature +34 -0
  7. data/features/environment.feature +17 -0
  8. data/features/fixtures/git/testrepo/objects/53/ac1a96882e666cee31504179abc21eac522f8d +2 -0
  9. data/features/fixtures/git/testrepo/refs/tags/email-v0 +1 -0
  10. data/features/git.feature +19 -1
  11. data/features/status.feature +1 -1
  12. data/features/step_definitions/basic.rb +11 -0
  13. data/features/step_definitions/git.rb +12 -0
  14. data/features/support/env.rb +3 -4
  15. data/features/support/minigit.rb +4 -0
  16. data/lib/vendorificator/cli.rb +32 -44
  17. data/lib/vendorificator/config.rb +63 -15
  18. data/lib/vendorificator/environment.rb +123 -16
  19. data/lib/vendorificator/errors.rb +3 -0
  20. data/lib/vendorificator/hooks/chef_cookbook.rb +1 -1
  21. data/lib/vendorificator/vendor/archive.rb +69 -65
  22. data/lib/vendorificator/vendor/chef_cookbook.rb +45 -39
  23. data/lib/vendorificator/vendor/download.rb +30 -26
  24. data/lib/vendorificator/vendor/git.rb +43 -32
  25. data/lib/vendorificator/vendor.rb +83 -91
  26. data/lib/vendorificator/version.rb +1 -1
  27. data/lib/vendorificator.rb +1 -0
  28. data/spec/spec_helper.rb +20 -22
  29. data/spec/vendorificator/config_spec.rb +64 -0
  30. data/spec/vendorificator/environment_spec.rb +81 -0
  31. data/spec/vendorificator/fixtures/vendorfiles/empty_vendor.rb +1 -0
  32. data/spec/vendorificator/fixtures/vendorfiles/vendor.rb +15 -0
  33. data/spec/vendorificator/vendor/chef_cookbook_spec.rb +13 -0
  34. data/spec/vendorificator/vendor_spec.rb +29 -33
  35. data/vendorificator.gemspec +3 -3
  36. metadata +45 -30
data/.gitignore CHANGED
@@ -17,4 +17,7 @@ test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
19
  /.rbx
20
+ /.ruby-version
21
+ /.ruby-gemset
20
22
  /log
23
+ .DS_Store
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changes
2
+
3
+ ## 0.3.0
4
+
5
+ - New command `vendor push` for pushing managed branches and tags to
6
+ remote repository
7
+ - Nicer syntax for mixin hooks
8
+ - Add `:tag` option for `git` submodule
9
+ - Better stashing of local changes when syncing
10
+ - Verbosity tweaks
11
+ - Refactor implementation of configuration, other internal refactors
12
+ - Improved test coverage
13
+
14
+ ## 0.2.0
15
+
16
+ - New vendor type `download` for downloading a single file
17
+ - Support `--version` and `-h` / `--help` switches
18
+ - New `:subdirectory` option for vendor modules
19
+ - Support JRuby
20
+ - Fix error when cleaning empty repository
21
+ - Misc verbosity tweaks
22
+ - Use MiniGit instead of Grit as Git library; other internal refactors
23
+ - Run Cucumber tests with Aruba
24
+
25
+ ## 0.1.1
26
+
27
+ - Add `--update` option to `vendor sync` and `vendor status` to check
28
+ whether upstream version has changed
29
+ - It is now possible to explicitly set module's category to `nil`
30
+ - Ruby 1.8.7 compatibility fix
31
+ - Gem runtime/development dependency fixes
32
+ - Initial minitest specs
33
+ - Make Cucumber tests use Webmock
34
+
35
+ ## 0.1.0
36
+
37
+ Initial release.
38
+
39
+
40
+
data/Gemfile CHANGED
@@ -4,8 +4,8 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
- git "git://github.com/mpasternacki/wrong.git",
8
- :ref => 'ad025241e5772373264d1bf62168e2bf3780ccf9' do
7
+ git "git://github.com/sconover/wrong.git",
8
+ :ref => '0cbc35a07cb63f6f409bb85da6ad7d107bdab021' do
9
9
  gem 'wrong'
10
10
  end
11
11
  gem 'minitest-ansi'
data/README.md CHANGED
@@ -50,8 +50,10 @@ detail.
50
50
 
51
51
  * `vendor sync` will update all vendor modules that need updating
52
52
  * `vendor status` will list all the modules and their status
53
- * `vendor pull` will pull all vendor branches, tags, and notes from a
54
- Git remote
53
+ * `vendor pull` will pull all vendor branches, tags, and notes from
54
+ a Git remote
55
+ * `vendor push` will push all vendor branches, tags, and notes to
56
+ a Git remote
55
57
  * `vendor diff` will show the differences between vendor module's
56
58
  pristine branch and curent work tree
57
59
  * `vendor log` will show a `git log` of all changes made to a particular
@@ -123,9 +125,9 @@ block).
123
125
  Example:
124
126
 
125
127
  ```ruby
126
- vendor 'generated', :version => '0.23' do |mod|
127
- File.open('README') { |f| f.puts "Hello, World!" }
128
- File.open('VERSION') { |f| f.puts mod.version }
128
+ vendor 'generated', :version => '0.23' do |v|
129
+ File.open('README', 'w') { |f| f.puts "Hello, World!" }
130
+ File.open('VERSION', 'w') { |f| f.puts v.version }
129
131
  end
130
132
  ```
131
133
 
@@ -160,7 +162,7 @@ unpacks it as contents of the module. It takes same options as
160
162
  disables this behaviour.
161
163
  * `:checksum` -- if set to SHA256 checksum of the file, it will be
162
164
  checked on download.
163
-
165
+
164
166
  Archive's `:version` defaults to file name.
165
167
 
166
168
  Example:
@@ -181,10 +183,11 @@ Downloads snapshot of a Git repository. Takes the same options as
181
183
  sets name to its basename then), just like `:url` for `archive`
182
184
  (e.g. `git "git://github.com/github/testrepo.git"` will be cloned
183
185
  from that repository, and named `testrepo`).
184
- * `:branch`, `:revision` -- what to check out when repository is
185
- cloned.
186
+ * `:branch`, `:revision`, `:tag` -- what to check out when repository
187
+ is cloned.
186
188
 
187
- Git module's `:version` defaults to the conjured revision.
189
+ Git module's `:version` defaults to the `:tag` if given, or the
190
+ conjured revision otherwise.
188
191
 
189
192
  Example:
190
193
 
@@ -208,7 +211,7 @@ always enough), plus:
208
211
  completely. If an array, don't download dependencies that are in
209
212
  the array. Default for that is `chef_cookbook_ignore_dependencies`
210
213
  setting.
211
-
214
+
212
215
  Examples:
213
216
 
214
217
  ```ruby
@@ -227,18 +230,16 @@ chef_cookbook 'memcached', ignore_dependencies => ['runit']
227
230
  ```
228
231
 
229
232
  If you get Chef cookbooks from Git or anywhere else than Opscode's
230
- community website, you can still use dependency resolution by mixing
231
- in the hook class:
233
+ community website, you can still use dependency resolution by using a :hooks
234
+ option to add it:
232
235
 
233
236
  ```ruby
234
- class << git 'git://github.com/user/cookbook.git', :category => :cookbooks
235
- include Vendorificator::Hooks::ChefCookbookDependencies
237
+ git 'git://github.com/user/cookbook.git',
238
+ :category => :cookbooks,
239
+ :hooks => 'ChefCookbookDependencies'
236
240
  end
237
241
  ```
238
242
 
239
- This is a bit convoluted, but there will soon be an argument to do
240
- that in a nicer way.
241
-
242
243
  ## Contributing
243
244
 
244
245
  1. Fork it
data/Rakefile CHANGED
@@ -13,6 +13,11 @@ namespace :relish do
13
13
  end
14
14
  end
15
15
 
16
+ task :info do
17
+ sh 'which git'
18
+ sh 'git --version'
19
+ end
20
+
16
21
  begin
17
22
  require 'cucumber'
18
23
  require 'cucumber/rake/task'
@@ -38,4 +43,13 @@ end
38
43
  mkdir_p 'tmp'
39
44
  ENV['TMPDIR'] ||= File.join(Dir.pwd, 'tmp')
40
45
 
41
- task :default => [:spec, :features]
46
+ task :default => [:info, :spec, :features]
47
+
48
+ if ENV['COVERAGE']
49
+ task :clean_coverage do
50
+ rm_rf 'coverage'
51
+ end
52
+
53
+ task :spec => :clean_coverage
54
+ task :features => :clean_coverage
55
+ end
@@ -0,0 +1,34 @@
1
+ Feature: weird edge cases
2
+
3
+ Scenario: module with a .gitignore file
4
+ Given a repository with following Vendorfile:
5
+ """ruby
6
+ vendor 'ignore', :version => 1 do
7
+ File.open('.gitignore', 'w') { |f| f.puts 'ignored.txt' }
8
+ end
9
+ """
10
+ When I successfully run `vendor sync`
11
+ Then the following has been conjured:
12
+ | Name | ignore |
13
+ | With file | .gitignore |
14
+
15
+ When I write to "vendor/ignore/ignored.txt" with:
16
+ """
17
+ whatever
18
+ """
19
+ Then git repository is clean
20
+
21
+ When I change Vendorfile to:
22
+ """ruby
23
+ vendor 'ignore', :version => 2 do
24
+ File.open('files.txt', 'w') { |f| f.puts Dir.entries('.').join("\n") }
25
+ File.open('.gitignore', 'w') { |f| f.puts 'ignored.txt' }
26
+ end
27
+ """
28
+ And I successfully run `vendor sync`
29
+ Then the file "vendor/ignore/files.txt" should not contain "ignored.txt"
30
+ And the file "vendor/ignore/ignored.txt" should contain exactly:
31
+ """
32
+ whatever
33
+ """
34
+ And git repository is clean
@@ -0,0 +1,17 @@
1
+ Feature: Environment management.
2
+
3
+ Scenario:
4
+ Given a repository with following Vendorfile:
5
+ """ruby
6
+ vendor 'generated', :version => '0.23' do |v|
7
+ File.open('README', 'w') { |f| f.puts "Hello, World!" }
8
+ end
9
+ """
10
+ And a remote repository
11
+ When I successfully run `vendor sync`
12
+ And I successfully run `vendor push`
13
+ Then branch "vendor/generated" exists in the remote repo
14
+ And tag "vendor/generated/0.23" exists in the remote repo
15
+
16
+
17
+
@@ -0,0 +1,2 @@
1
+ xE�A
2
+ �0@Q�9��E�IӦ���B2��TӖ:��ڍ��OCT�n���Bj�:;q�| ��m�2c䄱��BM��g੔�FWCJ�������Y����2~d8��]��;�������S�� јM�U���1l
@@ -0,0 +1 @@
1
+ 53ac1a96882e666cee31504179abc21eac522f8d
data/features/git.feature CHANGED
@@ -10,6 +10,7 @@ Scenario: Vendorificating a git repo
10
10
  | Name | testrepo |
11
11
  | Version | 10e9ac58c77bc229d8c59a5b4eb7422916453148 |
12
12
  | With file | test/alias.c |
13
+ And there's a git log message including "at revision 10e9ac58c77bc229d8c59a5b4eb7422916453148"
13
14
 
14
15
  Scenario: Vendorificating a subdirectory from a git repo
15
16
  Given a repository with following Vendorfile:
@@ -23,6 +24,7 @@ Scenario: Vendorificating a subdirectory from a git repo
23
24
  | Version | 10e9ac58c77bc229d8c59a5b4eb7422916453148 |
24
25
  | With file | alias.c |
25
26
  | Without file | test/alias.c |
27
+ And there's a git log message including "at revision 10e9ac58c77bc229d8c59a5b4eb7422916453148"
26
28
 
27
29
  Scenario: Vendorificating a certain branch from a git repo
28
30
  Given a repository with following Vendorfile:
@@ -35,6 +37,22 @@ Scenario: Vendorificating a certain branch from a git repo
35
37
  | Name | testrepo |
36
38
  | Version | ecbfa229ba5f11c05b18bcc4f7c32b8f25d63f8c |
37
39
  | With file | README.md |
40
+ And there's a git log message including "at revision ecbfa229ba5f11c05b18bcc4f7c32b8f25d63f8c"
41
+
42
+ @wip
43
+ Scenario: Vendorificating a certain tag from a git repo
44
+ Given a repository with following Vendorfile:
45
+ """ruby
46
+ git "file://#{ENV['FIXTURES_DIR']}/git/testrepo",
47
+ :tag => 'email-v0'
48
+ """
49
+ When I successfully run `vendor sync`
50
+ Then following has been conjured:
51
+ | Name | testrepo |
52
+ | Version | email-v0 |
53
+ | Without file | README.md |
54
+ | With file | test/alias.c |
55
+ And there's a git log message including "at revision f81247bde4ef7a1c7d280140cc0bcf0b8221a51f"
38
56
 
39
57
  Scenario: Vendorificating a certain revision from a git repo
40
58
  Given a repository with following Vendorfile:
@@ -48,4 +66,4 @@ Scenario: Vendorificating a certain revision from a git repo
48
66
  | Version | 6ff1be9c3819c93a2f41e0ddc09f252fcf154f34 |
49
67
  | With file | alias.c |
50
68
  | Without file | test/alias.c |
51
-
69
+ And there's a git log message including "at revision 6ff1be9c3819c93a2f41e0ddc09f252fcf154f34"
@@ -1,6 +1,6 @@
1
1
  Feature: Module status
2
2
 
3
- The `status` subcommand statuss all known modules and their status.
3
+ The `status` subcommand displays all known modules and their status.
4
4
 
5
5
  Background:
6
6
  Given a repository with following Vendorfile:
@@ -19,6 +19,17 @@ Given /^a repository with following Vendorfile:$/ do |vendorfile_contents|
19
19
  run_simple 'git commit -m "New repo"'
20
20
  end
21
21
 
22
+ Given /^a remote repository$/ do
23
+ create_dir '../remote-repository'
24
+ cd '../remote-repository'
25
+ run_simple 'git init --bare'
26
+ # Configure Git username & email to unclutter console output
27
+ run_simple 'git config user.name Cucumber'
28
+ run_simple 'git config user.email cucumber@`hostname --fqdn`'
29
+ cd '../working-repository'
30
+ run_simple 'git remote add origin ../remote-repository'
31
+ end
32
+
22
33
  When /^I change Vendorfile to:$/ do |vendorfile_contents|
23
34
  write_file('Vendorfile', vendorfile_contents)
24
35
  run_simple 'git commit -m "Updated Vendorfile" Vendorfile'
@@ -37,3 +37,15 @@ end
37
37
  Then /^tag matching (#{PATTERN}) does not exist$/ do |pat|
38
38
  deny { git.tags.any? { |t| t =~ pat } }
39
39
  end
40
+
41
+ Then(/^there's a git log message including "(.*?)"$/) do |message|
42
+ assert { git.log.lines.any? { |ln| ln.include?(message) } }
43
+ end
44
+
45
+ Then /^branch "(.*?)" exists in the remote repo$/ do |branch_name|
46
+ assert { remote_git.heads.include?(branch_name) }
47
+ end
48
+
49
+ Then /^tag "(.*?)" exists in the remote repo$/ do |tag_name|
50
+ assert { remote_git.tags.include?(tag_name) }
51
+ end
@@ -17,9 +17,8 @@ end
17
17
 
18
18
  Before do
19
19
  @aruba_timeout_seconds = case defined?(RUBY_ENGINE) && RUBY_ENGINE
20
- when 'ruby', nil then 3
21
- when 'jruby' then 20
22
- when 'rbx' then 10
23
- else 5
20
+ when 'jruby' then 45
21
+ when 'rbx' then 30
22
+ else 30
24
23
  end
25
24
  end
@@ -24,6 +24,10 @@ module Vendorificator
24
24
  def git
25
25
  @git ||= ::MiniGit::Capturing.new(current_dir)
26
26
  end
27
+
28
+ def remote_git
29
+ @remote_git ||= ::MiniGit::Capturing.new(current_dir + '/../remote-repository')
30
+ end
27
31
  end
28
32
  end
29
33
  end
@@ -1,5 +1,15 @@
1
- require 'thor'
1
+ if ENV['COVERAGE']
2
+ require 'pathname'
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_group 'Vendors', 'lib/vendorificator/vendor'
6
+ root Pathname.new(__FILE__).dirname.dirname.dirname.realpath.to_s
7
+ use_merging
8
+ end
9
+ SimpleCov.command_name "vendor##{$$}@#{DateTime.now.to_s}"
10
+ end
2
11
 
12
+ require 'thor'
3
13
  require 'vendorificator'
4
14
 
5
15
  module Vendorificator
@@ -24,6 +34,10 @@ module Vendorificator
24
34
  def initialize(args=[], options={}, config={})
25
35
  super
26
36
 
37
+ if self.options[:debug]
38
+ MiniGit.debug = true
39
+ end
40
+
27
41
  if self.options[:version]
28
42
  say "Vendorificator #{Vendorificator::VERSION}"
29
43
  exit
@@ -48,17 +62,9 @@ module Vendorificator
48
62
  desc :sync, "Download new or updated vendor files"
49
63
  method_option :update, :type => :boolean, :default => false
50
64
  def sync
51
- ensure_clean!
52
- environment.config[:use_upstream_version] = options[:update]
53
- Vendorificator::Vendor.each(*modules) do |mod|
54
- say_status :module, mod.name
55
- begin
56
- shell.padding += 1
57
- mod.run!
58
- ensure
59
- shell.padding -= 1
60
- end
61
- end
65
+ environment.sync options.merge(:modules => modules)
66
+ rescue DirtyRepoError
67
+ fail! 'Repository is not clean.'
62
68
  end
63
69
 
64
70
  desc "status", "List known vendor modules and their status"
@@ -66,7 +72,7 @@ module Vendorificator
66
72
  def status
67
73
  say_status 'WARNING', 'Git repository is not clean', :red unless environment.clean?
68
74
  environment.config[:use_upstream_version] = options[:update]
69
- Vendorificator::Vendor.each(*modules) do |mod|
75
+ environment.each_vendor_instance(*modules) do |mod|
70
76
  status_line = mod.to_s
71
77
 
72
78
  updatable = mod.updatable?
@@ -87,14 +93,19 @@ module Vendorificator
87
93
  method_option :remote, :aliases => ['-r'], :default => nil
88
94
  method_option :dry_run, :aliases => ['-n'], :default => false, :type => :boolean
89
95
  def pull
90
- ensure_clean!
91
- remotes = options[:remote] ? options[:remote].split(',') : environment.config[:remotes]
92
- remotes.each do |remote|
93
- indent 'remote', remote do
94
- environment.pull(remote, options)
95
- end
96
- end
96
+ environment.pull_all options
97
+ rescue DirtyRepoError
98
+ fail! 'Repository is not clean.'
97
99
  end
100
+
101
+ desc :push, "Push local changes back to the remote repository"
102
+ method_option :remote, :aliases => ['-r'], :default => nil
103
+ def push
104
+ environment.push options
105
+ rescue DirtyRepoError
106
+ fail! 'Repository is not clean.'
107
+ end
108
+
98
109
  desc "git GIT_COMMAND [GIT_ARGS [...]]",
99
110
  "Run a git command for specified modules"
100
111
  long_desc <<EOF
@@ -110,7 +121,7 @@ module Vendorificator
110
121
  vendor git diff --stat @MERGED@ -- @PATH@ # 'vendor diff', as diffstat
111
122
  EOF
112
123
  def git(command, *args)
113
- Vendorificator::Vendor.each(*modules) do |mod|
124
+ environment.each_vendor_instance(*modules) do |mod|
114
125
  unless mod.merged
115
126
  say_status 'unmerged', mod.to_s, :red unless options[:only_changed]
116
127
  next
@@ -185,28 +196,5 @@ EOF
185
196
  raise Thor::Error, 'I give up.'
186
197
  end
187
198
 
188
- def indent(*args, &block)
189
- say_status *args unless args.empty?
190
- shell.padding += 1
191
- yield
192
- ensure
193
- shell.padding -= 1
194
- end
195
-
196
- def ensure_clean!
197
- unless environment.clean?
198
- fail!('Repository is not clean.')
199
- end
200
- end
201
- end
202
- end
203
-
204
- # Monkey patch over https://github.com/wycats/thor/pull/298
205
- class Thor::Options
206
- alias_method :_orig_current_is_switch?, :current_is_switch?
207
- def current_is_switch?
208
- rv = _orig_current_is_switch?
209
- @parsing_options = false if !rv[0] && @stop_on_unknown && @parsing_options
210
- rv
211
199
  end
212
200
  end
@@ -1,33 +1,81 @@
1
1
  require 'pathname'
2
2
 
3
- require 'mixlib/config'
4
-
5
3
  module Vendorificator
6
4
  class Config
7
- extend Mixlib::Config
8
-
9
5
  attr_accessor :environment
10
6
 
11
- configure do |c|
12
- c[:basedir] = 'vendor'
13
- c[:branch_prefix] = 'vendor'
14
- c[:remotes] = %w(origin)
7
+ @defaults = {}
8
+ @modules = {}
9
+
10
+ def self.defaults
11
+ @defaults
12
+ end
13
+
14
+ def self.modules
15
+ @modules
16
+ end
17
+
18
+ def self.option(name, default = nil, &block)
19
+ define_method name do |*args|
20
+ if args.size == 0
21
+ @configuration[name.to_sym]
22
+ elsif args.size == 1
23
+ @configuration[name.to_sym] = args.first
24
+ else
25
+ raise 'Unsupported number of arguments (expected 0 or 1).'
26
+ end
27
+ end
28
+ @defaults[name.to_sym] = default if default
29
+ end
30
+
31
+ def self.register_module(name, klass)
32
+ @modules[name.to_sym] = klass
33
+ end
34
+
35
+ def initialize(params = {})
36
+ @configuration = self.class.defaults.merge(params)
15
37
  end
16
38
 
17
- def self.from_file(filename)
39
+ def read_file(filename)
18
40
  pathname = Pathname.new(filename).cleanpath.expand_path
19
41
 
20
- self[:vendorfile_path] = pathname
21
- self[:root_dir] =
22
- if ( pathname.basename.to_s == 'vendor.rb' &&
23
- pathname.dirname.basename.to_s == 'config' )
24
- # Correctly recognize root dir if main config is 'config/vendor.rb'
42
+ @configuration[:vendorfile_path] = pathname
43
+ @configuration[:root_dir] = if pathname.basename.to_s == 'vendor.rb' &&
44
+ pathname.dirname.basename.to_s == 'config'
25
45
  pathname.dirname.dirname
26
46
  else
27
47
  pathname.dirname
28
48
  end
29
49
 
30
- super(pathname.to_s)
50
+ instance_eval(IO.read(filename), filename, 1)
51
+ end
52
+
53
+ def configure(&block)
54
+ block.call @configuration
55
+ end
56
+
57
+ def [](key)
58
+ @configuration[key]
59
+ end
60
+
61
+ def []=(key, value)
62
+ @configuration[key] = value
63
+ end
64
+
65
+ def modules
66
+ self.class.modules
31
67
  end
68
+
69
+ def method_missing(method_symbol, *args, &block)
70
+ if modules.keys.include? method_symbol
71
+ modules[method_symbol].new(environment, args.delete_at(0).to_s, *args, &block)
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ option :basedir, 'vendor'
78
+ option :branch_prefix, 'vendor'
79
+ option :remotes, %w(origin)
32
80
  end
33
81
  end