Patchwork [1,of,3,V2] remove: recurse into subrepositories with --subrepos/-S flag

login
register
mail settings
Submitter Matt Harbison
Date Nov. 16, 2014, 6:16 a.m.
Message ID <a35171de7e3c42d7bdcb.1416118591@Envy>
Download mbox | patch
Permalink /patch/6745/
State Accepted
Commit 4165cfd675193a0b223e109d781b1c405e8c1ee0
Headers show

Comments

Matt Harbison - Nov. 16, 2014, 6:16 a.m.
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1416105379 18000
#      Sat Nov 15 21:36:19 2014 -0500
# Node ID a35171de7e3c42d7bdcbc3717e6f8fb620a76bb0
# Parent  991098579940552536d0a99fa3602dd1661aa388
remove: recurse into subrepositories with --subrepos/-S flag

Like 'forget', git and svn subrepos are currently not supported.  Unfortunately
the name 'remove' is already used in the subrepo classes, so we break the
convention of naming the subrepo function after the command.

Patch

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2052,21 +2052,44 @@ 
     forgot.extend(forget)
     return bad, forgot
 
-def remove(ui, repo, m, after, force):
+def remove(ui, repo, m, prefix, after, force, subrepos):
+    join = lambda f: os.path.join(prefix, f)
     ret = 0
     s = repo.status(match=m, clean=True)
     modified, added, deleted, clean = s[0], s[1], s[3], s[6]
 
+    wctx = repo[None]
+
+    if subrepos:
+        for subpath in sorted(wctx.substate):
+            sub = wctx.sub(subpath)
+            try:
+                submatch = matchmod.narrowmatcher(subpath, m)
+                if sub.removefiles(ui, submatch, prefix, after, force,
+                                   subrepos):
+                    ret = 1
+            except error.LookupError:
+                ui.status(_("skipping missing subrepository: %s\n")
+                               % join(subpath))
+
     # warn about failure to delete explicit files/dirs
-    wctx = repo[None]
     for f in m.files():
-        if f in repo.dirstate or f in wctx.dirs():
+        def insubrepo():
+            for subpath in wctx.substate:
+                if f.startswith(subpath):
+                    return True
+            return False
+
+        if f in repo.dirstate or f in wctx.dirs() or (subrepos and insubrepo()):
             continue
-        if os.path.exists(m.rel(f)):
-            if os.path.isdir(m.rel(f)):
-                ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
+
+        if os.path.exists(m.rel(join(f))):
+            if os.path.isdir(m.rel(join(f))):
+                ui.warn(_('not removing %s: no tracked files\n')
+                        % m.rel(join(f)))
             else:
-                ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
+                ui.warn(_('not removing %s: file is untracked\n')
+                        % m.rel(join(f)))
         # missing files will generate a warning elsewhere
         ret = 1
 
@@ -2075,22 +2098,22 @@ 
     elif after:
         list = deleted
         for f in modified + added + clean:
-            ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
+            ui.warn(_('not removing %s: file still exists\n') % m.rel(join(f)))
             ret = 1
     else:
         list = deleted + clean
         for f in modified:
             ui.warn(_('not removing %s: file is modified (use -f'
-                      ' to force removal)\n') % m.rel(f))
+                      ' to force removal)\n') % m.rel(join(f)))
             ret = 1
         for f in added:
             ui.warn(_('not removing %s: file has been marked for add'
-                      ' (use forget to undo)\n') % m.rel(f))
+                      ' (use forget to undo)\n') % m.rel(join(f)))
             ret = 1
 
     for f in sorted(list):
         if ui.verbose or not m.exact(f):
-            ui.status(_('removing %s\n') % m.rel(f))
+            ui.status(_('removing %s\n') % m.rel(join(f)))
 
     wlock = repo.wlock()
     try:
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -5086,7 +5086,7 @@ 
     [('A', 'after', None, _('record delete for missing files')),
     ('f', 'force', None,
      _('remove (and delete) file even if added or modified')),
-    ] + walkopts,
+    ] + subrepoopts + walkopts,
     _('[OPTION]... FILE...'),
     inferrepo=True)
 def remove(ui, repo, *pats, **opts):
@@ -5131,7 +5131,8 @@ 
         raise util.Abort(_('no files specified'))
 
     m = scmutil.match(repo[None], pats, opts)
-    return cmdutil.remove(ui, repo, m, after, force)
+    subrepos = opts.get('subrepos')
+    return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
 
 @command('rename|move|mv',
     [('A', 'after', None, _('record a rename that has already occurred')),
diff --git a/mercurial/help/subrepos.txt b/mercurial/help/subrepos.txt
--- a/mercurial/help/subrepos.txt
+++ b/mercurial/help/subrepos.txt
@@ -129,6 +129,10 @@ 
     elements. Subversion subrepositories are currently silently
     ignored.
 
+:remove: remove does not recurse into subrepositories unless
+    -S/--subrepos is specified.  Git and Subversion subrepositories
+    are currently silently ignored.
+
 :update: update restores the subrepos in the state they were
     originally committed in target changeset. If the recorded
     changeset is not available in the current subrepository, Mercurial
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -501,6 +501,13 @@ 
     def forget(self, ui, match, prefix):
         return ([], [])
 
+    def removefiles(self, ui, matcher, prefix, after, force, subrepos):
+        """remove the matched files from the subrepository and the filesystem,
+        possibly by force and/or after the file has been removed from the
+        filesystem.  Return 0 on success, 1 on any warning.
+        """
+        return 1
+
     def revert(self, ui, substate, *pats, **opts):
         ui.warn('%s: reverting %s subrepos is unsupported\n' \
             % (substate[0], substate[2]))
@@ -854,6 +861,12 @@ 
                               os.path.join(prefix, self._path), True)
 
     @annotatesubrepoerror
+    def removefiles(self, ui, matcher, prefix, after, force, subrepos):
+        return cmdutil.remove(ui, self._repo, matcher,
+                              os.path.join(prefix, self._path), after, force,
+                              subrepos)
+
+    @annotatesubrepoerror
     def revert(self, ui, substate, *pats, **opts):
         # reverting a subrepo is a 2 step process:
         # 1. if the no_backup is not set, revert all modified
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -210,7 +210,7 @@ 
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
-  remove: after, force, include, exclude
+  remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1944,6 +1944,9 @@ 
   <tr><td>-f</td>
   <td>--force</td>
   <td>remove (and delete) file even if added or modified</td></tr>
+  <tr><td>-S</td>
+  <td>--subrepos</td>
+  <td>recurse into subrepositories</td></tr>
   <tr><td>-I</td>
   <td>--include PATTERN [+]</td>
   <td>include names matching the given patterns</td></tr>
diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t
--- a/tests/test-subrepo-deep-nested-change.t
+++ b/tests/test-subrepo-deep-nested-change.t
@@ -110,6 +110,17 @@ 
   $ hg ci -Sm "add test.txt"
   committing subrepository sub1
   committing subrepository sub1/sub2 (glob)
+
+.. but first take a detour through some deep removal testing
+
+  $ hg remove -S -I 're:.*.txt' sub1
+  removing sub1/sub2/folder/test.txt (glob)
+  removing sub1/sub2/test.txt (glob)
+  $ hg status -S
+  R sub1/sub2/folder/test.txt
+  R sub1/sub2/test.txt
+  $ hg update -Cq
+
   $ hg --config extensions.largefiles=! archive -S ../archive_all
   $ find ../archive_all | sort
   ../archive_all