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
--- 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 ..