From eb55a56b07c6a72b5af5122617466127df580cc8 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 10:48:19 -0800 Subject: [PATCH 1/5] sort documented features These were nearly sorted to start with. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad3bca0b..cbd42268 100644 --- a/README.md +++ b/README.md @@ -837,8 +837,8 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf * `bare_repositories` - Differentiates between bare repositories and those with working copies. (Available with `git`.) * `basic_auth` - Supports HTTP Basic authentication. (Available with `hg` and `svn`.) -* `conflict` - Lets you decide how to resolve any conflicts between the source repository and your working copy. (Available with `svn`.) * `configuration` - Lets you specify the location of your configuration files. (Available with `svn`.) +* `conflict` - Lets you decide how to resolve any conflicts between the source repository and your working copy. (Available with `svn`.) * `cvs_rsh` - Understands the `CVS_RSH` environment variable. (Available with `cvs`.) * `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`.) @@ -846,11 +846,11 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf * `include_paths` - Lets you checkout only certain paths. (Available with `svn`.) * `modules` - Lets you choose a specific repository module. (Available with `cvs`.) * `multiple_remotes` - Tracks multiple remote repositories. (Available with `git`.) +* `p4config` - Supports setting the `P4CONFIG` environment. (Available with `p4`.) * `reference_tracking` - Lets you track revision references that can change over time (e.g., some VCS tags and branch names). (Available with all providers) * `ssh_identity` - Lets you specify an SSH identity file. (Available with `git` and `hg`.) -* `user` - Can run as a different user. (Available with `git`, `hg` and `cvs`.) -* `p4config` - Supports setting the `P4CONFIG` environment. (Available with `p4`.) * `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`.) ## Limitations From 68b8ba55a8e4d902c87255fc2ae9bd9073fde907 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 12:57:33 -0800 Subject: [PATCH 2/5] fix description of keep_local_changes --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbd42268..92b31b08 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,12 @@ vcsrepo { '/path/to/repo': } ~~~ -To keep the repository at the latest revision, set `ensure` to 'latest'. **Note**: `keep_local_changes` works by stashing local changes, switching the repo to the assigned revision and, finally, unstashing the local changes. It only comes into effect if the revision parameter is different from the local repo. This parameter DOES NOT delete/purge local changes by default on every run. -**WARNING:** This overwrites any local changes to the repository. +**WARNING:** This overwrites any conflicting local changes to the repository. + +To keep the repository at the latest revision, set `ensure` to 'latest': ~~~ puppet vcsrepo { '/path/to/repo': From 82299e4ec8577f3f8f44839b972f079bb4e07fd7 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 13:45:37 -0800 Subject: [PATCH 3/5] fix unit test categorization Before this change, tests for 'ensure' values other than 'present' are nested under 'when with an ensure of present', resulting in: Puppet::Type::Vcsrepo::ProviderGit when with an ensure of present when with an ensure of present - with a revision that is a remote branch executes 'git clone' and 'git checkout -b' [...] when with an ensure of bare - with revision raises an error when with an ensure of bare - without revision justs execute 'git clone --bare' when with an ensure of bare - without a source executes 'git init --bare' when with an ensure of mirror - with revision raises an error when with an ensure of mirror - without revision justs execute 'git clone --mirror' when with an ensure of mirror - without a source raises an exeption when with an ensure of mirror - with multiple remotes executes 'git clone --mirror' and set all remotes to mirror --- spec/unit/puppet/provider/vcsrepo/git_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index dd2b188f..780a3051 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -171,7 +171,9 @@ def branch_a_list(include_branch = nil?) provider.create end end + end + context 'when with an ensure of bare' do context 'when with an ensure of bare - with revision' do it 'raises an error' do resource[:ensure] = :bare @@ -202,7 +204,9 @@ def branch_a_list(include_branch = nil?) provider.create end end + end + context 'when with an ensure of mirror' do context 'when with an ensure of mirror - with revision' do it 'raises an error' do resource[:ensure] = :mirror From 5ba1953ae25785002c58cf999198c899f377bdb0 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 14:17:56 -0800 Subject: [PATCH 4/5] fix test names Neither of these tests set `ensure` to any value. --- spec/unit/puppet/provider/vcsrepo/git_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/spec/unit/puppet/provider/vcsrepo/git_spec.rb index 780a3051..ae745421 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -249,7 +249,7 @@ def branch_a_list(include_branch = nil?) end end - context 'when with an ensure of mirror - when the path is a working copy repository' do + context 'when the path is a working copy repository' do it 'clones overtop it using force' do resource[:force] = true expect(Dir).to receive(:chdir).with('/').once.and_yield @@ -267,7 +267,7 @@ def branch_a_list(include_branch = nil?) end end - context 'when with an ensure of mirror - when the path is not empty and not a repository' do + context 'when the path is not empty and not a repository' do it 'raises an exception' do expect(provider).to receive(:path_exists?).and_return(true) expect(provider).to receive(:path_empty?).and_return(false) From 801074ae32b845aef49fd9453038800fe4c8e0b4 Mon Sep 17 00:00:00 2001 From: Corey Hickey Date: Thu, 28 Dec 2023 09:43:53 -0800 Subject: [PATCH 5/5] support force-cleaning git repos This resets changes to both tracked and un-tracked files. Submodules are cleaned as well, when enabled. For untracked files, '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 | 25 ++++++++- lib/puppet/provider/vcsrepo/git.rb | 50 ++++++++++++++++- lib/puppet/type/vcsrepo.rb | 17 ++++++ spec/unit/puppet/provider/vcsrepo/git_spec.rb | 56 +++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92b31b08..4dd58026 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,28 @@ 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 all un-committed 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 commands necessary to ensure +that the status as reported by `git status` will not report any local changes. +This does not affect files specified in the `.gitignore` file; future versions +of vcsrepo may support more agressive cleaning if necessary, but this will not +be default. Note that when this parameter is is in use, the +`keep_local_changes` parameter has no net effect. + +This parameter respects the `submodules` option; when submodules are enabled, +the `default_clean` value will cause submodules to be cleaned as well and reset +to the commit specified by the containing repo. + To keep the repository at the latest revision, set `ensure` to 'latest': ~~~ puppet @@ -797,7 +819,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 +874,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..8aa04d15 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,52 @@ 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 + # 'git status' ignores files specified in .gitignore. + status = if @resource.value(:submodules) == :true + exec_git('status', '--porcelain') + else + exec_git('status', '--porcelain', '--ignore-submodules') + end + + 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('reset', '--hard', 'HEAD') + if @resource.value(:submodules) == :true + exec_git('submodule', 'foreach', '--recursive', 'git', 'clean', '-fd') + exec_git('submodule', 'foreach', '--recursive', 'git', 'reset', '--hard', 'HEAD') + # Ensure that submodules are on the revision specified by the containing repo. + update_submodules + end + 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..b198294a 100644 --- a/spec/unit/puppet/provider/vcsrepo/git_spec.rb +++ b/spec/unit/puppet/provider/vcsrepo/git_spec.rb @@ -171,6 +171,62 @@ 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 + context 'with defaults' 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('') + expect(provider).to receive(:exec_git).with('reset', '--hard', 'HEAD').and_return('') + expect(provider).to receive(:exec_git).with('submodule', 'foreach', '--recursive', 'git', 'clean', '-fd').and_return('') + expect(provider).to receive(:exec_git).with('submodule', 'foreach', '--recursive', 'git', 'reset', '--hard', 'HEAD').and_return('') + expect(provider).to receive(:update_submodules) + # this calls the setter method + provider.repository_status = :default_clean + end + end + + context 'with submodules disabled' do + it 'checks the status' do + resource[:submodules] = :false + 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', '--ignore-submodules').and_return('') + provider.repository_status + end + + it 'cleans the repo' do + resource[:submodules] = :false + expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield + expect(provider).to receive(:exec_git).with('clean', '-fd').and_return('') + expect(provider).to receive(:exec_git).with('reset', '--hard', 'HEAD').and_return('') + expect(provider).not_to receive(:update_submodules) + # this calls the setter method + provider.repository_status = :default_clean + end + end + end end context 'when with an ensure of bare' do