localrepo: add setparents() to adjust dirstate copies (issue3407) stable
authorPatrick Mezard <patrick@mezard.eu>
Sun, 29 Apr 2012 22:25:55 +0200
branchstable
changeset 16551 ebf6d38c9063
parent 16550 0d494a38c586
child 16552 90ca344a7c55
localrepo: add setparents() to adjust dirstate copies (issue3407) The fix introduced in eab9119c5dee was only partially successful. It is correct to turn dirstate 'm' merge records into normal/dirty ones but copy records are lost in the process. To adjust them as well, we need to look in the first parent manifest to know which files were added and preserve only related records. But the dirstate does not have access to changesets, the logic has to moved at another level, in localrepo.
hgext/largefiles/lfcommands.py
hgext/mq.py
hgext/rebase.py
hgext/transplant.py
mercurial/cmdutil.py
mercurial/commands.py
mercurial/dirstate.py
mercurial/localrepo.py
mercurial/merge.py
tests/bundles/rename.sh
tests/bundles/renames.hg
tests/test-rebase-collapse.t
--- a/hgext/largefiles/lfcommands.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/hgext/largefiles/lfcommands.py	Sun Apr 29 22:25:55 2012 +0200
@@ -248,7 +248,7 @@
     mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
                           getfilectx, ctx.user(), ctx.date(), ctx.extra())
     ret = rdst.commitctx(mctx)
-    rdst.dirstate.setparents(ret)
+    rdst.setparents(ret)
     revmap[ctx.node()] = rdst.changelog.tip()
 
 # Generate list of changed files
--- a/hgext/mq.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/hgext/mq.py	Sun Apr 29 22:25:55 2012 +0200
@@ -749,7 +749,7 @@
                 for f in merged:
                     repo.dirstate.merge(f)
                 p1, p2 = repo.dirstate.parents()
-                repo.dirstate.setparents(p1, merge)
+                repo.setparents(p1, merge)
 
             match = scmutil.matchfiles(repo, files or [])
             oldtip = repo['tip']
@@ -1355,7 +1355,7 @@
                     fctx = ctx[f]
                     repo.wwrite(f, fctx.data(), fctx.flags())
                     repo.dirstate.normal(f)
-                repo.dirstate.setparents(qp, nullid)
+                repo.setparents(qp, nullid)
             for patch in reversed(self.applied[start:end]):
                 self.ui.status(_("popping %s\n") % patch.name)
             del self.applied[start:end]
@@ -1546,7 +1546,7 @@
                 oldphase = repo[top].phase()
 
                 # assumes strip can roll itself back if interrupted
-                repo.dirstate.setparents(*cparents)
+                repo.setparents(*cparents)
                 self.applied.pop()
                 self.applieddirty = True
                 self.strip(repo, [top], update=False,
--- a/hgext/rebase.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/hgext/rebase.py	Sun Apr 29 22:25:55 2012 +0200
@@ -277,7 +277,7 @@
                                           editor=editor)
                 else:
                     # Skip commit if we are collapsing
-                    repo.dirstate.setparents(repo[p1].node())
+                    repo.setparents(repo[p1].node())
                     newrev = None
                 # Update the state
                 if newrev is not None:
@@ -361,7 +361,7 @@
 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
     'Commit the changes and store useful information in extra'
     try:
-        repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
+        repo.setparents(repo[p1].node(), repo[p2].node())
         ctx = repo[rev]
         if commitmsg is None:
             commitmsg = ctx.description()
--- a/hgext/transplant.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/hgext/transplant.py	Sun Apr 29 22:25:55 2012 +0200
@@ -274,7 +274,7 @@
             files = None
         if merge:
             p1, p2 = repo.dirstate.parents()
-            repo.dirstate.setparents(p1, node)
+            repo.setparents(p1, node)
             m = match.always(repo.root, '')
         else:
             m = match.exact(repo.root, '', files)
@@ -340,7 +340,7 @@
                     _('working dir not at transplant parent %s') %
                                  revlog.hex(parent))
             if merge:
-                repo.dirstate.setparents(p1, parents[1])
+                repo.setparents(p1, parents[1])
             n = repo.commit(message, user, date, extra=extra,
                             editor=self.editor)
             if not n:
--- a/mercurial/cmdutil.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/mercurial/cmdutil.py	Sun Apr 29 22:25:55 2012 +0200
@@ -1384,7 +1384,7 @@
         newid = repo.commitctx(new)
         if newid != old.node():
             # Reroute the working copy parent to the new changeset
-            repo.dirstate.setparents(newid, nullid)
+            repo.setparents(newid, nullid)
 
             # Move bookmarks from old parent to amend commit
             bms = repo.nodebookmarks(old.node())
--- a/mercurial/commands.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/mercurial/commands.py	Sun Apr 29 22:25:55 2012 +0200
@@ -2270,7 +2270,7 @@
 
     wlock = repo.wlock()
     try:
-        repo.dirstate.setparents(r1, r2)
+        repo.setparents(r1, r2)
     finally:
         wlock.release()
 
@@ -2693,7 +2693,7 @@
                 finally:
                     ui.setconfig('ui', 'forcemerge', '')
                 # drop the second merge parent
-                repo.dirstate.setparents(current.node(), nullid)
+                repo.setparents(current.node(), nullid)
                 repo.dirstate.write()
                 # fix up dirstate for copies and renames
                 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
@@ -3635,7 +3635,7 @@
                 if p1 != parents[0]:
                     hg.clean(repo, p1.node())
                 if p2 != parents[1]:
-                    repo.dirstate.setparents(p1.node(), p2.node())
+                    repo.setparents(p1.node(), p2.node())
 
                 if opts.get('exact') or opts.get('import_branch'):
                     repo.dirstate.setbranch(branch or 'default')
--- a/mercurial/dirstate.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/mercurial/dirstate.py	Sun Apr 29 22:25:55 2012 +0200
@@ -237,14 +237,26 @@
         return encoding.tolocal(self._branch)
 
     def setparents(self, p1, p2=nullid):
+        """Set dirstate parents to p1 and p2.
+
+        When moving from two parents to one, 'm' merged entries a
+        adjusted to normal and previous copy records discarded and
+        returned by the call.
+
+        See localrepo.setparents()
+        """
         self._dirty = self._dirtypl = True
         oldp2 = self._pl[1]
         self._pl = p1, p2
+        copies = {}
         if oldp2 != nullid and p2 == nullid:
             # Discard 'm' markers when moving away from a merge state
             for f, s in self._map.iteritems():
                 if s[0] == 'm':
+                    if f in self._copymap:
+                        copies[f] = self._copymap[f]
                     self.normallookup(f)
+        return copies
 
     def setbranch(self, branch):
         if branch in ['tip', '.', 'null']:
--- a/mercurial/localrepo.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/mercurial/localrepo.py	Sun Apr 29 22:25:55 2012 +0200
@@ -633,6 +633,17 @@
         '''get list of changectxs for parents of changeid'''
         return self[changeid].parents()
 
+    def setparents(self, p1, p2=nullid):
+        copies = self.dirstate.setparents(p1, p2)
+        if copies:
+            # Adjust copy records, the dirstate cannot do it, it
+            # requires access to parents manifests. Preserve them
+            # only for entries added to first parent.
+            pctx = self[p1]
+            for f in copies:
+                if f not in pctx and copies[f] in pctx:
+                    self.dirstate.copy(copies[f], f)
+
     def filectx(self, path, changeid=None, fileid=None):
         """changeid can be a changeset revision, node, or tag.
            fileid can be a file revision or node."""
--- a/mercurial/merge.py	Sun Apr 29 16:18:46 2012 +0200
+++ b/mercurial/merge.py	Sun Apr 29 22:25:55 2012 +0200
@@ -596,7 +596,7 @@
         stats = applyupdates(repo, action, wc, p2, pa, overwrite)
 
         if not partial:
-            repo.dirstate.setparents(fp1, fp2)
+            repo.setparents(fp1, fp2)
             recordupdates(repo, action, branchmerge)
             if not branchmerge:
                 repo.dirstate.setbranch(p2.branch())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/bundles/rename.sh	Sun Apr 29 22:25:55 2012 +0200
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#  @  3: 'move2'
+#  |
+#  o  2: 'move1'
+#  |
+#  | o  1: 'change'
+#  |/
+#  o  0: 'add'
+
+hg init copies
+cd copies
+echo a > a
+echo b > b
+echo c > c
+hg ci -Am add
+echo a >> a
+echo b >> b
+echo c >> c
+hg ci -m change
+hg up -qC 0
+hg cp a d
+hg mv b e
+hg mv c f
+hg ci -m move1
+hg mv e g
+hg mv f c
+hg ci -m move2
+hg bundle -a ../renames.hg
+cd ..
Binary file tests/bundles/renames.hg has changed
--- a/tests/test-rebase-collapse.t	Sun Apr 29 16:18:46 2012 +0200
+++ b/tests/test-rebase-collapse.t	Sun Apr 29 22:25:55 2012 +0200
@@ -541,3 +541,52 @@
   @@ -0,0 +1,2 @@
   +d
   +blah
+
+  $ cd ..
+
+Rebase, collapse and copies
+
+  $ hg init copies
+  $ cd copies
+  $ hg unbundle "$TESTDIR/bundles/renames.hg"
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 11 changes to 7 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg up -q tip
+  $ hg tglog
+  @  3: 'move2'
+  |
+  o  2: 'move1'
+  |
+  | o  1: 'change'
+  |/
+  o  0: 'add'
+  
+  $ hg rebase --collapse -d 1
+  merging a and d to d
+  merging b and e to e
+  merging c and f to f
+  merging e and g to g
+  merging f and c to c
+  saved backup bundle to $TESTTMP/copies/.hg/strip-backup/*-backup.hg (glob)
+  $ hg st
+  $ hg st --copies --change .
+  A d
+    a
+  A g
+    b
+  R b
+  $ cat c
+  c
+  c
+  $ cat d
+  a
+  a
+  $ cat g
+  b
+  b
+  $ hg log -r . --template "{file_copies}\n"
+  d (a)g (b)
+  $ cd ..