# HG changeset patch # User Patrick Mezard # Date 1335731155 -7200 # Node ID ebf6d38c9063deae5dfe9af1f72003696210bfd9 # Parent 0d494a38c586cf961ce29433e53326b37c9e302c 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 -r 0d494a38c586 -r ebf6d38c9063 hgext/largefiles/lfcommands.py --- 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 diff -r 0d494a38c586 -r ebf6d38c9063 hgext/mq.py --- 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, diff -r 0d494a38c586 -r ebf6d38c9063 hgext/rebase.py --- 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() diff -r 0d494a38c586 -r ebf6d38c9063 hgext/transplant.py --- 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: diff -r 0d494a38c586 -r ebf6d38c9063 mercurial/cmdutil.py --- 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()) diff -r 0d494a38c586 -r ebf6d38c9063 mercurial/commands.py --- 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') diff -r 0d494a38c586 -r ebf6d38c9063 mercurial/dirstate.py --- 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']: diff -r 0d494a38c586 -r ebf6d38c9063 mercurial/localrepo.py --- 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.""" diff -r 0d494a38c586 -r ebf6d38c9063 mercurial/merge.py --- 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()) diff -r 0d494a38c586 -r ebf6d38c9063 tests/bundles/rename.sh --- /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 .. diff -r 0d494a38c586 -r ebf6d38c9063 tests/bundles/renames.hg Binary file tests/bundles/renames.hg has changed diff -r 0d494a38c586 -r ebf6d38c9063 tests/test-rebase-collapse.t --- 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 ..