changeset 39480:7fea205fd5dc

merge: move purge logic from extension Working directory purging feels like functionality that should be in core rather than in an extension. This commit effectively moves the core purging logic from the purge extension to merge.py. Code was refactored slightly. Rather than deal with printing in this function, the function is a generator of paths and the caller can worry about printing. Differential Revision: https://phab.mercurial-scm.org/D4477
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 06 Sep 2018 18:30:12 -0700
parents e5449ff273d6
children 3dd34b401bc2
files hgext/purge.py mercurial/merge.py
diffstat 2 files changed, 82 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/purge.py	Thu Sep 06 23:37:24 2018 -0400
+++ b/hgext/purge.py	Thu Sep 06 18:30:12 2018 -0700
@@ -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))
--- a/mercurial/merge.py	Thu Sep 06 23:37:24 2018 -0400
+++ b/mercurial/merge.py	Thu Sep 06 18:30:12 2018 -0700
@@ -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