changeset 2943:25576132cb30

uncommit: fix the dirstate handling in `uncommit -i` This patch fixes the dirstate handling after `hg uncommit --interactive`. This patch is bit verbose about the logic and efforts are not put to plug the logic into existing logic as it's easier to understand like this. Plugging this into non-interactive logic deserves a separate patch.
author Pulkit Goyal <7895pulkit@gmail.com>
date Mon, 11 Sep 2017 17:41:53 +0200
parents 10194206acc7
children 281a11bfda43
files hgext3rd/evolve/cmdrewrite.py tests/test-uncommit-interactive.t
diffstat 2 files changed, 55 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/hgext3rd/evolve/cmdrewrite.py	Tue Sep 05 14:35:00 2017 +0530
+++ b/hgext3rd/evolve/cmdrewrite.py	Mon Sep 11 17:41:53 2017 +0200
@@ -189,7 +189,7 @@
     newid = repo.commitctx(new)
     return newid
 
-def _uncommitdirstate(repo, oldctx, match):
+def _uncommitdirstate(repo, oldctx, match, interactive):
     """Fix the dirstate after switching the working directory from
     oldctx to a copy of oldctx not containing changed files matched by
     match.
@@ -197,7 +197,55 @@
     ctx = repo['.']
     ds = repo.dirstate
     copies = dict(ds.copies())
-    if True:
+    if interactive:
+        # In interactive cases, we will find the status between oldctx and ctx
+        # and considering only the files which are changed between oldctx and
+        # ctx, and the status of what changed between oldctx and ctx will help
+        # us in defining the exact behavior
+        m, a, r = repo.status(oldctx, ctx, match=match)[:3]
+        for f in m:
+            # These are files which are modified between oldctx and ctx which
+            # contains two cases: 1) Were modified in oldctx and some
+            # modifications are uncommitted
+            # 2) Were added in oldctx but some part is uncommitted (this cannot
+            # contain the case when added files are uncommitted completely as
+            # that will result in status as removed not modified.)
+            # Also any modifications to a removed file will result the status as
+            # added, so we have only two cases. So in either of the cases, the
+            # resulting status can be modified or clean.
+            if ds[f] == 'r':
+                # But the file is removed in the working directory, leaving that
+                # as removed
+                continue
+            ds.normallookup(f)
+
+        for f in a:
+            # These are the files which are added between oldctx and ctx(new
+            # one), which means the files which were removed in oldctx
+            # but uncommitted completely while making the ctx
+            # This file should be marked as removed if the working directory
+            # does not adds it back. If it's adds it back, we do a normallookup.
+            # The file can't be removed in working directory, because it was
+            # removed in oldctx
+            if ds[f] == 'a':
+                ds.normallookup(f)
+                continue
+            ds.remove(f)
+
+        for f in r:
+            # These are files which are removed between oldctx and ctx, which
+            # means the files which were added in oldctx and were completely
+            # uncommitted in ctx. If a added file is partially uncommitted, that
+            # would have resulted in modified status, not removed.
+            # So a file added in a commit, and uncommitting that addition must
+            # result in file being stated as unknown.
+            if ds[f] == 'r':
+                # The working directory say it's removed, so lets make the file
+                # unknown
+                ds.drop(f)
+                continue
+            ds.add(f)
+    else:
         m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
         for f in m:
             if ds[f] == 'r':
@@ -221,6 +269,10 @@
 
     # Merge old parent and old working dir copies
     oldcopies = {}
+    if interactive:
+        # Interactive had different meaning of the variables so restoring the
+        # original meaning to use them
+        m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
     for f in (m + a):
         src = oldctx[f].renamed()
         if src:
@@ -320,7 +372,7 @@
         phases.retractboundary(repo, tr, oldphase, [newid])
         with repo.dirstate.parentchange():
             repo.dirstate.setparents(newid, node.nullid)
-            _uncommitdirstate(repo, old, match)
+            _uncommitdirstate(repo, old, match, interactive)
         updatebookmarks(newid)
         if not repo[newid].files():
             ui.warn(_("new changeset is empty\n"))
--- a/tests/test-uncommit-interactive.t	Tue Sep 05 14:35:00 2017 +0530
+++ b/tests/test-uncommit-interactive.t	Mon Sep 11 17:41:53 2017 +0200
@@ -456,8 +456,6 @@
 Testing when a new file is added in the last commit
 ===================================================
 
-XXX: This is buggy, look for the diff below
-
   $ echo "foo" >> foo
   $ touch x
   $ echo "abcd" >> x
@@ -502,10 +500,6 @@
   @@ -0,0 +1,1 @@
   +abcd
 
-XXX: The diff here should not contain the file 'x' as it is in the commit, there
-is a bug related to dirstate handling of files which are added in the commit
-which we are trying to uncommit. This should be fixed
-
   $ hg diff
   diff -r 25a080d13cb2 foo
   --- a/foo	Thu Jan 01 00:00:00 1970 +0000
@@ -513,19 +507,12 @@
   @@ -1,1 +1,2 @@
    hey
   +foo
-  diff -r 25a080d13cb2 x
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/x	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +abcd
 
   $ hg status
   M foo
-  A x
 
   $ hg revert --all
   reverting foo
-  forgetting x
 
 Testing between the stack and with dirty working copy
 =====================================================