The options provide a prompt to the user before permanent deletion are made.
The prompt is currently not aware of directory deletion. I'll fix this in the
next changesets.
( )
Alphare |
hg-reviewers |
The options provide a prompt to the user before permanent deletion are made.
The prompt is currently not aware of directory deletion. I'll fix this in the
next changesets.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | hgext/purge.py (3 lines) | |||
M | mercurial/merge.py (22 lines) | |||
M | tests/test-purge.t (17 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
b7657d36e4a3 | 7c1367c0b5bc | Pierre-Yves David | Jan 18 2021, 4:24 AM |
b'0', | b'0', | ||||
b'print0', | b'print0', | ||||
None, | None, | ||||
_( | _( | ||||
b'end filenames with NUL, for use with xargs' | b'end filenames with NUL, for use with xargs' | ||||
b' (implies -p/--print)' | b' (implies -p/--print)' | ||||
), | ), | ||||
), | ), | ||||
(b'', b'confirm', None, _(b'ask before permanently deleting files')), | |||||
] | ] | ||||
+ cmdutil.walkopts, | + cmdutil.walkopts, | ||||
_(b'hg purge [OPTION]... [DIR]...'), | _(b'hg purge [OPTION]... [DIR]...'), | ||||
helpcategory=command.CATEGORY_WORKING_DIRECTORY, | helpcategory=command.CATEGORY_WORKING_DIRECTORY, | ||||
) | ) | ||||
def purge(ui, repo, *dirs, **opts): | def purge(ui, repo, *dirs, **opts): | ||||
"""removes files not tracked by Mercurial | """removes files not tracked by Mercurial | ||||
ignored = True | ignored = True | ||||
unknown = True | unknown = True | ||||
else: | else: | ||||
ignored = opts.get(b'ignored', False) | ignored = opts.get(b'ignored', False) | ||||
unknown = not ignored | unknown = not ignored | ||||
removefiles = opts.get(b'files') | removefiles = opts.get(b'files') | ||||
removedirs = opts.get(b'dirs') | removedirs = opts.get(b'dirs') | ||||
confirm = opts.get(b'confirm') | |||||
if not removefiles and not removedirs: | if not removefiles and not removedirs: | ||||
removefiles = True | removefiles = True | ||||
removedirs = True | removedirs = True | ||||
match = scmutil.match(repo[None], dirs, opts) | match = scmutil.match(repo[None], dirs, opts) | ||||
paths = mergemod.purge( | paths = mergemod.purge( | ||||
repo, | repo, | ||||
match, | match, | ||||
unknown=unknown, | unknown=unknown, | ||||
ignored=ignored, | ignored=ignored, | ||||
removeemptydirs=removedirs, | removeemptydirs=removedirs, | ||||
removefiles=removefiles, | removefiles=removefiles, | ||||
abortonerror=opts.get(b'abort_on_err'), | abortonerror=opts.get(b'abort_on_err'), | ||||
noop=not act, | noop=not act, | ||||
confirm=confirm, | |||||
) | ) | ||||
for path in paths: | for path in paths: | ||||
if not act: | if not act: | ||||
ui.write(b'%s%s' % (path, eol)) | ui.write(b'%s%s' % (path, eol)) |
repo, | repo, | ||||
matcher, | matcher, | ||||
unknown=True, | unknown=True, | ||||
ignored=False, | ignored=False, | ||||
removeemptydirs=True, | removeemptydirs=True, | ||||
removefiles=True, | removefiles=True, | ||||
abortonerror=False, | abortonerror=False, | ||||
noop=False, | noop=False, | ||||
confirm=False, | |||||
): | ): | ||||
"""Purge the working directory of untracked files. | """Purge the working directory of untracked files. | ||||
``matcher`` is a matcher configured to scan the working directory - | ``matcher`` is a matcher configured to scan the working directory - | ||||
potentially a subset. | potentially a subset. | ||||
``unknown`` controls whether unknown files should be purged. | ``unknown`` controls whether unknown files should be purged. | ||||
``ignored`` controls whether ignored files should be purged. | ``ignored`` controls whether ignored files should be purged. | ||||
``removeemptydirs`` controls whether empty directories should be removed. | ``removeemptydirs`` controls whether empty directories should be removed. | ||||
``removefiles`` controls whether files are removed. | ``removefiles`` controls whether files are removed. | ||||
``abortonerror`` causes an exception to be raised if an error occurs | ``abortonerror`` causes an exception to be raised if an error occurs | ||||
deleting a file or directory. | deleting a file or directory. | ||||
``noop`` controls whether to actually remove files. If not defined, actions | ``noop`` controls whether to actually remove files. If not defined, actions | ||||
will be taken. | will be taken. | ||||
``confirm`` ask confirmation before actually removing anything. | |||||
Returns an iterable of relative paths in the working directory that were | Returns an iterable of relative paths in the working directory that were | ||||
or would be removed. | or would be removed. | ||||
""" | """ | ||||
def remove(removefn, path): | def remove(removefn, path): | ||||
try: | try: | ||||
removefn(path) | removefn(path) | ||||
except OSError: | except OSError: | ||||
try: | try: | ||||
if removeemptydirs: | if removeemptydirs: | ||||
directories = [] | directories = [] | ||||
matcher.traversedir = directories.append | matcher.traversedir = directories.append | ||||
status = repo.status(match=matcher, ignored=ignored, unknown=unknown) | status = repo.status(match=matcher, ignored=ignored, unknown=unknown) | ||||
if confirm: | |||||
nb_ignored = len(status.ignored) | |||||
nb_unkown = len(status.unknown) | |||||
if nb_unkown and nb_ignored: | |||||
msg = _(b"permanently delete %d unkown and %d ignored files?") | |||||
msg %= (nb_unkown, nb_ignored) | |||||
elif nb_unkown: | |||||
msg = _(b"permanently delete %d unkown files?") | |||||
msg %= nb_unkown | |||||
elif nb_ignored: | |||||
msg = _(b"permanently delete %d ignored files?") | |||||
msg %= nb_ignored | |||||
else: | |||||
# XXX we might be missing directory there | |||||
return res | |||||
msg += b" (yN)$$ &Yes $$ &No" | |||||
if repo.ui.promptchoice(msg, default=1) == 1: | |||||
raise error.CanceledError(_(b'removal cancelled')) | |||||
if removefiles: | if removefiles: | ||||
for f in sorted(status.unknown + status.ignored): | for f in sorted(status.unknown + status.ignored): | ||||
if not noop: | if not noop: | ||||
repo.ui.note(_(b'removing file %s\n') % f) | repo.ui.note(_(b'removing file %s\n') % f) | ||||
remove(repo.wvfs.unlink, f) | remove(repo.wvfs.unlink, f) | ||||
res.append(f) | res.append(f) | ||||
if removeemptydirs: | if removeemptydirs: |
> import os | > import os | ||||
> import stat | > import stat | ||||
> f = 'untracked_file_readonly' | > f = 'untracked_file_readonly' | ||||
> os.chmod(f, stat.S_IMODE(os.stat(f).st_mode) & ~stat.S_IWRITE) | > os.chmod(f, stat.S_IMODE(os.stat(f).st_mode) & ~stat.S_IWRITE) | ||||
> EOF | > EOF | ||||
$ hg purge -p | $ hg purge -p | ||||
untracked_file | untracked_file | ||||
untracked_file_readonly | untracked_file_readonly | ||||
$ hg purge --confirm | |||||
permanently delete 2 unkown files? (yN) n | |||||
abort: removal cancelled | |||||
[250] | |||||
$ hg purge -v | $ hg purge -v | ||||
removing file untracked_file | removing file untracked_file | ||||
removing file untracked_file_readonly | removing file untracked_file_readonly | ||||
$ ls -A | $ ls -A | ||||
.hg | .hg | ||||
.hgignore | .hgignore | ||||
directory | directory | ||||
r1 | r1 | ||||
delete only part of the tree | delete only part of the tree | ||||
$ mkdir -p untracked_directory/nested_directory | $ mkdir -p untracked_directory/nested_directory | ||||
$ touch directory/untracked_file | $ touch directory/untracked_file | ||||
$ cd directory | $ cd directory | ||||
$ hg purge -p ../untracked_directory | $ hg purge -p ../untracked_directory | ||||
untracked_directory/nested_directory | untracked_directory/nested_directory | ||||
$ hg purge --confirm | |||||
permanently delete 1 unkown files? (yN) n | |||||
abort: removal cancelled | |||||
[250] | |||||
$ hg purge -v ../untracked_directory | $ hg purge -v ../untracked_directory | ||||
removing directory untracked_directory/nested_directory | removing directory untracked_directory/nested_directory | ||||
removing directory untracked_directory | removing directory untracked_directory | ||||
$ cd .. | $ cd .. | ||||
$ ls -A | $ ls -A | ||||
.hg | .hg | ||||
.hgignore | .hgignore | ||||
directory | directory | ||||
r1 | r1 | ||||
$ ls directory/untracked_file | $ ls directory/untracked_file | ||||
directory/untracked_file | directory/untracked_file | ||||
$ rm directory/untracked_file | $ rm directory/untracked_file | ||||
skip ignored files if -i or --all not specified | skip ignored files if -i or --all not specified | ||||
$ touch ignored | $ touch ignored | ||||
$ hg purge -p | $ hg purge -p | ||||
$ hg purge --confirm | |||||
$ hg purge -v | $ hg purge -v | ||||
$ touch untracked_file | $ touch untracked_file | ||||
$ ls | $ ls | ||||
directory | directory | ||||
ignored | ignored | ||||
r1 | r1 | ||||
untracked_file | untracked_file | ||||
$ hg purge -p -i | $ hg purge -p -i | ||||
ignored | ignored | ||||
$ hg purge --confirm -i | |||||
permanently delete 1 ignored files? (yN) n | |||||
abort: removal cancelled | |||||
[250] | |||||
$ hg purge -v -i | $ hg purge -v -i | ||||
removing file ignored | removing file ignored | ||||
$ ls -A | $ ls -A | ||||
.hg | .hg | ||||
.hgignore | .hgignore | ||||
directory | directory | ||||
r1 | r1 | ||||
untracked_file | untracked_file | ||||
$ touch ignored | $ touch ignored | ||||
$ hg purge -p --all | $ hg purge -p --all | ||||
ignored | ignored | ||||
untracked_file | untracked_file | ||||
$ hg purge --confirm --all | |||||
permanently delete 1 unkown and 1 ignored files? (yN) n | |||||
abort: removal cancelled | |||||
[250] | |||||
$ hg purge -v --all | $ hg purge -v --all | ||||
removing file ignored | removing file ignored | ||||
removing file untracked_file | removing file untracked_file | ||||
$ ls | $ ls | ||||
directory | directory | ||||
r1 | r1 | ||||
abort with missing files until we support name mangling filesystems | abort with missing files until we support name mangling filesystems |