From d6fb3dfad89fce2071e39d09ed06751e157440de Mon Sep 17 00:00:00 2001 From: Jon-Paul Lindquist Date: Thu, 2 May 2024 10:22:46 -0700 Subject: [PATCH 1/3] Enable Git Includes (Sparse Checkout) --- lib/puppet/provider/vcsrepo/git.rb | 46 ++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb index 306013d3..4ccac321 100644 --- a/lib/puppet/provider/vcsrepo/git.rb +++ b/lib/puppet/provider/vcsrepo/git.rb @@ -7,7 +7,7 @@ has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth, :branch, :submodules, :safe_directory, :hooks_allowed, - :umask, :http_proxy, :tmpdir + :umask, :http_proxy, :tmpdir, :include_paths def create check_force @@ -19,6 +19,7 @@ def create set_mirror if @resource.value(:ensure) == :mirror && @resource.value(:source).is_a?(Hash) self.skip_hooks = @resource.value(:skip_hooks) unless @resource.value(:skip_hooks).nil? + configure_sparse_checkout if @resource.value(:includes) checkout if @resource.value(:revision) update_submodules if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true @@ -91,6 +92,20 @@ def revision=(desired) update_owner_and_excludes end + def includes + return nil if bare_exists? + + at_path do + return nil unless File.file?('.git/info/sparse-checkout') + File.readlines('.git/info/sparse-checkout').map(&:chomp) + end + end + + def includes=(_desired) + configure_sparse_checkout + checkout + end + def bare_exists? bare_git_config_exists? && !working_copy_exists? end @@ -407,6 +422,33 @@ def init_repository end end + # @!visibility private + def configure_sparse_checkout + raise("Cannot set includes on a #{@resource.value(:ensure)} repository") if ensure_bare_or_mirror? || bare_exists? + + git_ver = git_version + if Gem::Version.new(git_ver) >= Gem::Version.new('2.25.0') + # sparse-checkout command was introduced in version 2.25.0. + at_path do + args = ['sparse-checkout', 'set', '--no-cone'] + @resource.value(:includes) + exec_git(*args) + end + else + at_path do + exec_git('config', '--local', '--bool', 'core.sparseCheckout', 'true') + + # Includes may be an Array or a String + File.open('.git/info/sparse-checkout', 'w') do |f| + if @resource.value(:includes).respond_to?(:each) + @resource.value(:includes).each { |inc| f.puts inc } + else + f.puts @resource.value(:includes) + end + end + end + end + end + # @!visibility private def commits? at_path do @@ -493,7 +535,7 @@ def tags def set_excludes # Excludes may be an Array or a String. at_path do - open('.git/info/exclude', 'w') do |f| + File.open('.git/info/exclude', 'w') do |f| if @resource.value(:excludes).respond_to?(:each) @resource.value(:excludes).each { |ex| f.puts ex } else From 21c388d60b4f97477b3c0eeb615fdafafcf83b8d Mon Sep 17 00:00:00 2001 From: Jon-Paul Lindquist Date: Thu, 2 May 2024 10:23:12 -0700 Subject: [PATCH 2/3] Update Documentation / Examples for Git Includes --- README.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad3bca0b..74010fcf 100644 --- a/README.md +++ b/README.md @@ -665,25 +665,39 @@ vcsrepo { '/path/to/repo': ####Checking out only specific paths -**Note:** The `includes` param is only supported when subversion client version is >= 1.6. +**Note:** The `includes` param is supported on all git clients, and subversion clients with version >= 1.6. You can check out only specific paths in a particular repository by providing their relative paths to the `includes` parameter, like so: -~~~ +~~~ puppet vcsrepo { '/path/to/repo': ensure => present, provider => svn, source => 'http://svnrepo/hello/trunk', includes => [ 'root-file.txt', + 'file/this-file.txt', 'checkout-folder', + 'folder/this-folder/', + ] +} +~~~ + +~~~ puppet +vcsrepo { '/path/to/repo': + ensure => present, + provider => git, + source => 'git@example.com:project.git', + includes => [ + 'root-file.txt', 'file/this-file.txt', + 'checkout-folder', 'folder/this-folder/', ] } ~~~ -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. +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. 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. @@ -843,7 +857,7 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf * `depth` - Supports shallow clones in `git` or sets the scope limit in `svn`. (Available with `git` and `svn`.) * `filesystem_types` - Supports multiple types of filesystem. (Available with `svn`.) * `gzip_compression` - Supports explicit GZip compression levels. (Available with `cvs`.) -* `include_paths` - Lets you checkout only certain paths. (Available with `svn`.) +* `include_paths` - Lets you checkout only certain paths. (Available with `git` and `svn`.) * `modules` - Lets you choose a specific repository module. (Available with `cvs`.) * `multiple_remotes` - Tracks multiple remote repositories. (Available with `git`.) * `reference_tracking` - Lets you track revision references that can change over time (e.g., some VCS tags and branch names). (Available with all providers) From 56744131b3481873a73cc78958cc2ec4d05af72b Mon Sep 17 00:00:00 2001 From: Jon-Paul Lindquist Date: Thu, 2 May 2024 10:23:22 -0700 Subject: [PATCH 3/3] Add Tests for Git Includes --- spec/unit/puppet/provider/vcsrepo/git_spec.rb | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index dd2b188f..3cce3ddf 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -25,6 +25,8 @@ def branch_a_list(include_branch = nil?) let(:provider) { resource.provider } + let(:test_includes) { ['file1', 'path1/'] } + before :each do allow(Puppet::Util).to receive(:which).with('git').and_return('/usr/bin/git') end @@ -713,4 +715,64 @@ def branch_a_list(include_branch = nil?) end end end + + describe 'includes' do + context 'when with an ensure of bare and includes are defined' do + it 'raises an error when trying to clone a repo with an ensure of bare' do + resource.delete(:revision) + resource[:ensure] = :bare + resource[:includes] = test_includes + expect(provider).to receive(:exec_git).with('clone', '--bare', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:update_remotes) + expect { provider.create }.to raise_error(RuntimeError, %r{Cannot set includes on a bare repository}) + end + end + + context 'when with an ensure of mirror and includes are defined' do + it 'raises an error when trying to clone a repo with an ensure of mirror' do + resource.delete(:revision) + resource[:ensure] = :mirror + resource[:includes] = test_includes + expect(provider).to receive(:exec_git).with('clone', '--mirror', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:update_remotes) + expect { provider.create }.to raise_error(RuntimeError, %r{Cannot set includes on a mirror repository}) + end + end + + context 'when with an ensure of present and includes are defined' do + let(:sparse_checkout_file) { StringIO.new } + + it 'performs a sparse checkout with git >= 2.25.0' do + resource[:includes] = test_includes + expect(Dir).to receive(:chdir).with('/').once.and_yield + expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield + expect(provider).to receive(:exec_git).with('clone', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:update_remotes) + expect(provider).to receive(:exec_git).with('--version').and_return('2.36.1') + expect(provider).to receive(:exec_git).with('sparse-checkout', 'set', '--no-cone', *resource.value(:includes)) + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) + expect(provider).to receive(:update_submodules) + provider.create + end + + it 'performs a sparse checkout with git < 2.25.0' do + resource[:includes] = test_includes + expect(Dir).to receive(:chdir).with('/').once.and_yield + expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield + expect(provider).to receive(:exec_git).with('clone', resource.value(:source), resource.value(:path)) + expect(provider).to receive(:update_remotes) + expect(provider).to receive(:exec_git).with('--version').and_return('1.8.3.1') + expect(provider).to receive(:exec_git).with('config', '--local', '--bool', 'core.sparseCheckout', 'true') + expect(File).to receive(:open).with('.git/info/sparse-checkout', 'w').and_yield(sparse_checkout_file) + resource.value(:includes).each do |inc| + expect(sparse_checkout_file).to receive(:puts).with(inc) + end + expect(provider).to receive(:exec_git).with('checkout', '--force', resource.value(:revision)) + expect(provider).to receive(:exec_git).with('branch', '--no-color', '-a').and_return(branch_a_list(resource.value(:revision))) + expect(provider).to receive(:update_submodules) + provider.create + end + end + end end