vendorificator 0.5.git.v0.4.0.63.g8e9d54d → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +9 -9
  2. data/.travis.yml +2 -2
  3. data/CHANGELOG.md +10 -0
  4. data/Gemfile +5 -12
  5. data/README.md +27 -1
  6. data/Rakefile +2 -8
  7. data/features/chef_cookbook.feature +4 -4
  8. data/features/download.feature +1 -1
  9. data/features/edgecases.feature +15 -0
  10. data/features/environment.feature +3 -2
  11. data/features/fake_mode.feature +15 -0
  12. data/features/git.feature +4 -4
  13. data/features/overlay.feature +99 -0
  14. data/features/step_definitions/basic.rb +22 -0
  15. data/features/step_definitions/git.rb +16 -0
  16. data/features/step_definitions/vendorificator.rb +9 -4
  17. data/features/support/aruba_ext.rb +4 -0
  18. data/features/support/env.rb +3 -0
  19. data/features/tarball.feature +2 -2
  20. data/features/tarball_edit.feature +3 -3
  21. data/features/tool.feature +6 -4
  22. data/features/tool_shortcuts.feature +3 -3
  23. data/features/tool_specs.feature +62 -0
  24. data/features/vendor.feature +4 -3
  25. data/lib/vendorificator.rb +7 -1
  26. data/lib/vendorificator/cli.rb +12 -11
  27. data/lib/vendorificator/config.rb +32 -1
  28. data/lib/vendorificator/environment.rb +23 -27
  29. data/lib/vendorificator/hooks/chef_cookbook.rb +2 -2
  30. data/lib/vendorificator/overlay.rb +17 -0
  31. data/lib/vendorificator/segment.rb +376 -0
  32. data/lib/vendorificator/segment/overlay.rb +114 -0
  33. data/lib/vendorificator/segment/vendor.rb +115 -0
  34. data/lib/vendorificator/vendor.rb +25 -279
  35. data/lib/vendorificator/vendor/tool.rb +40 -23
  36. data/lib/vendorificator/version.rb +1 -1
  37. data/spec/vendorificator/config_spec.rb +66 -0
  38. data/spec/vendorificator/environment_spec.rb +7 -7
  39. data/spec/vendorificator/fixtures/vendorfiles/overlay.rb +4 -0
  40. data/spec/vendorificator/segment/vendor_spec.rb +19 -0
  41. data/spec/vendorificator/segment_spec.rb +106 -0
  42. data/spec/vendorificator/vendor_spec.rb +0 -89
  43. data/vendorificator.gemspec +5 -5
  44. metadata +45 -29
@@ -23,7 +23,7 @@ module Vendorificator::Hooks
23
23
  environment.config[:chef_cookbook_ignore_dependencies]
24
24
 
25
25
  if !ign || ign.respond_to?(:include?)
26
- metadata = File.join(self.work_dir, 'metadata.rb')
26
+ metadata = File.join(work_dir, 'metadata.rb')
27
27
 
28
28
  unless File.exist?(metadata)
29
29
  say_status :quiet, 'WARNING', "Metadata of #{name} does not exist at #{metadata}, could not gather dependencies", :red
@@ -44,7 +44,7 @@ module Vendorificator::Hooks
44
44
  # Reject dependencies that already have a module
45
45
  deps.reject! do |dep|
46
46
  dir = basedir.join(dep).to_s
47
- environment.vendor_instances.any? do |vi|
47
+ environment.segments.any? do |vi|
48
48
  vi.work_dir == dir
49
49
  end
50
50
  end
@@ -0,0 +1,17 @@
1
+ module Vendorificator
2
+ class Overlay
3
+ attr_reader :path, :name, :segments
4
+
5
+ def initialize(options = {})
6
+ @name = strip_leading_slash(options[:name])
7
+ @path = options[:path] ? strip_leading_slash(options[:path]) : @name
8
+ @segments = []
9
+ end
10
+
11
+ private
12
+
13
+ def strip_leading_slash(string)
14
+ (result = string.gsub(/\A\//, '')) != '' ? result : nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,376 @@
1
+ module Vendorificator
2
+ class Segment
3
+
4
+ def initialize(args = {})
5
+ @metadata = {}
6
+ end
7
+
8
+ def fast_forward(branch)
9
+ in_branch { |tmpgit| tmpgit.merge({:ff_only => true}, branch) }
10
+ end
11
+
12
+ def status
13
+ # If there's no branch yet, it's a completely new module
14
+ return :new unless head
15
+
16
+ # If there's a branch but no tag, it's a known module that's not
17
+ # been updated for the new definition yet.
18
+ return :outdated unless tagged_sha1
19
+
20
+ # Well, this is awkward: branch is in config and exists, but is
21
+ # not merged into current branch at all.
22
+ return :unmerged unless merged?
23
+
24
+ # Merge base is tagged with our tag. We're good.
25
+ return :up_to_date if tagged_sha1 == merged_base
26
+
27
+ return :unpulled if environment.fast_forwardable?(tagged_sha1, merged_base)
28
+
29
+ return :unknown
30
+ end
31
+
32
+ def run!(options = {})
33
+ say_status :default, :module, name
34
+ indent do
35
+ case status
36
+
37
+ when :up_to_date
38
+ say_status :default, 'up to date', to_s
39
+
40
+ when :unpulled, :unmerged
41
+ say_status :default, 'merging', to_s, :yellow
42
+ merge_back tagged_sha1
43
+
44
+ when :outdated, :new
45
+ say_status :default, 'fetching', to_s, :yellow
46
+ update options
47
+
48
+ else
49
+ say_status :quiet, self.status, "I'm unsure what to do.", :red
50
+ end
51
+ end
52
+ end
53
+
54
+ def pushable_refs
55
+ created_tags.unshift("refs/heads/#{branch_name}")
56
+ end
57
+
58
+ def work_dir(relative = false)
59
+ arr = relative ? [] : [git.git_work_tree]
60
+ arr << environment.relative_root_dir
61
+ arr << work_subdir
62
+
63
+ _join *arr
64
+ end
65
+
66
+ def included_in_list?(module_list)
67
+ modpaths = module_list.map { |m| File.expand_path(m) }
68
+
69
+ module_list.include?(name) ||
70
+ module_list.include?("#{group}/#{name}") ||
71
+ modpaths.include?(File.expand_path(work_dir)) ||
72
+ module_list.include?(merged_base) ||
73
+ module_list.include?(branch_name)
74
+ end
75
+
76
+ def updatable?
77
+ return nil if self.status == :up_to_date
78
+ return false if !head
79
+ return false if head && merged_base == head
80
+ git.describe({:abbrev => 0, :always => true}, branch_name)
81
+ end
82
+
83
+ def to_s
84
+ _join name, version
85
+ end
86
+
87
+ # Public: Get git vendor notes of the merged commit.
88
+ #
89
+ # Returns the Hash of git vendor notes.
90
+ def merged_notes
91
+ Commit.new(merged_base, git).notes?
92
+ end
93
+
94
+ def merged_version
95
+ merged_tag && merged_tag[(1 + tag_name_base.length)..-1]
96
+ end
97
+
98
+ private
99
+
100
+ def config
101
+ environment.config
102
+ end
103
+
104
+ # Private: Commits and annotates the conjured module.
105
+ #
106
+ # environment_metadata - Hash with environment metadata where vendor was run
107
+ #
108
+ # Returns nothing.
109
+ def commit_and_annotate(environment_metadata = {})
110
+ return if config.fake_mode?
111
+
112
+ git.capturing.add work_dir, *@vendor.git_add_extra_paths
113
+ git.capturing.commit :m => @vendor.conjure_commit_message
114
+ git.capturing.notes({:ref => 'vendor'}, 'add', {:m => conjure_note(environment_metadata)}, 'HEAD')
115
+ git.capturing.tag( { :a => true, :m => tag_message }, tag_name )
116
+ say_status :default, :tag, tag_name
117
+ end
118
+
119
+ # Private: Merges all the data we use for the commit note.
120
+ #
121
+ # environment_metadata - Hash with environment metadata where vendor was run
122
+ #
123
+ # Returns: The note in the YAML format.
124
+ def conjure_note(environment_metadata = {})
125
+ config.metadata.
126
+ merge(environment_metadata).
127
+ merge(metadata).
128
+ merge(@vendor.metadata).
129
+ to_yaml
130
+ end
131
+
132
+ def head
133
+ git.capturing.rev_parse({:verify => true, :quiet => true}, "refs/heads/#{branch_name}").strip
134
+ rescue MiniGit::GitError
135
+ nil
136
+ end
137
+
138
+ def in_branch(branch = branch_name, options = {}, &block)
139
+ Dir.mktmpdir do |tmpdir|
140
+ tmpgit = create_temp_git_repo(branch, options, tmpdir)
141
+ fetch_repo_data tmpgit
142
+ repo_cleanup tmpgit if options[:clean] || !branch_exists?(branch)
143
+
144
+ Dir.chdir(tmpdir){ yield tmpgit }
145
+
146
+ propagate_repo_data_to_original branch, tmpdir
147
+ end
148
+ end
149
+
150
+ def create_temp_git_repo(branch, options, dir)
151
+ clone_opts = {shared: true, no_checkout: true}
152
+ clone_opts[:branch] = branch if branch_exists? branch
153
+ say { MiniGit::Capturing.git :clone, clone_opts, git.git_dir, dir }
154
+
155
+ tmpgit = MiniGit.new(dir)
156
+ unless branch_exists? branch
157
+ say { tmpgit.capturing.checkout({orphan: true}, branch) }
158
+ end
159
+
160
+ tmpgit
161
+ end
162
+
163
+ def repo_cleanup(tmpgit)
164
+ tmpgit.rm({ :r => true, :f => true, :q => true, :ignore_unmatch => true }, '.')
165
+ end
166
+
167
+ def fetch_repo_data(tmpgit)
168
+ tmpgit.fetch git.git_dir, "refs/notes/vendor:refs/notes/vendor" if notes_exist?
169
+ end
170
+
171
+ def propagate_repo_data_to_original(branch, clone_dir)
172
+ if config.fake_mode?
173
+ add_path_to_git_exclude
174
+ copy_back_from_temporary_clone(clone_dir)
175
+ else
176
+ fetch_back_from_temporary_clone(branch, clone_dir)
177
+ end
178
+ end
179
+
180
+ # Private: Fetches the branches from the temporary clone in the main repo,
181
+ # to get the conjured data.
182
+ #
183
+ # branch - branch name to fetch
184
+ # clone_dir - path to the local temporary clone
185
+ #
186
+ # Returns nothing.
187
+ def fetch_back_from_temporary_clone(branch, clone_dir)
188
+ git.fetch clone_dir
189
+ git.fetch({tags: true}, clone_dir)
190
+ git.fetch clone_dir,
191
+ "refs/heads/#{branch}:refs/heads/#{branch}",
192
+ "refs/notes/vendor:refs/notes/vendor"
193
+ end
194
+
195
+ # Private: Copies the conjured vendor files back to main repo, instead of
196
+ # just fetching the branches. Used in fake development mode.
197
+ #
198
+ # clone_dir - path to the local temporary clone
199
+ #
200
+ # Returns nothing.
201
+ def copy_back_from_temporary_clone(clone_dir)
202
+ FileUtils.mkdir_p work_dir
203
+ FileUtils.cp_r clone_dir, work_dir
204
+ end
205
+
206
+ # Private: adds conjured directory path to git exclude file. Used in fake
207
+ # mode.
208
+ #
209
+ # Returns nothing.
210
+ def add_path_to_git_exclude
211
+ return if check_if_work_dir_excluded
212
+
213
+ if File.exists? work_dir
214
+ say_status(:quiet, 'FATAL', "Directory #{work_dir(true).inspect} already exists. Aborting.", :red)
215
+ exit
216
+ end
217
+ File.open('.git/info/exclude', 'a') { |f| f.puts work_dir(true) }
218
+ end
219
+
220
+ # Private: Checks if segment work_dir has already been excluded.
221
+ #
222
+ # Returns true/false.
223
+ def check_if_work_dir_excluded
224
+ File.open('.git/info/exclude').each_line.any? do |line|
225
+ line =="#{work_dir(true)}\n"
226
+ end
227
+ end
228
+
229
+ def in_work_dir
230
+ FileUtils::mkdir_p work_dir
231
+
232
+ Dir::chdir work_dir do
233
+ begin
234
+ shell.padding += 1
235
+ yield
236
+ ensure
237
+ shell.padding -= 1
238
+ end
239
+ end
240
+ end
241
+
242
+ def notes_exist?
243
+ git.capturing.rev_parse({verify: true, quiet: true}, 'refs/notes/vendor')
244
+ true
245
+ rescue MiniGit::GitError
246
+ false
247
+ end
248
+
249
+ def metadata
250
+ default = {
251
+ }
252
+ default.merge @metadata
253
+ end
254
+
255
+ def name
256
+ raise NotImplementedError
257
+ end
258
+
259
+ def version
260
+ raise NotImplementedError
261
+ end
262
+
263
+ def tag_message
264
+ @vendor.conjure_commit_message
265
+ end
266
+
267
+ def _join(*parts)
268
+ parts.compact.map(&:to_s).join('/')
269
+ end
270
+
271
+ def git
272
+ @git || environment.git
273
+ end
274
+
275
+ def make_subdir_root(subdir_path)
276
+ curdir = Pathname.pwd
277
+ tmpdir = Pathname.pwd.dirname.join("#{Pathname.pwd.basename}.tmp")
278
+ subdir = Pathname.pwd.join(subdir_path)
279
+
280
+ Dir.chdir('..')
281
+
282
+ subdir.rename(tmpdir.to_s)
283
+ curdir.rmtree
284
+ tmpdir.rename(curdir.to_s)
285
+ ensure
286
+ Dir.chdir(curdir.to_s) if curdir.exist?
287
+ end
288
+
289
+ def created_tags
290
+ git.capturing.show_ref.lines.map{ |line| line.split(' ')[1] }.
291
+ select{ |ref| ref =~ /\Arefs\/tags\/#{tag_name_base}\// }
292
+ end
293
+
294
+ def tagged_sha1
295
+ @tagged_sha1 ||= git.capturing.rev_parse(
296
+ {:verify => true, :quiet => true}, "refs/tags/#{tag_name}^{commit}"
297
+ ).strip
298
+ rescue MiniGit::GitError
299
+ nil
300
+ end
301
+
302
+ def group
303
+ nil
304
+ end
305
+
306
+ def branch_name
307
+ _join(config[:branch_prefix], group, name)
308
+ end
309
+
310
+ def tag_name
311
+ _join(tag_name_base, version)
312
+ end
313
+
314
+ def tag_name_base
315
+ branch_name
316
+ end
317
+
318
+ def merged_base
319
+ return @merged_base if defined? @merged_base
320
+ base = git.capturing.merge_base(head, 'HEAD').strip
321
+ @merged_base = base.empty? ? nil : base
322
+ rescue MiniGit::GitError
323
+ @merged_base = nil
324
+ end
325
+
326
+ def merged?
327
+ !merged_base.nil?
328
+ end
329
+
330
+ def merged_tag
331
+ return @merged_tag if defined? @merged_tag
332
+ @merged_tag = if merged?
333
+ tag = git.capturing.describe( {
334
+ :exact_match => true,
335
+ :match => _join(tag_name_base, '*') },
336
+ merged_base).strip
337
+ tag.empty? ? nil : tag
338
+ else
339
+ nil
340
+ end
341
+ end
342
+
343
+ # Private: Checks whether a particular branch exists.
344
+ #
345
+ # branch - name of the branch to check, default to segment branch
346
+ #
347
+ # Returns true/false.
348
+ def branch_exists?(branch = branch_name)
349
+ git.capturing.rev_parse({:verify => true}, "refs/heads/#{branch}")
350
+ true
351
+ rescue MiniGit::GitError
352
+ false
353
+ end
354
+
355
+ def shell
356
+ environment.shell
357
+ end
358
+
359
+ def say(verb_level= :default, &block)
360
+ output = yield
361
+ environment.say verb_level, output
362
+ end
363
+
364
+ def say_status(*args, &block)
365
+ environment.say_status(*args, &block)
366
+ end
367
+
368
+ def indent(verb_level = :default, *args, &block)
369
+ say_status verb_level, *args unless args.empty?
370
+ shell.padding += 1 if shell
371
+ yield
372
+ ensure
373
+ shell.padding -= 1 if shell
374
+ end
375
+ end
376
+ end
@@ -0,0 +1,114 @@
1
+ module Vendorificator
2
+ class Segment::Overlay < Segment
3
+ attr_reader :overlay, :segments, :environment
4
+
5
+ def initialize(options)
6
+ @overlay = ::Vendorificator::Overlay.new(options[:overlay_opts])
7
+ @environment = options[:environment]
8
+ @segments = []
9
+ super
10
+ end
11
+
12
+ def name
13
+ "Overlay \"#{overlay.path}\""
14
+ end
15
+
16
+ def base_branch_name
17
+ _join config[:branch_prefix], 'overlay', (overlay.name || overlay.path)
18
+ end
19
+
20
+ def branch_name
21
+ _join base_branch_name, 'layer'
22
+ end
23
+
24
+ def merge_branch_name
25
+ _join base_branch_name, 'merged'
26
+ end
27
+
28
+ def compute_dependencies!
29
+ each_segment { |seg| seg.compute_dependencies! }
30
+ end
31
+
32
+ # Public: Goes through all the Vendor instances and runs the block
33
+ #
34
+ # segments - An Array of vendor segments to yield the block for.
35
+ #
36
+ # Returns nothing.
37
+ def each_segment(*segments)
38
+ # We don't use @segments.each here, because Vendor#run! is
39
+ # explicitly allowed to append to instantiate new dependencies, and #each
40
+ # fails to catch up on some Ruby implementations.
41
+ i = 0
42
+ while true
43
+ break if i >= @segments.length
44
+ seg = @segments[i]
45
+ yield seg if segments.empty? || seg.included_in_list?(segments)
46
+ i += 1
47
+ end
48
+ end
49
+
50
+ def group
51
+ nil
52
+ end
53
+
54
+ def version
55
+ nil
56
+ end
57
+
58
+ def path
59
+ _join overlay.path
60
+ end
61
+
62
+ private
63
+
64
+ def merge_back
65
+ unless config.fake_mode?
66
+ in_branch merge_branch_name do |git|
67
+ each_segment do |seg|
68
+ git.capturing.merge({:no_edit => true, :no_ff => true}, seg.branch_name)
69
+ end
70
+ end
71
+ git.capturing.merge({:no_edit => true, :no_ff => true}, merge_branch_name)
72
+ end
73
+
74
+ each_segment do |seg|
75
+ seg.vendor.postprocess! if seg.vendor.respond_to? :postprocess!
76
+ seg.vendor.compute_dependencies!
77
+ end
78
+ end
79
+
80
+ def update(options = {})
81
+ shell.padding += 1
82
+ each_segment do |seg|
83
+ seg.conjure options
84
+ end
85
+
86
+ merge_back
87
+ ensure
88
+ shell.padding -= 1
89
+ end
90
+
91
+ def work_subdir
92
+ _join path
93
+ end
94
+
95
+ def fetch_repo_data(tmpgit)
96
+ super
97
+ each_segment do |seg|
98
+ tmpgit.fetch git.git_dir,
99
+ "refs/heads/#{seg.branch_name}:refs/heads/#{seg.branch_name}"
100
+ end
101
+ end
102
+
103
+ def create_temp_git_repo(branch, options, dir)
104
+ tmpgit = super
105
+ unless branch_exists? branch
106
+ tmpgit.capturing.commit allow_empty: true, message: 'Empty init'
107
+ end
108
+
109
+ tmpgit
110
+ end
111
+
112
+ end
113
+ end
114
+