From patchwork Tue Jan 28 23:54:59 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: D8030: uncopy: add support for unmarking committed copies From: phabricator X-Patchwork-Id: 44722 Message-Id: To: Phabricator Cc: mercurial-devel@mercurial-scm.org Date: Tue, 28 Jan 2020 23:54:59 +0000 martinvonz created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The simplest way I'm aware of to unmark a file as copied after committing is this: hg uncommit --keep hg forget hg add hg amend This patch teaches `hg uncopy` a `-r` argument to simplify that into: hg uncopy -r . In addition to being simpler, it doesn't touch the working copy, so it can easily be used even if the destination file has been modified in the working copy. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D8030 AFFECTED FILES mercurial/cmdutil.py mercurial/commands.py mercurial/context.py relnotes/5.3 relnotes/next tests/test-completion.t tests/test-copy.t tests/test-rename-after-merge.t CHANGE DETAILS To: martinvonz, #hg-reviewers Cc: mercurial-devel 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 .. 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-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/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/relnotes/5.3 b/relnotes/5.3 --- a/relnotes/5.3 +++ b/relnotes/5.3 @@ -2,7 +2,8 @@ * Windows will process hgrc files in %PROGRAMDATA%\Mercurial\hgrc.d. - * `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/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/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -7493,7 +7493,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, ) 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