Mercurial > public > mercurial-scm > hg
comparison mercurial/copies.py @ 41392:b80af0707066
copies: consider nullrev a common ancestor
I've seen many bugs in the git codebase that were caused by it not
having a null revision and being forced to treat root commits
differently. Mercurial has a null revision and I think it's generally
a bug to treat it differently from other commits in graph algorithms.
This effectively undoes 83cfa1baf8ad (copies: don't report copies with
unrelated branch, 2010-01-01). The test cases that that commit added
still passes. I suspect some other fix after that commit made it
unnecessary.
Differential Revision: https://phab.mercurial-scm.org/D5594
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Tue, 15 Jan 2019 11:16:42 -0800 |
parents | f3f5bfbf7e04 |
children | dc50121126ae |
comparison
equal
deleted
inserted
replaced
41391:bc843e251134 | 41392:b80af0707066 |
---|---|
29 Find the last revision that needs to be checked to ensure that a full | 29 Find the last revision that needs to be checked to ensure that a full |
30 transitive closure for file copies can be properly calculated. | 30 transitive closure for file copies can be properly calculated. |
31 Generally, this means finding the earliest revision number that's an | 31 Generally, this means finding the earliest revision number that's an |
32 ancestor of a or b but not both, except when a or b is a direct descendent | 32 ancestor of a or b but not both, except when a or b is a direct descendent |
33 of the other, in which case we can return the minimum revnum of a and b. | 33 of the other, in which case we can return the minimum revnum of a and b. |
34 None if no such revision exists. | |
35 """ | 34 """ |
36 | 35 |
37 # basic idea: | 36 # basic idea: |
38 # - mark a and b with different sides | 37 # - mark a and b with different sides |
39 # - if a parent's children are all on the same side, the parent is | 38 # - if a parent's children are all on the same side, the parent is |
53 | 52 |
54 side = {a: -1, b: 1} | 53 side = {a: -1, b: 1} |
55 visit = [-a, -b] | 54 visit = [-a, -b] |
56 heapq.heapify(visit) | 55 heapq.heapify(visit) |
57 interesting = len(visit) | 56 interesting = len(visit) |
58 hascommonancestor = False | |
59 limit = node.wdirrev | 57 limit = node.wdirrev |
60 | 58 |
61 while interesting: | 59 while interesting: |
62 r = -heapq.heappop(visit) | 60 r = -heapq.heappop(visit) |
63 if r == node.wdirrev: | 61 if r == node.wdirrev: |
64 parents = [cl.rev(p) for p in repo.dirstate.parents()] | 62 parents = [cl.rev(p) for p in repo.dirstate.parents()] |
65 else: | 63 else: |
66 parents = cl.parentrevs(r) | 64 parents = cl.parentrevs(r) |
65 if parents[1] == node.nullrev: | |
66 parents = parents[:1] | |
67 for p in parents: | 67 for p in parents: |
68 if p < 0: | |
69 continue | |
70 if p not in side: | 68 if p not in side: |
71 # first time we see p; add it to visit | 69 # first time we see p; add it to visit |
72 side[p] = side[r] | 70 side[p] = side[r] |
73 if side[p]: | 71 if side[p]: |
74 interesting += 1 | 72 interesting += 1 |
75 heapq.heappush(visit, -p) | 73 heapq.heappush(visit, -p) |
76 elif side[p] and side[p] != side[r]: | 74 elif side[p] and side[p] != side[r]: |
77 # p was interesting but now we know better | 75 # p was interesting but now we know better |
78 side[p] = 0 | 76 side[p] = 0 |
79 interesting -= 1 | 77 interesting -= 1 |
80 hascommonancestor = True | |
81 if side[r]: | 78 if side[r]: |
82 limit = r # lowest rev visited | 79 limit = r # lowest rev visited |
83 interesting -= 1 | 80 interesting -= 1 |
84 | |
85 if not hascommonancestor: | |
86 return None | |
87 | 81 |
88 # Consider the following flow (see test-commit-amend.t under issue4405): | 82 # Consider the following flow (see test-commit-amend.t under issue4405): |
89 # 1/ File 'a0' committed | 83 # 1/ File 'a0' committed |
90 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1') | 84 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1') |
91 # 3/ Move back to first commit | 85 # 3/ Move back to first commit |
167 dbg = repo.ui.debug | 161 dbg = repo.ui.debug |
168 if debug: | 162 if debug: |
169 dbg('debug.copies: looking into rename from %s to %s\n' | 163 dbg('debug.copies: looking into rename from %s to %s\n' |
170 % (a, b)) | 164 % (a, b)) |
171 limit = _findlimit(repo, a.rev(), b.rev()) | 165 limit = _findlimit(repo, a.rev(), b.rev()) |
172 if limit is None: | |
173 limit = node.nullrev | |
174 if debug: | 166 if debug: |
175 dbg('debug.copies: search limit: %d\n' % limit) | 167 dbg('debug.copies: search limit: %d\n' % limit) |
176 am = a.manifest() | 168 am = a.manifest() |
177 | 169 |
178 # find where new files came from | 170 # find where new files came from |
463 tca = base | 455 tca = base |
464 if graft: | 456 if graft: |
465 tca = _c1.ancestor(_c2) | 457 tca = _c1.ancestor(_c2) |
466 | 458 |
467 limit = _findlimit(repo, c1.rev(), c2.rev()) | 459 limit = _findlimit(repo, c1.rev(), c2.rev()) |
468 if limit is None: | |
469 # no common ancestor, no copies | |
470 return {}, {}, {}, {}, {} | |
471 repo.ui.debug(" searching for copies back to rev %d\n" % limit) | 460 repo.ui.debug(" searching for copies back to rev %d\n" % limit) |
472 | 461 |
473 m1 = c1.manifest() | 462 m1 = c1.manifest() |
474 m2 = c2.manifest() | 463 m2 = c2.manifest() |
475 mb = base.manifest() | 464 mb = base.manifest() |