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.