# HG changeset patch # User Matt Harbison # Date 2014-11-16 02:36:19 # Node ID 4165cfd675193a0b223e109d781b1c405e8c1ee0 # Parent 69f86b9370350a939ed415f49e2ff110085d447d 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. diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -2052,21 +2052,44 @@ def forget(ui, repo, match, prefix, expl 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 @@ def remove(ui, repo, m, after, force): 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 @@ def recover(ui, repo): [('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 @@ def remove(ui, repo, *pats, **opts): 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 @@ Interaction with Mercurial Commands 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 @@ class abstractsubrepo(object): 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 @@ class hgsubrepo(abstractsubrepo): 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 @@ Show all commands + options 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 @@ Dish up an empty repo; serve it cold. -f --force remove (and delete) file even if added or modified + -S + --subrepos + recurse into subrepositories -I --include PATTERN [+] include names matching the given patterns 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 @@ Check that deep archiving works $ 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