Mercurial > hg
changeset 44365:7c4b98a4e536
copy: add experimetal support for unmarking committed copies
The simplest way I'm aware of to unmark a file as copied after
committing is this:
hg uncommit --keep <dest>
hg forget <dest>
hg add <dest>
hg amend
This patch teaches `hg copy --forget` a `-r` argument to simplify that into:
hg copy --forget --at-rev . <dest>
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.
I'll teach `hg copy` without `--forget` to work with `--at-rev` next.
Differential Revision: https://phab.mercurial-scm.org/D8030
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Tue, 28 Jan 2020 14:07:57 -0800 |
parents | 8be0c63535b5 |
children | d8b49bf6cfec |
files | mercurial/cmdutil.py mercurial/commands.py mercurial/context.py relnotes/next tests/test-completion.t tests/test-copy.t tests/test-rename-after-merge.t |
diffstat | 7 files changed, 122 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Fri Dec 20 15:50:13 2019 -0800 +++ b/mercurial/cmdutil.py Tue Jan 28 14:07:57 2020 -0800 @@ -1427,14 +1427,33 @@ uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) if forget: - match = scmutil.match(wctx, pats, opts) - - current_copies = wctx.p1copies() - current_copies.update(wctx.p2copies()) - - for f in wctx.walk(match): + rev = opts[b'at_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) + + current_copies = ctx.p1copies() + current_copies.update(ctx.p2copies()) + + uipathfn = scmutil.getuipathfn(repo) + for f in ctx.walk(match): if f in current_copies: - wctx[f].markcopied(None) + new_ctx[f].markcopied(None) elif match.exact(f): ui.warn( _( @@ -1442,8 +1461,25 @@ ) % 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 + ) + return + if opts.get(b'at_rev'): + raise error.Abort(_("--at-rev is only supported with --forget")) + def walkpat(pat): srcs = [] m = scmutil.match(ctx, [pat], opts, globbed=True)
--- a/mercurial/commands.py Fri Dec 20 15:50:13 2019 -0800 +++ b/mercurial/commands.py Tue Jan 28 14:07:57 2020 -0800 @@ -2312,6 +2312,13 @@ (b'', b'forget', None, _(b'unmark a file as copied')), (b'A', b'after', None, _(b'record a copy that has already occurred')), ( + b'', + b'at-rev', + b'', + _(b'unmark copies in the given revision (EXPERIMENTAL)'), + _(b'REV'), + ), + ( b'f', b'force', None,
--- a/mercurial/context.py Fri Dec 20 15:50:13 2019 -0800 +++ b/mercurial/context.py Tue Jan 28 14:07:57 2020 -0800 @@ -2487,6 +2487,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
--- a/relnotes/next Fri Dec 20 15:50:13 2019 -0800 +++ b/relnotes/next Tue Jan 28 14:07:57 2020 -0800 @@ -17,6 +17,9 @@ == New Experimental Features == + * Use `hg copy --forget --at-rev REV` to unmark already committed + copies. + == Bug Fixes ==
--- a/tests/test-completion.t Fri Dec 20 15:50:13 2019 -0800 +++ b/tests/test-completion.t Tue Jan 28 14:07:57 2020 -0800 @@ -257,7 +257,7 @@ commit: addremove, close-branch, amend, secret, edit, force-close-branch, interactive, include, exclude, message, logfile, date, user, subrepos config: untrusted, edit, local, global, template continue: dry-run - copy: forget, after, force, include, exclude, dry-run + copy: forget, after, at-rev, force, include, exclude, dry-run debugancestor: debugapplystreamclonebundle: debugbuilddag: mergeable-file, overwritten-file, new-file
--- a/tests/test-copy.t Fri Dec 20 15:50:13 2019 -0800 +++ b/tests/test-copy.t Tue Jan 28 14:07:57 2020 -0800 @@ -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 copy --forget --at-rev . 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 ..
--- a/tests/test-rename-after-merge.t Fri Dec 20 15:50:13 2019 -0800 +++ b/tests/test-rename-after-merge.t Tue Jan 28 14:07:57 2020 -0800 @@ -120,4 +120,10 @@ $ hg log -r tip -C -v | grep copies copies: b2 (b1) +Test unmarking copies in merge commit + + $ hg copy --forget --at-rev . b2 + abort: cannot unmark copy in merge commit + [255] + $ cd ..