[PATCH] localrepo: add setparents() to adjust dirstate copies (issue3407)

Patrick Mezard patrick at mezard.eu
Sun Apr 29 20:26:34 UTC 2012


# HG changeset patch
# User Patrick Mezard <patrick at mezard.eu>
# Date 1335731155 -7200
# Branch stable
# Node ID b2776200971894a83f1a9fb15827b33e88693a7a
# Parent  be786c5ac0a852cab965d9e541611f882bdb0bb8
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.

diff --git a/hgext/largefiles/lfcommands.py b/hgext/largefiles/lfcommands.py
--- a/hgext/largefiles/lfcommands.py
+++ b/hgext/largefiles/lfcommands.py
@@ -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
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -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,
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -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()
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -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:
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -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())
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -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')
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -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']:
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -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."""
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -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())
diff --git a/tests/bundles/rename.sh b/tests/bundles/rename.sh
new file mode 100755
--- /dev/null
+++ b/tests/bundles/rename.sh
@@ -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 ..
diff --git a/tests/bundles/renames.hg b/tests/bundles/renames.hg
new file mode 100644
index 0000000000000000000000000000000000000000..977a8454d038945828ee3ff6f30d7c0fc9b8ee66
GIT binary patch
literal 1600
zc$@)12EX}8M=>x$T4*^jL0KkKS!<WTYXAUAfB*l#{Zsxw|Nr;@|Np=L|N8a)et-Xe
z`@eg=wtc(*9`n!y_^5yh*bD#?Qc0-OQM{zt9;oyusgG3kJx>T~X at f>7`kI)~$WK!v
zCK)DZjXfF-G8$-}fF at 5sG}?_anDnO5BYLN`JtIvtWjzO}h#F--R6qa#20#F415E=#
zp`Zb#fXDy<G8q^k27t&0hJmJ<0iYTH0gwhpKmnvQOavwniU4E{4FG5cni>Y0WC7{}
z)EWR98UO$Q0LTMC0B8UJ02%-Yr7|=OfCE4P&;S4c05miPfB*mkOn?9y00SdH41fRt
z0000G0004y02%<(K+tGt0BN8y000bzMhF3*G6A8WX{LZ^27mx$0g;daz_>~0I9?$~
zvok(XYRL*N%j!|)7QU<J;k4SE)F9_NlC-HoW!eamj!%_4i~<*+5Y3dfq2fTe{Ts5(
zvci&4<Wi1PrAX3g7|gb3m9McPz(hcVB?Q4nLP!RXk}41)f*>>`9799{O}=DMiJ&Cd
z!HE=rF)1Y3iJ+C-fYL>boOx4ck)yWsBJ@|F!-&|;0 at sZ6D+q3~l^!=h@>>H7bd at 9!
zdSuKDOjaic5aKH#LD1 at ott?6(mq?Ora7PDp1_b7TqFG!Eft(4tzLjis3cvyLk7_pv
zXt9)u{rGxG*-d(o-n5l)pQYR;BUz*$ctspYF)F*ZzNEtKGN52j6k0ztiN1AEW(kHA
zn5TmpX4T+kdCH*+8P(^sy$1D}NE6v9Hqc3FePQqhmncE$q$fYPPE at j?k$tBz5 at JMT
z6KFE1Irat|!^29qSx~8=;$gHsrWsLnCL}_NqcI5ulvW<nP_0P0;&T*<#t?9z!ca~X
zsw<Xq6E->#C54GHELujZZ_<Qjn at AD7S`uQ>0fOO`P^eH0lpNL2`yMO{K*0gvkv@^Y
zCT|R=I!dHPcQh<aC at l^csSg#v*;^tg4}g5}Oij~JP;g^}X%TCKlG0UXC=y!Qu$qHt
z3lyv^izd_(4gzGA0V*dUQE7yZkph at WFg1@Q3i5Sppix(tCh-{2Vea+|AdrHp;-MnA
zTM;0-Lxdh^DIhtD7Y7KiCb2gZEhc`XSQ{iha77Ld$61QoI;v1<93T*<R&hDJ*i2xh
zB2shJNoNaA5QHFV`;ksZf8=H`mnuVy)LgXzsTtbFpo>nc#y*-hjiy(IvQ}Q7!ItCO
z`oNW-llNJa34e4~RHxr9K5V38G$2NmMWvBFmr8Qg?-*9X>&}4(#3C{KeW2?~j-P&L
zP!Dn1lD8eI48j3$(g79eMGOm2&A?bL7Z+-$M8z(`ti=fuEu`!TLz9RY0Rji2!x@!w
zRYp*HQ1=5oqkSk)aBWz1L(<l<yk3BWUw}OtE;^B2bm47 at 2EePCh$vz!f<T<ZL|s5c
zb%mM+9CHb4O0cxj3}b9TT#I|e3&Ja4qVVt^#VsIPRnC?G at mV6_Y|0CAAv+g^;X$s`
z`r^PtH&A{PW at J@>K#@X3XI2FmSuq5>W{NNh!e~lWVgc9%(qwW;r8x<kf<wxWD$OOV
zq+(Qdc%(#BXe<LDon^$A2-x-qLc4Bg3=vZoX&GEO+$1)i0~PN^8BC)l1l~ys3y`+4
zeFt_*f!{^s`|yFwY!M+Bi^b~oRW>m&3_kM&d}Ds18<o+q8^<NleMK2aP$^D58-Wr7
z|G5<i?O6!eR}W*%Axek_H5d~ZlXGH9^w^6bV9fv<)@&w(U=H2r6L3=W+SPp|jz$Vo
zc8HhzYo8q=l-I>vkaSc??WvtiB%e$UrctwovqPG?Yf<P9vsL}``XEDC)b83B64aKk
zEvzqKMMbefO5M_OLl<8*Xr<?Tg_<O(GMZRl2e1YnZwA|v;M|y0z3YqNd->ne^T|@P
zc at JbLvFnO5a1N}Rs)R1+549Q6(fOe`Rp;`RYIc-+kT1DSwyRj+gkCpr11tzQbg+}!
z%fD at 7ab-o+13fkc;PB{v)}GSl=J(U<zicbr9&)$+RObx0DY2cEK8kVWh58-Q?1f|h
y0lwlYI>dP4(<KbQcFe)gILs(wA?8S6WLys?-HX&^lr_i~{x0N-aG at aAFM!s>TCUar

diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
--- a/tests/test-rebase-collapse.t
+++ b/tests/test-rebase-collapse.t
@@ -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 ..



More information about the Mercurial-devel mailing list