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