Skip to content

Commit 2f31c06

Browse files
authored
Merge pull request #637 from jplindquist/git-sparse-checkout
Enable Git Includes (Sparse Checkout)
2 parents 3a481df + 387c2b1 commit 2f31c06

3 files changed

Lines changed: 124 additions & 6 deletions

File tree

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,25 +665,39 @@ vcsrepo { '/path/to/repo':
665665

666666
####Checking out only specific paths
667667

668-
**Note:** The `includes` param is only supported when subversion client version is >= 1.6.
668+
**Note:** The `includes` param is supported on all git clients, and subversion clients with version >= 1.6.
669669

670670
You can check out only specific paths in a particular repository by providing their relative paths to the `includes` parameter, like so:
671671

672-
~~~
672+
~~~ puppet
673673
vcsrepo { '/path/to/repo':
674674
ensure => present,
675675
provider => svn,
676676
source => 'http://svnrepo/hello/trunk',
677677
includes => [
678678
'root-file.txt',
679+
'file/this-file.txt',
679680
'checkout-folder',
681+
'folder/this-folder/',
682+
]
683+
}
684+
~~~
685+
686+
~~~ puppet
687+
vcsrepo { '/path/to/repo':
688+
ensure => present,
689+
provider => git,
690+
source => 'git@example.com:project.git',
691+
includes => [
692+
'root-file.txt',
680693
'file/this-file.txt',
694+
'checkout-folder',
681695
'folder/this-folder/',
682696
]
683697
}
684698
~~~
685699

686-
This will create files `/path/to/repo/file-at-root-path.txt` and `/path/to/repo/file/nested/within/repo.jmx`, with folders `/path/to/repo/some-folder` and `/path/to/repo/nested/folder/to/checkout` completely recreating their corresponding working tree path.
700+
This will create files `/path/to/repo/root-file.txt` and `/path/to/repo/file/this-file.txt`, with folders `/path/to/repo/checkout-folder` and `/path/to/repo/folder/this-folder/` completely recreating their corresponding working tree path.
687701

688702
When specified, the `depth` parameter will also be applied to the `includes` -- the root directory will be checked out using an `empty` depth, and the `includes` you specify will be checked out using the `depth` you provide.
689703

@@ -843,7 +857,7 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf
843857
* `depth` - Supports shallow clones in `git` or sets the scope limit in `svn`. (Available with `git` and `svn`.)
844858
* `filesystem_types` - Supports multiple types of filesystem. (Available with `svn`.)
845859
* `gzip_compression` - Supports explicit GZip compression levels. (Available with `cvs`.)
846-
* `include_paths` - Lets you checkout only certain paths. (Available with `svn`.)
860+
* `include_paths` - Lets you checkout only certain paths. (Available with `git` and `svn`.)
847861
* `modules` - Lets you choose a specific repository module. (Available with `cvs`.)
848862
* `multiple_remotes` - Tracks multiple remote repositories. (Available with `git`.)
849863
* `reference_tracking` - Lets you track revision references that can change over time (e.g., some VCS tags and branch names). (Available with all providers)

lib/puppet/provider/vcsrepo/git.rb

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes,
99
:user, :depth, :branch, :submodules, :safe_directory, :hooks_allowed,
10-
:umask, :http_proxy, :tmpdir
10+
:umask, :http_proxy, :tmpdir, :include_paths
1111

1212
def create
1313
check_force
@@ -19,6 +19,7 @@ def create
1919
set_mirror if @resource.value(:ensure) == :mirror && @resource.value(:source).is_a?(Hash)
2020
self.skip_hooks = @resource.value(:skip_hooks) unless @resource.value(:skip_hooks).nil?
2121

22+
configure_sparse_checkout if @resource.value(:includes)
2223
checkout if @resource.value(:revision)
2324
update_submodules if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true
2425

@@ -91,6 +92,20 @@ def revision=(desired)
9192
update_owner_and_excludes
9293
end
9394

95+
def includes
96+
return nil if bare_exists?
97+
98+
at_path do
99+
return nil unless File.file?('.git/info/sparse-checkout')
100+
File.readlines('.git/info/sparse-checkout').map(&:chomp)
101+
end
102+
end
103+
104+
def includes=(_desired)
105+
configure_sparse_checkout
106+
checkout
107+
end
108+
94109
def bare_exists?
95110
bare_git_config_exists? && !working_copy_exists?
96111
end
@@ -407,6 +422,33 @@ def init_repository
407422
end
408423
end
409424

425+
# @!visibility private
426+
def configure_sparse_checkout
427+
raise("Cannot set includes on a #{@resource.value(:ensure)} repository") if ensure_bare_or_mirror? || bare_exists?
428+
429+
git_ver = git_version
430+
if Gem::Version.new(git_ver) >= Gem::Version.new('2.25.0')
431+
# sparse-checkout command was introduced in version 2.25.0.
432+
at_path do
433+
args = ['sparse-checkout', 'set', '--no-cone'] + @resource.value(:includes)
434+
exec_git(*args)
435+
end
436+
else
437+
at_path do
438+
exec_git('config', '--local', '--bool', 'core.sparseCheckout', 'true')
439+
440+
# Includes may be an Array or a String
441+
File.open('.git/info/sparse-checkout', 'w') do |f|
442+
if @resource.value(:includes).respond_to?(:each)
443+
@resource.value(:includes).each { |inc| f.puts inc }
444+
else
445+
f.puts @resource.value(:includes)
446+
end
447+
end
448+
end
449+
end
450+
end
451+
410452
# @!visibility private
411453
def commits?
412454
at_path do
@@ -493,7 +535,7 @@ def tags
493535
def set_excludes
494536
# Excludes may be an Array or a String.
495537
at_path do
496-
open('.git/info/exclude', 'w') do |f|
538+
File.open('.git/info/exclude', 'w') do |f|
497539
if @resource.value(:excludes).respond_to?(:each)
498540
@resource.value(:excludes).each { |ex| f.puts ex }
499541
else

spec/unit/puppet/provider/vcsrepo/git_spec.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def branch_a_list(include_branch = nil?)
2525

2626
let(:provider) { resource.provider }
2727

28+
let(:test_includes) { ['file1', 'path1/'] }
29+
2830
before :each do
2931
allow(Puppet::Util).to receive(:which).with('git').and_return('/usr/bin/git')
3032
end
@@ -713,4 +715,64 @@ def branch_a_list(include_branch = nil?)
713715
end
714716
end
715717
end
718+
719+
describe 'includes' do
720+
context 'when with an ensure of bare and includes are defined' do
721+
it 'raises an error when trying to clone a repo with an ensure of bare' do
722+
resource.delete(:revision)
723+
resource[:ensure] = :bare
724+
resource[:includes] = test_includes
725+
expect(provider).to receive(:exec_git).with('clone', '--bare', resource.value(:source), resource.value(:path))
726+
expect(provider).to receive(:update_remotes)
727+
expect { provider.create }.to raise_error(RuntimeError, %r{Cannot set includes on a bare repository})
728+
end
729+
end
730+
731+
context 'when with an ensure of mirror and includes are defined' do
732+
it 'raises an error when trying to clone a repo with an ensure of mirror' do
733+
resource.delete(:revision)
734+
resource[:ensure] = :mirror
735+
resource[:includes] = test_includes
736+
expect(provider).to receive(:exec_git).with('clone', '--mirror', resource.value(:source), resource.value(:path))
737+
expect(provider).to receive(:update_remotes)
738+
expect { provider.create }.to raise_error(RuntimeError, %r{Cannot set includes on a mirror repository})
739+
end
740+
end
741+
742+
context 'when with an ensure of present and includes are defined' do
743+
let(:sparse_checkout_file) { StringIO.new }
744+
745+
it 'performs a sparse checkout with git >= 2.25.0' do
746+
resource[:includes] = test_includes
747+
expect(Dir).to receive(:chdir).with('/').once.and_yield
748+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
749+
expect(provider).to receive(:exec_git).with('clone', resource.value(:source), resource.value(:path))
750+
expect(provider).to receive(:update_remotes)
751+
expect(provider).to receive(:exec_git).with('--version').and_return('2.36.1')
752+
expect(provider).to receive(:exec_git).with('sparse-checkout', 'set', '--no-cone', *resource.value(:includes))
753+
expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision))
754+
expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision)))
755+
expect(provider).to receive(:update_submodules)
756+
provider.create
757+
end
758+
759+
it 'performs a sparse checkout with git < 2.25.0' do
760+
resource[:includes] = test_includes
761+
expect(Dir).to receive(:chdir).with('/').once.and_yield
762+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
763+
expect(provider).to receive(:exec_git).with('clone', resource.value(:source), resource.value(:path))
764+
expect(provider).to receive(:update_remotes)
765+
expect(provider).to receive(:exec_git).with('--version').and_return('1.8.3.1')
766+
expect(provider).to receive(:exec_git).with('config', '--local', '--bool', 'core.sparseCheckout', 'true')
767+
expect(File).to receive(:open).with('.git/info/sparse-checkout', 'w').and_yield(sparse_checkout_file)
768+
resource.value(:includes).each do |inc|
769+
expect(sparse_checkout_file).to receive(:puts).with(inc)
770+
end
771+
expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision))
772+
expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision)))
773+
expect(provider).to receive(:update_submodules)
774+
provider.create
775+
end
776+
end
777+
end
716778
end

0 commit comments

Comments
 (0)