Skip to content

Commit 801074a

Browse files
committed
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.
1 parent 5ba1953 commit 801074a

File tree

4 files changed

+146
-2
lines changed

4 files changed

+146
-2
lines changed

README.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,28 @@ It only comes into effect if the revision parameter is different from the local
179179

180180
**WARNING:** This overwrites any conflicting local changes to the repository.
181181

182+
To remove all un-committed changes in the local repository and submodules, set `repository_status` to `default_clean`.
183+
184+
~~~ puppet
185+
vcsrepo { '/path/to/repo':
186+
ensure => present,
187+
provider => git,
188+
source => 'git://example.com/repo.git',
189+
repository_status => 'default_clean',
190+
}
191+
~~~
192+
193+
The `default_clean` value directs vcsrepo to run commands necessary to ensure
194+
that the status as reported by `git status` will not report any local changes.
195+
This does not affect files specified in the `.gitignore` file; future versions
196+
of vcsrepo may support more agressive cleaning if necessary, but this will not
197+
be default. Note that when this parameter is is in use, the
198+
`keep_local_changes` parameter has no net effect.
199+
200+
This parameter respects the `submodules` option; when submodules are enabled,
201+
the `default_clean` value will cause submodules to be cleaned as well and reset
202+
to the commit specified by the containing repo.
203+
182204
To keep the repository at the latest revision, set `ensure` to 'latest':
183205

184206
~~~ puppet
@@ -797,7 +819,7 @@ For information on the classes and types, see the [REFERENCE.md](https://github.
797819

798820
##### `git` - Supports the Git VCS.
799821

800-
Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user`
822+
Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user`, `working_copy_status'
801823

802824
Parameters: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user`
803825

@@ -852,6 +874,7 @@ Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conf
852874
* `ssh_identity` - Lets you specify an SSH identity file. (Available with `git` and `hg`.)
853875
* `submodules` - Supports repository submodules which can be optionally initialized. (Available with `git`.)
854876
* `user` - Can run as a different user. (Available with `git`, `hg` and `cvs`.)
877+
* `working_copy_status` - Can enforce the status of a working copy. (Available with `git`.)
855878

856879
<a id="limitations"></a>
857880
## Limitations

lib/puppet/provider/vcsrepo/git.rb

+49-1
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, :repository_status
1111

1212
def create
1313
check_force
@@ -72,6 +72,8 @@ def revision
7272
# @param [String] desired The desired revision to which the repo should be
7373
# set.
7474
def revision=(desired)
75+
# Set the working copy status first
76+
set_repository_status(@resource.value(:repository_status))
7577
# just checkout tags and shas; fetch has already happened so they should be updated.
7678
checkout(desired)
7779
# branches require more work.
@@ -232,6 +234,52 @@ def update_references
232234
end
233235
end
234236

237+
# Return the status of the working copy.
238+
def repository_status
239+
# Optimization: if we don't care about the status, then return right away.
240+
# This avoids running 'git status', which may be costly on very large repos
241+
# on slow, uncached filesystems.
242+
if @resource.value(:repository_status) == :ignore
243+
return :ignore
244+
end
245+
246+
at_path do
247+
# 'git status' ignores files specified in .gitignore.
248+
status = if @resource.value(:submodules) == :true
249+
exec_git('status', '--porcelain')
250+
else
251+
exec_git('status', '--porcelain', '--ignore-submodules')
252+
end
253+
254+
return :default_clean if status.empty?
255+
return :default_dirty
256+
end
257+
end
258+
259+
def repository_status=(desired)
260+
set_repository_status(desired)
261+
end
262+
263+
def set_repository_status(desired)
264+
case desired
265+
when :default_clean
266+
at_path do
267+
exec_git('clean', '-fd')
268+
exec_git('reset', '--hard', 'HEAD')
269+
if @resource.value(:submodules) == :true
270+
exec_git('submodule', 'foreach', '--recursive', 'git', 'clean', '-fd')
271+
exec_git('submodule', 'foreach', '--recursive', 'git', 'reset', '--hard', 'HEAD')
272+
# Ensure that submodules are on the revision specified by the containing repo.
273+
update_submodules
274+
end
275+
end
276+
when :ignore
277+
# nothing to do (rubocop requires code or a comment here)
278+
else
279+
raise Puppet::Error, "Desired repository_status not implemented: #{desired}"
280+
end
281+
end
282+
235283
# Convert working copy to bare
236284
#
237285
# Moves:

lib/puppet/type/vcsrepo.rb

+17
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
feature :tmpdir,
7777
'The provider supports setting the temp directory used for wrapper scripts.'
7878

79+
feature :repository_status,
80+
'The provider supports setting the local repository status (to remove uncommitted local changes).'
81+
7982
ensurable do
8083
desc 'Ensure the version control repository.'
8184
attr_accessor :latest
@@ -355,6 +358,20 @@ def insync?(is)
355358
desc 'The temp directory used for wrapper scripts.'
356359
end
357360

361+
newproperty :repository_status, required_features: [:repository_status] do
362+
newvalue :default_clean
363+
newvalue :ignore
364+
defaultto :ignore
365+
366+
def insync?(is)
367+
# unwrap @should
368+
should = @should[0]
369+
return true if should == :ignore
370+
return true if is == should
371+
false
372+
end
373+
end
374+
358375
autorequire(:package) do
359376
['git', 'git-core', 'mercurial', 'subversion']
360377
end

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

+56
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,62 @@ def branch_a_list(include_branch = nil?)
171171
provider.create
172172
end
173173
end
174+
175+
context 'when with an ensure of present - with repository_status of ignore' do
176+
it 'does not check the status' do
177+
resource[:repository_status] = :ignore
178+
expect(provider).not_to receive(:exec_git)
179+
provider.repository_status
180+
end
181+
182+
it 'does not clean' do
183+
expect(provider).not_to receive(:exec_git)
184+
# this calls the setter method
185+
provider.repository_status = :ignore
186+
end
187+
end
188+
189+
context 'when with an ensure of present - with repository_status of default_clean' do
190+
context 'with defaults' do
191+
it 'checks the status' do
192+
resource[:repository_status] = :default_clean
193+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
194+
expect(provider).to receive(:exec_git).with('status', '--porcelain').and_return('')
195+
provider.repository_status
196+
end
197+
198+
it 'cleans the repo' do
199+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
200+
expect(provider).to receive(:exec_git).with('clean', '-fd').and_return('')
201+
expect(provider).to receive(:exec_git).with('reset', '--hard', 'HEAD').and_return('')
202+
expect(provider).to receive(:exec_git).with('submodule', 'foreach', '--recursive', 'git', 'clean', '-fd').and_return('')
203+
expect(provider).to receive(:exec_git).with('submodule', 'foreach', '--recursive', 'git', 'reset', '--hard', 'HEAD').and_return('')
204+
expect(provider).to receive(:update_submodules)
205+
# this calls the setter method
206+
provider.repository_status = :default_clean
207+
end
208+
end
209+
210+
context 'with submodules disabled' do
211+
it 'checks the status' do
212+
resource[:submodules] = :false
213+
resource[:repository_status] = :default_clean
214+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
215+
expect(provider).to receive(:exec_git).with('status', '--porcelain', '--ignore-submodules').and_return('')
216+
provider.repository_status
217+
end
218+
219+
it 'cleans the repo' do
220+
resource[:submodules] = :false
221+
expect(Dir).to receive(:chdir).with('/tmp/test').at_least(:once).and_yield
222+
expect(provider).to receive(:exec_git).with('clean', '-fd').and_return('')
223+
expect(provider).to receive(:exec_git).with('reset', '--hard', 'HEAD').and_return('')
224+
expect(provider).not_to receive(:update_submodules)
225+
# this calls the setter method
226+
provider.repository_status = :default_clean
227+
end
228+
end
229+
end
174230
end
175231

176232
context 'when with an ensure of bare' do

0 commit comments

Comments
 (0)