D8030: uncopy: add support for unmarking committed copies

martinvonz (Martin von Zweigbergk) phabricator at mercurial-scm.org
Wed Jan 29 18:40:42 EST 2020


martinvonz updated this revision to Diff 19693.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D8030?vs=19670&id=19693

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8030/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D8030

AFFECTED 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

CHANGE DETAILS

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/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
@@ -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,
 )
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



To: martinvonz, #hg-reviewers, durin42
Cc: durin42, mercurial-devel


More information about the Mercurial-devel mailing list