Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Git Includes (Sparse Checkout) #637

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand Down
46 changes: 44 additions & 2 deletions lib/puppet/provider/vcsrepo/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions spec/unit/puppet/provider/vcsrepo/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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