Merge remote-tracking branch 'puppetlabs/master'
[puppet_vcsrepo.git] / spec / unit / puppet / provider / vcsrepo / git_spec.rb
1 require 'spec_helper'
2
3 describe Puppet::Type.type(:vcsrepo).provider(:git_provider) do
4   def branch_a_list(include_branch = nil?)
5     <<branches
6 end
7 #{"*  master" unless  include_branch.nil?}
8 #{"*  " + include_branch unless !include_branch}
9    remote/origin/master
10    remote/origin/foo
11
12 branches
13   end
14   let(:resource) { Puppet::Type.type(:vcsrepo).new({
15     :name     => 'test',
16     :ensure   => :present,
17     :provider => :git,
18     :revision => '2634',
19     :source   => 'git@repo',
20     :path     => '/tmp/test',
21     :force    => false
22   })}
23
24   let(:provider) { resource.provider }
25
26   before :each do
27     Puppet::Util.stubs(:which).with('git').returns('/usr/bin/git')
28   end
29
30   context 'creating' do
31     context "with a revision that is a remote branch" do
32       it "should execute 'git clone' and 'git checkout -b'" do
33         resource[:revision] = 'only/remote'
34         Dir.expects(:chdir).with('/').at_least_once.yields
35         Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
36         provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
37         provider.expects(:update_submodules)
38         provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
39         provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
40         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
41         provider.create
42       end
43     end
44
45     context "with a remote not named 'origin'" do
46       it "should execute 'git clone --origin not_origin" do
47         resource[:remote] = 'not_origin'
48         Dir.expects(:chdir).with('/').at_least_once.yields
49         Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
50         provider.expects(:git).with('clone', '--origin', 'not_origin', resource.value(:source), resource.value(:path))
51         provider.expects(:update_submodules)
52         provider.expects(:update_remote_url).with("not_origin", resource.value(:source)).returns false
53         provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
54         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
55         provider.create
56       end
57     end
58
59     context "with shallow clone enable" do
60       it "should execute 'git clone --depth 1'" do
61         resource[:revision] = 'only/remote'
62         resource[:depth] = 1
63         Dir.expects(:chdir).with('/').at_least_once.yields
64         Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
65         provider.expects(:git).with('clone', '--depth', '1', '--branch', resource.value(:revision),resource.value(:source), resource.value(:path))
66         provider.expects(:update_submodules)
67         provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
68         provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
69         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
70         provider.create
71       end
72     end
73
74     context "with a revision that is not a remote branch" do
75       it "should execute 'git clone' and 'git reset --hard'" do
76         resource[:revision] = 'a-commit-or-tag'
77         Dir.expects(:chdir).with('/').at_least_once.yields
78         Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
79         provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
80         provider.expects(:update_submodules)
81         provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
82         provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
83         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
84         provider.create
85       end
86
87       it "should execute 'git clone' and submodule commands" do
88         resource.delete(:revision)
89         provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
90         provider.expects(:update_submodules)
91         provider.expects(:update_remotes)
92         provider.create
93       end
94     end
95
96     context "with an ensure of bare" do
97       context "with revision" do
98         it "should raise an error" do
99           resource[:ensure] = :bare
100           expect { provider.create }.to raise_error Puppet::Error, /cannot set a revision.+bare/i
101         end
102       end
103       context "without revision" do
104         it "should just execute 'git clone --bare'" do
105           resource[:ensure] = :bare
106           resource.delete(:revision)
107           provider.expects(:git).with('clone', '--bare', resource.value(:source), resource.value(:path))
108           provider.expects(:update_remotes)
109           provider.create
110         end
111       end
112     end
113
114     context "with an ensure of mirror" do
115       context "with revision" do
116         it "should raise an error" do
117           resource[:ensure] = :mirror
118           expect { provider.create }.to raise_error Puppet::Error, /cannot set a revision.+bare/i
119         end
120       end
121       context "without revision" do
122         it "should just execute 'git clone --mirror'" do
123           resource[:ensure] = :mirror
124           resource.delete(:revision)
125           provider.expects(:git).with('clone', '--mirror', resource.value(:source), resource.value(:path))
126           provider.expects(:update_remotes)
127           provider.create
128         end
129       end
130     end
131
132     context "when a source is not given" do
133       context "when the path does not exist" do
134         it "should execute 'git init'" do
135           resource[:ensure] = :present
136           resource.delete(:source)
137           expects_mkdir
138           expects_chdir
139           expects_directory?(false)
140
141           provider.expects(:bare_exists?).returns(false)
142           provider.expects(:git).with('init')
143           provider.create
144         end
145       end
146
147       context "when the path is a bare repository" do
148         it "should convert it to a working copy" do
149           resource[:ensure] = :present
150           resource.delete(:source)
151           provider.expects(:bare_exists?).returns(true)
152           provider.expects(:convert_bare_to_working_copy)
153           provider.create
154         end
155       end
156
157       context "when the path is not empty and not a repository" do
158         it "should raise an exception" do
159           provider.expects(:path_exists?).returns(true)
160           provider.expects(:path_empty?).returns(false)
161           expect { provider.create }.to raise_error(Puppet::Error)
162         end
163       end
164     end
165
166     context "when the path does not exist" do
167       it "should execute 'git init --bare'" do
168         resource[:ensure] = :bare
169         resource.delete(:source)
170         resource.delete(:revision)
171         expects_chdir
172         expects_mkdir
173         expects_directory?(false)
174         provider.expects(:working_copy_exists?).returns(false)
175         provider.expects(:git).with('init', '--bare')
176         provider.create
177       end
178
179       it "should raise an exeption" do
180         resource[:ensure] = :mirror
181         resource.delete(:source)
182         resource.delete(:revision)
183
184         expect { provider.create }.to raise_error Puppet::Error, /cannot init repository with mirror.+try bare/i
185       end
186     end
187
188     context "when the path is a working copy repository" do
189       it "should convert it to a bare repository" do
190         resource[:ensure] = :bare
191         resource.delete(:source)
192         resource.delete(:revision)
193         provider.expects(:working_copy_exists?).returns(true)
194         provider.expects(:convert_working_copy_to_bare)
195         provider.create
196       end
197       it "should clone overtop it using force" do
198         resource[:force] = true
199         Dir.expects(:chdir).with('/').at_least_once.yields
200         Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
201         provider.expects(:path_exists?).returns(true)
202         provider.expects(:path_empty?).returns(false)
203         provider.destroy
204         provider.expects(:git).with('clone',resource.value(:source), resource.value(:path))
205         provider.expects(:update_submodules)
206         provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
207         provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
208         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
209         provider.create
210       end
211     end
212
213     context "when the path is not empty and not a repository" do
214       it "should raise an exception" do
215         provider.expects(:path_exists?).returns(true)
216         provider.expects(:path_empty?).returns(false)
217         provider.expects(:working_copy_exists?).returns(false)
218         expect { provider.create }.to raise_error(Puppet::Error)
219       end
220     end
221   end
222
223
224   context 'destroying' do
225     it "it should remove the directory" do
226       #expects_rm_rf
227       provider.destroy
228     end
229   end
230
231   context "checking the revision property" do
232     before do
233       expects_chdir('/tmp/test')
234       resource[:revision] = 'currentsha'
235       resource[:source] = 'http://example.com'
236       provider.stubs(:git).with('config', 'remote.origin.url').returns('')
237       provider.stubs(:git).with('fetch', 'origin') # FIXME
238       provider.stubs(:git).with('fetch', '--tags', 'origin')
239       provider.stubs(:git).with('rev-parse', 'HEAD').returns('currentsha')
240       provider.stubs(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
241       provider.stubs(:git).with('tag', '-l').returns("Hello")
242     end
243
244     context "when its SHA is not different than the current SHA" do
245       it "should return the ref" do
246         provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
247         provider.expects(:update_remotes)
248         expect(provider.revision).to eq(resource.value(:revision))
249       end
250     end
251
252     context "when its SHA is different than the current SHA" do
253       it "should return the current SHA" do
254         provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('othersha')
255         provider.expects(:update_remotes)
256         expect(provider.revision).to eq(resource.value(:revision))
257       end
258     end
259
260     context "when its a ref to a remote head" do
261       it "should return the revision" do
262         provider.stubs(:git).with('branch', '-a').returns("  remotes/origin/#{resource.value(:revision)}")
263         provider.expects(:git).with('rev-parse', "origin/#{resource.value(:revision)}").returns("newsha")
264         provider.expects(:update_remotes)
265         expect(provider.revision).to eq(resource.value(:revision))
266       end
267     end
268
269     context "when its a ref to non existant remote head" do
270       it "should fail" do
271         provider.expects(:git).with('branch', '-a').returns(branch_a_list)
272         provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('')
273         provider.expects(:update_remotes)
274         expect { provider.revision }.to raise_error(Puppet::Error, /not a local or remote ref$/)
275       end
276     end
277
278     context "when the source is modified" do
279       it "should update the origin url" do
280         resource[:source] = 'git://git@foo.com/bar.git'
281         provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n")
282         provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git')
283         provider.expects(:git).with('remote','update')
284         provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
285         expect(provider.revision).to eq(resource.value(:revision))
286       end
287     end
288
289     context "when multiple sources are modified" do
290       it "should update the urls" do
291         resource[:source] = {"origin" => "git://git@foo.com/bar.git", "new_remote" => "git://git@foo.com/baz.git"}
292         provider.expects(:git).at_least_once.with('config', '-l').returns("remote.origin.url=git://git@foo.com/bar.git\n", "remote.origin.url=git://git@foo.com/foo.git\n")
293         provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git')
294         provider.expects(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git')
295         provider.expects(:git).with('remote','update')
296         provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
297         expect(provider.revision).to eq(resource.value(:revision))
298       end
299     end
300
301     context "when there's no source" do
302       it 'should return the revision' do
303         resource.delete(:source)
304         provider.expects(:git).with('status')
305         provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
306         expect(provider.revision).to eq(resource.value(:revision))
307       end
308     end
309   end
310
311   context "setting the revision property" do
312     before do
313       expects_chdir
314     end
315     context "when it's an existing local branch" do
316       it "should use 'git fetch' and 'git reset'" do
317         resource[:revision] = 'feature/foo'
318         provider.expects(:update_submodules)
319         provider.expects(:git).with('branch', '-a').at_least_once.returns(branch_a_list(resource.value(:revision)))
320         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
321         provider.expects(:git).with('reset', '--hard',  "origin/#{resource.value(:revision)}")
322         provider.revision = resource.value(:revision)
323       end
324     end
325     context "when it's a remote branch" do
326       it "should use 'git fetch' and 'git reset'" do
327         resource[:revision] = 'only/remote'
328         provider.expects(:update_submodules)
329         provider.expects(:git).with('branch', '-a').at_least_once.returns(resource.value(:revision))
330         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
331         provider.expects(:git).with('reset', '--hard',  "origin/#{resource.value(:revision)}")
332         provider.revision = resource.value(:revision)
333       end
334     end
335     context "when it's a commit or tag" do
336       it "should use 'git fetch' and 'git reset'" do
337         resource[:revision] = 'a-commit-or-tag'
338         provider.expects(:git).with('branch', '-a').at_least_once.returns(fixture(:git_branch_a))
339         provider.expects(:git).with('checkout', '--force', resource.value(:revision))
340         provider.expects(:git).with('branch', '-a').returns(fixture(:git_branch_a))
341         provider.expects(:git).with('branch', '-a').returns(fixture(:git_branch_a))
342         provider.expects(:git).with('submodule', 'update', '--init', '--recursive')
343         provider.revision = resource.value(:revision)
344       end
345     end
346   end
347
348   context "updating references" do
349     it "should use 'git fetch --tags'" do
350       resource.delete(:source)
351       expects_chdir
352       provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n")
353       provider.expects(:git).with('fetch', 'origin')
354       provider.expects(:git).with('fetch', '--tags', 'origin')
355       provider.update_references
356     end
357   end
358
359   describe 'latest?' do
360     context 'when true' do
361       it do
362         provider.expects(:revision).returns('testrev')
363         provider.expects(:latest_revision).returns('testrev')
364         expect(provider.latest?).to be_truthy
365       end
366     end
367     context 'when false' do
368       it do
369         provider.expects(:revision).returns('master')
370         provider.expects(:latest_revision).returns('testrev')
371         expect(provider.latest?).to be_falsey
372       end
373     end
374   end
375
376   describe 'convert_working_copy_to_bare' do
377     it do
378       FileUtils.expects(:mv).returns(true)
379       FileUtils.expects(:rm_rf).returns(true)
380       FileUtils.expects(:mv).returns(true)
381
382       provider.instance_eval { convert_working_copy_to_bare }
383     end
384   end
385
386     describe 'convert_bare_to_working_copy' do
387     it do
388       FileUtils.expects(:mv).returns(true)
389       FileUtils.expects(:mkdir).returns(true)
390       FileUtils.expects(:mv).returns(true)
391       provider.expects(:commits_in?).returns(true)
392       # If you forget to stub these out you lose 3 hours of rspec work.
393       provider.expects(:reset).with('HEAD').returns(true)
394       provider.expects(:git_with_identity).returns(true)
395       provider.expects(:update_owner_and_excludes).returns(true)
396
397       provider.instance_eval { convert_bare_to_working_copy }
398     end
399   end
400
401 end