diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1695,7 +1695,23 @@ def uncopy(ui, repo, pats, opts): - ctx = repo[None] + rev = opts[b'rev'] + if rev: + ctx = scmutil.revsingle(repo, rev) + else: + ctx = repo[None] + if ctx.rev() is None: + new_ctx = ctx + else: + if len(ctx.parents()) > 1: + raise error.Abort(_(b'cannot unmark copy in merge commit')) + # avoid cycle context -> subrepo -> cmdutil + from . import context + + rewriteutil.precheck(repo, [ctx.rev()], b'uncopy') + new_ctx = context.overlayworkingctx(repo) + new_ctx.setbase(ctx.p1()) + mergemod.graft(repo, ctx, wctx=new_ctx) match = scmutil.match(ctx, pats, opts) @@ -1705,13 +1721,24 @@ uipathfn = scmutil.getuipathfn(repo) for f in ctx.walk(match): if f in current_copies: - ctx[f].markcopied(None) + new_ctx[f].markcopied(None) elif match.exact(f): ui.warn( _(b'%s: not uncopying - file is not marked as copied\n') % uipathfn(f) ) + if ctx.rev() is not None: + with repo.lock(): + mem_ctx = new_ctx.tomemctx_for_amend(ctx) + new_node = mem_ctx.commit() + + if repo.dirstate.p1() == ctx.node(): + with repo.dirstate.parentchange(): + scmutil.movedirstate(repo, repo[new_node]) + replacements = {ctx.node(): [new_node]} + scmutil.cleanupnodes(repo, replacements, b'uncopy', fixphase=True) + ## facility to let extension process additional data into an import patch # list of identifier to be executed in order diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -7495,7 +7495,8 @@ @command( b'uncopy', - walkopts, + [(b'r', b'rev', b'', _(b'unmark copies in the given revision'), _(b'REV'))] + + walkopts, _(b'[OPTION]... DEST...'), helpcategory=command.CATEGORY_FILE_CONTENTS, ) @@ -7504,7 +7505,8 @@ Unmark DEST as having copies of source files. - This command takes effect with the next commit. + This command takes effect with the next commit by default. Use + ``-r`` to unmark copies in an existing commit. Returns 0 on success, 1 if errors are encountered. """ diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -2488,6 +2488,17 @@ editor=editor, ) + def tomemctx_for_amend(self, precursor): + extra = precursor.extra().copy() + extra[b'amend_source'] = precursor.hex() + return self.tomemctx( + text=precursor.description(), + branch=precursor.branch(), + extra=extra, + date=precursor.date(), + user=precursor.user(), + ) + def isdirty(self, path): return path in self._cache diff --git a/relnotes/next b/relnotes/next --- a/relnotes/next +++ b/relnotes/next @@ -1,6 +1,7 @@ == New Features == - * `hg uncopy` can be used to unmark a file as copied. + * `hg uncopy` can be used to unmark a file as copied. Use `hg uncopy -r REV` + to unmark already committed copies. == New Experimental Features == diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -357,7 +357,7 @@ tags: template tip: patch, git, style, template unbundle: update - uncopy: include, exclude + uncopy: rev, include, exclude unshelve: abort, continue, interactive, keep, name, tool, date update: clean, check, merge, date, rev, tool verify: full diff --git a/tests/test-copy.t b/tests/test-copy.t --- a/tests/test-copy.t +++ b/tests/test-copy.t @@ -319,5 +319,56 @@ A dir2/bar A dir2/foo ? dir2/untracked +# Clean up for next test + $ hg forget dir2 + removing dir2/bar + removing dir2/foo + $ rm -r dir2 + +Test uncopy on committed copies + +# Commit some copies + $ hg cp bar baz + $ hg cp bar qux + $ hg ci -m copies + $ hg st -C --change . + A baz + bar + A qux + bar + $ base=$(hg log -r '.^' -T '{rev}') + $ hg log -G -T '{rev}:{node|short} {desc}\n' -r $base: + @ 5:a612dc2edfda copies + | + o 4:4800b1f1f38e add dir/ + | + ~ +# Add a dirty change on top to show that it's unaffected + $ echo dirty >> baz + $ hg st + M baz + $ cat baz + bleah + dirty + $ hg uncopy -r . baz + saved backup bundle to $TESTTMP/part2/.hg/strip-backup/a612dc2edfda-e36b4448-uncopy.hg +# The unwanted copy is no longer recorded, but the unrelated one is + $ hg st -C --change . + A baz + A qux + bar +# The old commit is gone and we have updated to the new commit + $ hg log -G -T '{rev}:{node|short} {desc}\n' -r $base: + @ 5:c45090e5effe copies + | + o 4:4800b1f1f38e add dir/ + | + ~ +# Working copy still has the uncommitted change + $ hg st + M baz + $ cat baz + bleah + dirty $ cd .. diff --git a/tests/test-rename-after-merge.t b/tests/test-rename-after-merge.t --- a/tests/test-rename-after-merge.t +++ b/tests/test-rename-after-merge.t @@ -120,4 +120,10 @@ $ hg log -r tip -C -v | grep copies copies: b2 (b1) +Test unmarking copies in merge commit + + $ hg uncopy -r . b2 + abort: cannot unmark copy in merge commit + [255] + $ cd ..