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)