Mercurial > hg
comparison mercurial/copies.py @ 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 | 8c5a36805d5d |
comparison
equal
deleted
inserted
replaced
42594:d013099c551b | 42595:819712deac69 |
---|---|
148 t[k] = t[v] | 148 t[k] = t[v] |
149 else: | 149 else: |
150 t[k] = v | 150 t[k] = v |
151 return t | 151 return t |
152 | 152 |
153 def _tracefile(fctx, am, limit): | 153 def _tracefile(fctx, am, basemf, limit): |
154 """return file context that is the ancestor of fctx present in ancestor | 154 """return file context that is the ancestor of fctx present in ancestor |
155 manifest am, stopping after the first ancestor lower than limit""" | 155 manifest am, stopping after the first ancestor lower than limit""" |
156 | 156 |
157 for f in fctx.ancestors(): | 157 for f in fctx.ancestors(): |
158 path = f.path() | 158 path = f.path() |
159 if am.get(path, None) == f.filenode(): | 159 if am.get(path, None) == f.filenode(): |
160 return path | |
161 if basemf and basemf.get(path, None) == f.filenode(): | |
160 return path | 162 return path |
161 if not f.isintroducedafter(limit): | 163 if not f.isintroducedafter(limit): |
162 return None | 164 return None |
163 | 165 |
164 def _dirstatecopies(repo, match=None): | 166 def _dirstatecopies(repo, match=None): |
181 def usechangesetcentricalgo(repo): | 183 def usechangesetcentricalgo(repo): |
182 """Checks if we should use changeset-centric copy algorithms""" | 184 """Checks if we should use changeset-centric copy algorithms""" |
183 return (repo.ui.config('experimental', 'copies.read-from') in | 185 return (repo.ui.config('experimental', 'copies.read-from') in |
184 ('changeset-only', 'compatibility')) | 186 ('changeset-only', 'compatibility')) |
185 | 187 |
186 def _committedforwardcopies(a, b, match): | 188 def _committedforwardcopies(a, b, base, match): |
187 """Like _forwardcopies(), but b.rev() cannot be None (working copy)""" | 189 """Like _forwardcopies(), but b.rev() cannot be None (working copy)""" |
188 # files might have to be traced back to the fctx parent of the last | 190 # files might have to be traced back to the fctx parent of the last |
189 # one-side-only changeset, but not further back than that | 191 # one-side-only changeset, but not further back than that |
190 repo = a._repo | 192 repo = a._repo |
191 | 193 |
199 % (a, b)) | 201 % (a, b)) |
200 limit = _findlimit(repo, a, b) | 202 limit = _findlimit(repo, a, b) |
201 if debug: | 203 if debug: |
202 dbg('debug.copies: search limit: %d\n' % limit) | 204 dbg('debug.copies: search limit: %d\n' % limit) |
203 am = a.manifest() | 205 am = a.manifest() |
206 basemf = None if base is None else base.manifest() | |
204 | 207 |
205 # find where new files came from | 208 # find where new files came from |
206 # we currently don't try to find where old files went, too expensive | 209 # we currently don't try to find where old files went, too expensive |
207 # this means we can miss a case like 'hg rm b; hg cp a b' | 210 # this means we can miss a case like 'hg rm b; hg cp a b' |
208 cm = {} | 211 cm = {} |
230 fctx = b[f] | 233 fctx = b[f] |
231 fctx._ancestrycontext = ancestrycontext | 234 fctx._ancestrycontext = ancestrycontext |
232 | 235 |
233 if debug: | 236 if debug: |
234 start = util.timer() | 237 start = util.timer() |
235 opath = _tracefile(fctx, am, limit) | 238 opath = _tracefile(fctx, am, basemf, limit) |
236 if opath: | 239 if opath: |
237 if debug: | 240 if debug: |
238 dbg('debug.copies: rename of: %s\n' % opath) | 241 dbg('debug.copies: rename of: %s\n' % opath) |
239 cm[f] = opath | 242 cm[f] = opath |
240 if debug: | 243 if debug: |
309 if f in newcopies: | 312 if f in newcopies: |
310 del newcopies[f] | 313 del newcopies[f] |
311 heapq.heappush(work, (c, parent, newcopies)) | 314 heapq.heappush(work, (c, parent, newcopies)) |
312 assert False | 315 assert False |
313 | 316 |
314 def _forwardcopies(a, b, match=None): | 317 def _forwardcopies(a, b, base=None, match=None): |
315 """find {dst@b: src@a} copy mapping where a is an ancestor of b""" | 318 """find {dst@b: src@a} copy mapping where a is an ancestor of b""" |
316 | 319 |
320 if base is None: | |
321 base = a | |
317 match = a.repo().narrowmatch(match) | 322 match = a.repo().narrowmatch(match) |
318 # check for working copy | 323 # check for working copy |
319 if b.rev() is None: | 324 if b.rev() is None: |
320 cm = _committedforwardcopies(a, b.p1(), match) | 325 cm = _committedforwardcopies(a, b.p1(), base, match) |
321 # combine copies from dirstate if necessary | 326 # combine copies from dirstate if necessary |
322 copies = _chain(cm, _dirstatecopies(b._repo, match)) | 327 copies = _chain(cm, _dirstatecopies(b._repo, match)) |
323 else: | 328 else: |
324 copies = _committedforwardcopies(a, b, match) | 329 copies = _committedforwardcopies(a, b, base, match) |
325 return copies | 330 return copies |
326 | 331 |
327 def _backwardrenames(a, b, match): | 332 def _backwardrenames(a, b, match): |
328 if a._repo.ui.config('experimental', 'copytrace') == 'off': | 333 if a._repo.ui.config('experimental', 'copytrace') == 'off': |
329 return {} | 334 return {} |
367 repo.ui.debug('debug.copies: search mode: backward\n') | 372 repo.ui.debug('debug.copies: search mode: backward\n') |
368 copies = _backwardrenames(x, y, match=match) | 373 copies = _backwardrenames(x, y, match=match) |
369 else: | 374 else: |
370 if debug: | 375 if debug: |
371 repo.ui.debug('debug.copies: search mode: combined\n') | 376 repo.ui.debug('debug.copies: search mode: combined\n') |
377 base = None | |
378 if a.rev() != node.nullrev: | |
379 base = x | |
372 copies = _chain(_backwardrenames(x, a, match=match), | 380 copies = _chain(_backwardrenames(x, a, match=match), |
373 _forwardcopies(a, y, match=match)) | 381 _forwardcopies(a, y, base, match=match)) |
374 _filter(x, y, copies) | 382 _filter(x, y, copies) |
375 return copies | 383 return copies |
376 | 384 |
377 def mergecopies(repo, c1, c2, base): | 385 def mergecopies(repo, c1, c2, base): |
378 """ | 386 """ |