diff --git a/hgext/purge.py b/hgext/purge.py --- a/hgext/purge.py +++ b/hgext/purge.py @@ -25,16 +25,13 @@ '''command to delete untracked files from the working directory''' from __future__ import absolute_import -import os - from mercurial.i18n import _ from mercurial import ( cmdutil, - error, + merge as mergemod, pycompat, registrar, scmutil, - util, ) cmdtable = {} @@ -86,44 +83,28 @@ option. ''' opts = pycompat.byteskwargs(opts) + act = not opts.get('print') eol = '\n' if opts.get('print0'): eol = '\0' act = False # --print0 implies --print + removefiles = opts.get('files') removedirs = opts.get('dirs') + if not removefiles and not removedirs: removefiles = True removedirs = True - def remove(remove_func, name): - if act: - try: - remove_func(repo.wjoin(name)) - except OSError: - m = _('%s cannot be removed') % name - if opts.get('abort_on_err'): - raise error.Abort(m) - ui.warn(_('warning: %s\n') % m) - else: - ui.write('%s%s' % (name, eol)) + match = scmutil.match(repo[None], dirs, opts) - match = scmutil.match(repo[None], dirs, opts) - if removedirs: - directories = [] - match.explicitdir = match.traversedir = directories.append - status = repo.status(match=match, ignored=opts.get('all'), unknown=True) + paths = mergemod.purge( + repo, match, ignored=opts.get('all', False), + removeemptydirs=removedirs, removefiles=removefiles, + abortonerror=opts.get('abort_on_err'), + noop=not act) - if removefiles: - for f in sorted(status.unknown + status.ignored): - if act: - ui.note(_('removing file %s\n') % f) - remove(util.unlink, f) - - if removedirs: - for f in sorted(directories, reverse=True): - if match(f) and not os.listdir(repo.wjoin(f)): - if act: - ui.note(_('removing directory %s\n') % f) - remove(os.rmdir, f) + for path in paths: + if not act: + ui.write('%s%s' % (path, eol)) diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -9,6 +9,7 @@ import errno import hashlib +import os import shutil import struct @@ -2240,3 +2241,71 @@ # fix up dirstate for copies and renames copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev()) return stats + +def purge(repo, matcher, ignored=False, removeemptydirs=True, + removefiles=True, abortonerror=False, noop=False): + """Purge the working directory of untracked files. + + ``matcher`` is a matcher configured to scan the working directory - + potentially a subset. + + ``ignored`` controls whether ignored files should also be purged. + + ``removeemptydirs`` controls whether empty directories should be removed. + + ``removefiles`` controls whether files are removed. + + ``abortonerror`` causes an exception to be raised if an error occurs + deleting a file or directory. + + ``noop`` controls whether to actually remove files. If not defined, actions + will be taken. + + Returns an iterable of relative paths in the working directory that were + or would be removed. + """ + + def remove(removefn, path): + try: + removefn(repo.wvfs.join(path)) + except OSError: + m = _('%s cannot be removed') % path + if abortonerror: + raise error.Abort(m) + else: + repo.ui.warn(_('warning: %s\n') % m) + + # There's no API to copy a matcher. So mutate the passed matcher and + # restore it when we're done. + oldexplicitdir = matcher.explicitdir + oldtraversedir = matcher.traversedir + + res = [] + + try: + if removeemptydirs: + directories = [] + matcher.explicitdir = matcher.traversedir = directories.append + + status = repo.status(match=matcher, ignored=ignored, unknown=True) + + if removefiles: + for f in sorted(status.unknown + status.ignored): + if not noop: + repo.ui.note(_('removing file %s\n') % f) + remove(util.unlink, f) + res.append(f) + + if removeemptydirs: + for f in sorted(directories, reverse=True): + if matcher(f) and not os.listdir(repo.wvfs.join(f)): + if not noop: + repo.ui.note(_('removing directory %s\n') % f) + remove(os.rmdir, f) + res.append(f) + + return res + + finally: + matcher.explicitdir = oldexplicitdir + matcher.traversedir = oldtraversedir