From 17706d4030ab0952371b243feb3974e62625bf53 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 09:43:53 -0800 Subject: [PATCH] support force-cleaning git repos 'git clean -fd' should have the most reasonably expected result (files in .gitignore are still ignored). This change is written to support other cleaning methods in the future, if desired. --- README.md | 19 ++++++++- lib/puppet/provider/vcsrepo/git.rb | 39 ++++++++++++++++++- lib/puppet/type/vcsrepo.rb | 17 ++++++++ spec/unit/puppet/provider/vcsrepo/git_spec.rb | 30 ++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92b31b08..db9e40f0 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,22 @@ It only comes into effect if the revision parameter is different from the local **WARNING:** This overwrites any conflicting local changes to the repository. +To remove untracked changes in the local repository and submodules, set `repository_status` to `default_clean`. + +~~~ puppet +vcsrepo { '/path/to/repo': + ensure => present, + provider => git, + source => 'git://example.com/repo.git', + repository_status => 'default_clean', +} +~~~ + +The `default_clean` value directs vcsrepo to run `git clean -fd`, which does +not affect files specified in the `.gitignore` file; future versions of +vcsrepo may support other values for `repository_status`. Note that when this +is in use, the `keep_local_changes` parameter has no net effect. + To keep the repository at the latest revision, set `ensure` to 'latest': ~~~ puppet @@ -797,7 +813,7 @@ For information on the classes and types, see the [REFERENCE.md](https://github. ##### `git` - Supports the Git VCS. -Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user` +Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user`, `working_copy_status' Parameters: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user` @@ -852,6 +868,7 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf * `ssh_identity` - Lets you specify an SSH identity file. (Available with `git` and `hg`.) * `submodules` - Supports repository submodules which can be optionally initialized. (Available with `git`.) * `user` - Can run as a different user. (Available with `git`, `hg` and `cvs`.) +* `working_copy_status` - Can enforce the status of a working copy. (Available with `git`.) ## Limitations diff --git a/lib/puppet/provider/vcsrepo/git.rb b/lib/puppet/provider/vcsrepo/git.rb index 306013d3..18b71ca2 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, :repository_status def create check_force @@ -72,6 +72,8 @@ def revision # @param [String] desired The desired revision to which the repo should be # set. def revision=(desired) + # Set the working copy status first + set_repository_status(@resource.value(:repository_status)) # just checkout tags and shas; fetch has already happened so they should be updated. checkout(desired) # branches require more work. @@ -232,6 +234,41 @@ def update_references end end + # Return the status of the working copy. + def repository_status + # Optimization: if we don't care about the status, then return right away. + # This avoids running 'git status', which may be costly on very large repos + # on slow, uncached filesystems. + if @resource.value(:repository_status) == :ignore + return :ignore + end + + at_path do + # This allows files specified in .gitignore. + status = exec_git('status', '--porcelain') + return :default_clean if status.empty? + return :default_dirty + end + end + + def repository_status=(desired) + set_repository_status(desired) + end + + def set_repository_status(desired) + case desired + when :default_clean + at_path do + exec_git('clean', '-fd') + exec_git('submodule', 'foreach', '--recursive', 'git', 'clean', '-fd') + end + when :ignore + # nothing to do (rubocop requires code or a comment here) + else + raise Puppet::Error, "Desired repository_status not implemented: #{desired}" + end + end + # Convert working copy to bare # # Moves: diff --git a/lib/puppet/type/vcsrepo.rb b/lib/puppet/type/vcsrepo.rb index 31ed3b62..95b3bcfa 100644 --- a/lib/puppet/type/vcsrepo.rb +++ b/lib/puppet/type/vcsrepo.rb @@ -76,6 +76,9 @@ feature :tmpdir, 'The provider supports setting the temp directory used for wrapper scripts.' + feature :repository_status, + 'The provider supports setting the local repository status (to remove uncommitted local changes).' + ensurable do desc 'Ensure the version control repository.' attr_accessor :latest @@ -355,6 +358,20 @@ def insync?(is) desc 'The temp directory used for wrapper scripts.' end + newproperty :repository_status, required_features: [:repository_status] do + newvalue :default_clean + newvalue :ignore + defaultto :ignore + + def insync?(is) + # unwrap @should + should = @should[0] + return true if should == :ignore + return true if is == should + false + end + end + autorequire(:package) do ['git', 'git-core', 'mercurial', 'subversion'] end diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index ae745421..96fe5b7c 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -171,6 +171,36 @@ def branch_a_list(include_branch = nil?) provider.create end end + + context 'when with an ensure of present - with repository_status of ignore' do + it 'does not check the status' do + resource[:repository_status] = :ignore + expect(provider).not_to receive(:exec_git) + provider.repository_status + end + + it 'does not clean' do + expect(provider).not_to receive(:exec_git) + # this calls the setter method + provider.repository_status = :ignore + end + end + + context 'when with an ensure of present - with repository_status of default_clean' do + it 'checks the status' do + resource[:repository_status] = :default_clean + expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield + expect(provider).to receive(:exec_git).with('status', '--porcelain').and_return('') + provider.repository_status + end + + it 'cleans the repo' do + expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield + expect(provider).to receive(:exec_git).with('clean', '-fd').and_return('') + # this calls the setter method + provider.repository_status = :default_clean + end + end end context 'when with an ensure of bare' do