Mercurial > hg
changeset 42595:819712deac69
copies: follow copies across merge base without source file (issue6163)
As in the previous patch, consider these two histories:
@ 4 'rename x to y'
|
o 3 'add x again'
|
o 2 'remove x'
|
| o 1 'modify x'
|/
o 0 'add x'
@ 4 'rename x to y'
|
o 3 'add x again'
|
| o 2 'modify x'
| |
| o 1 'add x'
|/
o 0 'base'
We trace copies from the 'modify x' commit to commit 4 by going via
the merge base (commit 0). When tracing file 'y' (_tracefile()) in the
first case, we immediately find the rename from 'x'. We check to see
if 'x' exists in the merge base, which it does, so we consider it a
valid copy. In the second case, 'x' does not exist in the merge base,
so it's not considered a valid copy. As a workaround, this patch makes
it so we also attempt the check in mergecopies's base commit (commit 1
in the second case). That feels pretty ugly to me, but I don't have
any better ideas.
Note that we actually also check not only that the filename matches,
but also that the file's nodeid matches. I don't know why we do that,
but it was like that already before I rewrote mergecopies(). That
means that the rebase will still fail in cases like this (again, it
already failed before my rewrite):
@ 4 'rename x to y'
|
o 3 'add x again with content X2'
|
o 2 'remove x'
|
| o 1 'modify x to content X2'
|/
o 1 'modify x to content X1'
|
o 0 'add x with content X0'
Differential Revision: https://phab.mercurial-scm.org/D6604
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Fri, 28 Jun 2019 12:59:21 -0700 |
parents | d013099c551b |
children | 83666f011679 |
files | mercurial/copies.py tests/test-copies-unrelated.t |
diffstat | 2 files changed, 16 insertions(+), 20 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/copies.py Tue Jun 25 14:25:03 2019 -0700 +++ b/mercurial/copies.py Fri Jun 28 12:59:21 2019 -0700 @@ -150,7 +150,7 @@ t[k] = v return t -def _tracefile(fctx, am, limit): +def _tracefile(fctx, am, basemf, limit): """return file context that is the ancestor of fctx present in ancestor manifest am, stopping after the first ancestor lower than limit""" @@ -158,6 +158,8 @@ path = f.path() if am.get(path, None) == f.filenode(): return path + if basemf and basemf.get(path, None) == f.filenode(): + return path if not f.isintroducedafter(limit): return None @@ -183,7 +185,7 @@ return (repo.ui.config('experimental', 'copies.read-from') in ('changeset-only', 'compatibility')) -def _committedforwardcopies(a, b, match): +def _committedforwardcopies(a, b, base, match): """Like _forwardcopies(), but b.rev() cannot be None (working copy)""" # files might have to be traced back to the fctx parent of the last # one-side-only changeset, but not further back than that @@ -201,6 +203,7 @@ if debug: dbg('debug.copies: search limit: %d\n' % limit) am = a.manifest() + basemf = None if base is None else base.manifest() # find where new files came from # we currently don't try to find where old files went, too expensive @@ -232,7 +235,7 @@ if debug: start = util.timer() - opath = _tracefile(fctx, am, limit) + opath = _tracefile(fctx, am, basemf, limit) if opath: if debug: dbg('debug.copies: rename of: %s\n' % opath) @@ -311,17 +314,19 @@ heapq.heappush(work, (c, parent, newcopies)) assert False -def _forwardcopies(a, b, match=None): +def _forwardcopies(a, b, base=None, match=None): """find {dst@b: src@a} copy mapping where a is an ancestor of b""" + if base is None: + base = a match = a.repo().narrowmatch(match) # check for working copy if b.rev() is None: - cm = _committedforwardcopies(a, b.p1(), match) + cm = _committedforwardcopies(a, b.p1(), base, match) # combine copies from dirstate if necessary copies = _chain(cm, _dirstatecopies(b._repo, match)) else: - copies = _committedforwardcopies(a, b, match) + copies = _committedforwardcopies(a, b, base, match) return copies def _backwardrenames(a, b, match): @@ -369,8 +374,11 @@ else: if debug: repo.ui.debug('debug.copies: search mode: combined\n') + base = None + if a.rev() != node.nullrev: + base = x copies = _chain(_backwardrenames(x, a, match=match), - _forwardcopies(a, y, match=match)) + _forwardcopies(a, y, base, match=match)) _filter(x, y, copies) return copies
--- a/tests/test-copies-unrelated.t Tue Jun 25 14:25:03 2019 -0700 +++ b/tests/test-copies-unrelated.t Fri Jun 28 12:59:21 2019 -0700 @@ -299,22 +299,10 @@ o 0 base a $ hg debugpathcopies 1 4 - x -> y (no-filelog !) -#if filelog -BROKEN: This should succeed and merge the changes from x into y - $ hg graft -r 2 - grafting 2:* "modify x" (glob) - file 'x' was deleted in local [local] but was modified in other [graft]. - What do you want to do? - use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u - abort: unresolved conflicts, can't continue - (use 'hg resolve' and 'hg graft --continue') - [255] -#else + x -> y $ hg graft -r 2 grafting 2:* "modify x" (glob) merging y and x to y -#endif $ hg co -qC 2 $ hg graft -r 4 grafting 4:* "rename x to y"* (glob)