Mercurial > hg
comparison mercurial/copies.py @ 30195:88626de195f8
copies: make _checkcopies handle simple renames in a rotated DAG
This introduces a distinction between "merge base" and
"topological common ancestor". During a regular merge, these two are
identical. Graft, however, performs a merge in a rotated DAG, where the
merge base will not be a common ancestor at all in the
original DAG.
To correctly find copies in case of a graft, we need to take both the
merge base and the topological CA into account, and track any renames
between them in reverse. Fortunately we can detect this in advance,
see comment in the code about "backwards".
This patch only supports finding non-divergent renames contained entirely
between the merge base and the topological CA. Further patches are coming
to support more complex cases.
(Pierre-Yves David was involved in the cleanup of this patch.)
author | Gábor Stefanik <gabor.stefanik@nng.com> |
---|---|
date | Thu, 13 Oct 2016 02:03:54 +0200 |
parents | 8c69c52ced98 |
children | d738cda70894 |
comparison
equal
deleted
inserted
replaced
30194:8c69c52ced98 | 30195:88626de195f8 |
---|---|
372 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2) | 372 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2) |
373 u1u, u2u = u1r, u2r | 373 u1u, u2u = u1r, u2r |
374 bothnew = sorted(addedinm1 & addedinm2) | 374 bothnew = sorted(addedinm1 & addedinm2) |
375 | 375 |
376 for f in u1u: | 376 for f in u1u: |
377 _checkcopies(c1, f, m1, m2, base, limit, data1) | 377 _checkcopies(c1, f, m1, m2, base, tca, limit, data1) |
378 | 378 |
379 for f in u2u: | 379 for f in u2u: |
380 _checkcopies(c2, f, m2, m1, base, limit, data2) | 380 _checkcopies(c2, f, m2, m1, base, tca, limit, data2) |
381 | 381 |
382 copy = dict(data1['copy'].items() + data2['copy'].items()) | 382 copy = dict(data1['copy'].items() + data2['copy'].items()) |
383 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items()) | 383 fullcopy = dict(data1['fullcopy'].items() + data2['fullcopy'].items()) |
384 | 384 |
385 renamedelete = {} | 385 renamedelete = {} |
403 bothdata = {'copy': {}, | 403 bothdata = {'copy': {}, |
404 'fullcopy': {}, | 404 'fullcopy': {}, |
405 'diverge': bothdiverge, | 405 'diverge': bothdiverge, |
406 } | 406 } |
407 for f in bothnew: | 407 for f in bothnew: |
408 _checkcopies(c1, f, m1, m2, base, limit, bothdata) | 408 _checkcopies(c1, f, m1, m2, base, tca, limit, bothdata) |
409 _checkcopies(c2, f, m2, m1, base, limit, bothdata) | 409 _checkcopies(c2, f, m2, m1, base, tca, limit, bothdata) |
410 for of, fl in bothdiverge.items(): | 410 for of, fl in bothdiverge.items(): |
411 if len(fl) == 2 and fl[0] == fl[1]: | 411 if len(fl) == 2 and fl[0] == fl[1]: |
412 copy[fl[0]] = of # not actually divergent, just matching renames | 412 copy[fl[0]] = of # not actually divergent, just matching renames |
413 | 413 |
414 if fullcopy and repo.ui.debugflag: | 414 if fullcopy and repo.ui.debugflag: |
519 elif f1r == f2r or f1r < limit or f2r < limit: | 519 elif f1r == f2r or f1r < limit or f2r < limit: |
520 return False # copy no longer relevant | 520 return False # copy no longer relevant |
521 except StopIteration: | 521 except StopIteration: |
522 return False | 522 return False |
523 | 523 |
524 def _checkcopies(ctx, f, m1, m2, base, limit, data): | 524 def _checkcopies(ctx, f, m1, m2, base, tca, limit, data): |
525 """ | 525 """ |
526 check possible copies of f from m1 to m2 | 526 check possible copies of f from m1 to m2 |
527 | 527 |
528 ctx = starting context for f in m1 | 528 ctx = starting context for f in m1 |
529 f = the filename to check (as in m1) | 529 f = the filename to check (as in m1) |
530 m1 = the source manifest | 530 m1 = the source manifest |
531 m2 = the destination manifest | 531 m2 = the destination manifest |
532 base = the changectx used as a merge base | 532 base = the changectx used as a merge base |
533 tca = topological common ancestor for graft-like scenarios | |
533 limit = the rev number to not search beyond | 534 limit = the rev number to not search beyond |
534 data = dictionary of dictionary to store copy data. (see mergecopies) | 535 data = dictionary of dictionary to store copy data. (see mergecopies) |
535 | 536 |
536 note: limit is only an optimization, and there is no guarantee that | 537 note: limit is only an optimization, and there is no guarantee that |
537 irrelevant revisions will not be limited | 538 irrelevant revisions will not be limited |
538 there is no easy way to make this algorithm stop in a guaranteed way | 539 there is no easy way to make this algorithm stop in a guaranteed way |
539 once it "goes behind a certain revision". | 540 once it "goes behind a certain revision". |
540 """ | 541 """ |
541 | 542 |
542 mb = base.manifest() | 543 mb = base.manifest() |
544 # Might be true if this call is about finding backward renames, | |
545 # This happens in the case of grafts because the DAG is then rotated. | |
546 # If the file exists in both the base and the source, we are not looking | |
547 # for a rename on the source side, but on the part of the DAG that is | |
548 # traversed backwards. | |
549 # | |
550 # In the case there is both backward and forward renames (before and after | |
551 # the base) this is more complicated as we must detect a divergence. This | |
552 # is currently broken and hopefully some later code update will make that | |
553 # work (we use 'backwards = False' in that case) | |
554 backwards = base != tca and f in mb | |
543 getfctx = _makegetfctx(ctx) | 555 getfctx = _makegetfctx(ctx) |
544 | 556 |
545 of = None | 557 of = None |
546 seen = set([f]) | 558 seen = set([f]) |
547 for oc in getfctx(f, m1[f]).ancestors(): | 559 for oc in getfctx(f, m1[f]).ancestors(): |
552 if ocr < limit: | 564 if ocr < limit: |
553 break | 565 break |
554 continue | 566 continue |
555 seen.add(of) | 567 seen.add(of) |
556 | 568 |
557 data['fullcopy'][f] = of # remember for dir rename detection | 569 # remember for dir rename detection |
570 if backwards: | |
571 data['fullcopy'][of] = f # grafting backwards through renames | |
572 else: | |
573 data['fullcopy'][f] = of | |
558 if of not in m2: | 574 if of not in m2: |
559 continue # no match, keep looking | 575 continue # no match, keep looking |
560 if m2[of] == mb.get(of): | 576 if m2[of] == mb.get(of): |
561 return # no merge needed, quit early | 577 return # no merge needed, quit early |
562 c2 = getfctx(of, m2[of]) | 578 c2 = getfctx(of, m2[of]) |
563 # c2 might be a plain new file on added on destination side that is | 579 # c2 might be a plain new file on added on destination side that is |
564 # unrelated to the droids we are looking for. | 580 # unrelated to the droids we are looking for. |
565 cr = _related(oc, c2, base.rev()) | 581 cr = _related(oc, c2, tca.rev()) |
566 if cr and (of == f or of == c2.path()): # non-divergent | 582 if cr and (of == f or of == c2.path()): # non-divergent |
567 if of in mb: | 583 if backwards: |
584 data['copy'][of] = f | |
585 elif of in mb: | |
568 data['copy'][f] = of | 586 data['copy'][f] = of |
569 return | 587 return |
570 | 588 |
571 if of in mb: | 589 if of in mb: |
572 data['diverge'].setdefault(of, []).append(f) | 590 data['diverge'].setdefault(of, []).append(f) |