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.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
M | hgext/purge.py (3 lines) | |||
M | mercurial/merge.py (22 lines) | |||
M | tests/test-purge.t (17 lines) |
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 |