450 revision 4, and if user have copytrace disabled, we prints the following |
450 revision 4, and if user have copytrace disabled, we prints the following |
451 message: |
451 message: |
452 |
452 |
453 ```other changed <file> which local deleted``` |
453 ```other changed <file> which local deleted``` |
454 |
454 |
455 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and |
455 Returns a tuple where: |
456 "dirmove". |
456 |
457 |
457 "branch_copies" an instance of branch_copies. |
458 "copy" is a mapping from destination name -> source name, |
|
459 where source is in c1 and destination is in c2 or vice-versa. |
|
460 |
|
461 "movewithdir" is a mapping from source name -> destination name, |
|
462 where the file at source present in one context but not the other |
|
463 needs to be moved to destination by the merge process, because the |
|
464 other context moved the directory it is in. |
|
465 |
458 |
466 "diverge" is a mapping of source name -> list of destination names |
459 "diverge" is a mapping of source name -> list of destination names |
467 for divergent renames. |
460 for divergent renames. |
468 |
461 |
469 "renamedelete" is a mapping of source name -> list of destination |
|
470 names for files deleted in c1 that were renamed in c2 or vice-versa. |
|
471 |
|
472 "dirmove" is a mapping of detected source dir -> destination dir renames. |
|
473 This is needed for handling changes to new files previously grafted into |
|
474 renamed directories. |
|
475 |
|
476 This function calls different copytracing algorithms based on config. |
462 This function calls different copytracing algorithms based on config. |
477 """ |
463 """ |
478 # avoid silly behavior for update from empty dir |
464 # avoid silly behavior for update from empty dir |
479 if not c1 or not c2 or c1 == c2: |
465 if not c1 or not c2 or c1 == c2: |
480 return {}, {}, {}, {}, {} |
466 return branch_copies(), {} |
481 |
467 |
482 narrowmatch = c1.repo().narrowmatch() |
468 narrowmatch = c1.repo().narrowmatch() |
483 |
469 |
484 # avoid silly behavior for parent -> working dir |
470 # avoid silly behavior for parent -> working dir |
485 if c2.node() is None and c1.node() == repo.dirstate.p1(): |
471 if c2.node() is None and c1.node() == repo.dirstate.p1(): |
486 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {} |
472 return branch_copies(_dirstatecopies(repo, narrowmatch)), {} |
487 |
473 |
488 copytracing = repo.ui.config(b'experimental', b'copytrace') |
474 copytracing = repo.ui.config(b'experimental', b'copytrace') |
489 if stringutil.parsebool(copytracing) is False: |
475 if stringutil.parsebool(copytracing) is False: |
490 # stringutil.parsebool() returns None when it is unable to parse the |
476 # stringutil.parsebool() returns None when it is unable to parse the |
491 # value, so we should rely on making sure copytracing is on such cases |
477 # value, so we should rely on making sure copytracing is on such cases |
492 return {}, {}, {}, {}, {} |
478 return branch_copies(), {} |
493 |
479 |
494 if usechangesetcentricalgo(repo): |
480 if usechangesetcentricalgo(repo): |
495 # The heuristics don't make sense when we need changeset-centric algos |
481 # The heuristics don't make sense when we need changeset-centric algos |
496 return _fullcopytracing(repo, c1, c2, base) |
482 return _fullcopytracing(repo, c1, c2, base) |
497 |
483 |
546 # dst not added on side 2 (handle as regular |
532 # dst not added on side 2 (handle as regular |
547 # "both created" case in manifestmerge otherwise) |
533 # "both created" case in manifestmerge otherwise) |
548 copy[dst] = src |
534 copy[dst] = src |
549 |
535 |
550 |
536 |
|
537 class branch_copies(object): |
|
538 """Information about copies made on one side of a merge/graft. |
|
539 |
|
540 "copy" is a mapping from destination name -> source name, |
|
541 where source is in c1 and destination is in c2 or vice-versa. |
|
542 |
|
543 "movewithdir" is a mapping from source name -> destination name, |
|
544 where the file at source present in one context but not the other |
|
545 needs to be moved to destination by the merge process, because the |
|
546 other context moved the directory it is in. |
|
547 |
|
548 "renamedelete" is a mapping of source name -> list of destination |
|
549 names for files deleted in c1 that were renamed in c2 or vice-versa. |
|
550 |
|
551 "dirmove" is a mapping of detected source dir -> destination dir renames. |
|
552 This is needed for handling changes to new files previously grafted into |
|
553 renamed directories. |
|
554 """ |
|
555 |
|
556 def __init__( |
|
557 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None |
|
558 ): |
|
559 self.copy = {} if copy is None else copy |
|
560 self.renamedelete = {} if renamedelete is None else renamedelete |
|
561 self.dirmove = {} if dirmove is None else dirmove |
|
562 self.movewithdir = {} if movewithdir is None else movewithdir |
|
563 |
|
564 |
551 def _fullcopytracing(repo, c1, c2, base): |
565 def _fullcopytracing(repo, c1, c2, base): |
552 """ The full copytracing algorithm which finds all the new files that were |
566 """ The full copytracing algorithm which finds all the new files that were |
553 added from merge base up to the top commit and for each file it checks if |
567 added from merge base up to the top commit and for each file it checks if |
554 this file was copied from another file. |
568 this file was copied from another file. |
555 |
569 |
562 |
576 |
563 copies1 = pathcopies(base, c1) |
577 copies1 = pathcopies(base, c1) |
564 copies2 = pathcopies(base, c2) |
578 copies2 = pathcopies(base, c2) |
565 |
579 |
566 if not (copies1 or copies2): |
580 if not (copies1 or copies2): |
567 return {}, {}, {}, {}, {} |
581 return branch_copies(), {} |
568 |
582 |
569 inversecopies1 = {} |
583 inversecopies1 = {} |
570 inversecopies2 = {} |
584 inversecopies2 = {} |
571 for dst, src in copies1.items(): |
585 for dst, src in copies1.items(): |
572 inversecopies1.setdefault(src, []).append(dst) |
586 inversecopies1.setdefault(src, []).append(dst) |
670 copy1.update(copy2) |
684 copy1.update(copy2) |
671 renamedelete1.update(renamedelete2) |
685 renamedelete1.update(renamedelete2) |
672 movewithdir1.update(movewithdir2) |
686 movewithdir1.update(movewithdir2) |
673 dirmove1.update(dirmove2) |
687 dirmove1.update(dirmove2) |
674 |
688 |
675 return copy1, movewithdir1, diverge, renamedelete1, dirmove1 |
689 return branch_copies(copy1, renamedelete1, dirmove1, movewithdir1), diverge |
676 |
690 |
677 |
691 |
678 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles): |
692 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles): |
679 """Finds moved directories and files that should move with them. |
693 """Finds moved directories and files that should move with them. |
680 |
694 |
844 # if there are a few related copies then we'll merge |
858 # if there are a few related copies then we'll merge |
845 # changes into all of them. This matches the behaviour |
859 # changes into all of them. This matches the behaviour |
846 # of upstream copytracing |
860 # of upstream copytracing |
847 copies[candidate] = f |
861 copies[candidate] = f |
848 |
862 |
849 return copies, {}, {}, {}, {} |
863 return branch_copies(copies), {} |
850 |
864 |
851 |
865 |
852 def _related(f1, f2): |
866 def _related(f1, f2): |
853 """return True if f1 and f2 filectx have a common ancestor |
867 """return True if f1 and f2 filectx have a common ancestor |
854 |
868 |