571 for dst, src in copies1.items(): |
571 for dst, src in copies1.items(): |
572 inversecopies1.setdefault(src, []).append(dst) |
572 inversecopies1.setdefault(src, []).append(dst) |
573 for dst, src in copies2.items(): |
573 for dst, src in copies2.items(): |
574 inversecopies2.setdefault(src, []).append(dst) |
574 inversecopies2.setdefault(src, []).append(dst) |
575 |
575 |
576 copy = {} |
576 copy1 = {} |
|
577 copy2 = {} |
577 diverge = {} |
578 diverge = {} |
578 renamedelete = {} |
579 renamedelete1 = {} |
|
580 renamedelete2 = {} |
579 allsources = set(inversecopies1) | set(inversecopies2) |
581 allsources = set(inversecopies1) | set(inversecopies2) |
580 for src in allsources: |
582 for src in allsources: |
581 dsts1 = inversecopies1.get(src) |
583 dsts1 = inversecopies1.get(src) |
582 dsts2 = inversecopies2.get(src) |
584 dsts2 = inversecopies2.get(src) |
583 if dsts1 and dsts2: |
585 if dsts1 and dsts2: |
590 # consider it not divergent. For example, if side 1 copies 'a' |
592 # consider it not divergent. For example, if side 1 copies 'a' |
591 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c' |
593 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c' |
592 # and 'd' and deletes 'a'. |
594 # and 'd' and deletes 'a'. |
593 if dsts1 & dsts2: |
595 if dsts1 & dsts2: |
594 for dst in dsts1 & dsts2: |
596 for dst in dsts1 & dsts2: |
595 copy[dst] = src |
597 copy1[dst] = src |
|
598 copy2[dst] = src |
596 else: |
599 else: |
597 diverge[src] = sorted(dsts1 | dsts2) |
600 diverge[src] = sorted(dsts1 | dsts2) |
598 elif src in m1 and src in m2: |
601 elif src in m1 and src in m2: |
599 # copied on both sides |
602 # copied on both sides |
600 dsts1 = set(dsts1) |
603 dsts1 = set(dsts1) |
601 dsts2 = set(dsts2) |
604 dsts2 = set(dsts2) |
602 for dst in dsts1 & dsts2: |
605 for dst in dsts1 & dsts2: |
603 copy[dst] = src |
606 copy1[dst] = src |
|
607 copy2[dst] = src |
604 # TODO: Handle cases where it was renamed on one side and copied |
608 # TODO: Handle cases where it was renamed on one side and copied |
605 # on the other side |
609 # on the other side |
606 elif dsts1: |
610 elif dsts1: |
607 # copied/renamed only on side 1 |
611 # copied/renamed only on side 1 |
608 _checksinglesidecopies( |
612 _checksinglesidecopies( |
609 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete |
613 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1 |
610 ) |
614 ) |
611 elif dsts2: |
615 elif dsts2: |
612 # copied/renamed only on side 2 |
616 # copied/renamed only on side 2 |
613 _checksinglesidecopies( |
617 _checksinglesidecopies( |
614 src, dsts2, m2, m1, mb, c1, base, copy, renamedelete |
618 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2 |
615 ) |
619 ) |
616 |
620 |
617 # find interesting file sets from manifests |
621 # find interesting file sets from manifests |
618 addedinm1 = m1.filesnotin(mb, repo.narrowmatch()) |
622 addedinm1 = m1.filesnotin(mb, repo.narrowmatch()) |
619 addedinm2 = m2.filesnotin(mb, repo.narrowmatch()) |
623 addedinm2 = m2.filesnotin(mb, repo.narrowmatch()) |
632 if repo.ui.debugflag: |
636 if repo.ui.debugflag: |
633 renamedeleteset = set() |
637 renamedeleteset = set() |
634 divergeset = set() |
638 divergeset = set() |
635 for dsts in diverge.values(): |
639 for dsts in diverge.values(): |
636 divergeset.update(dsts) |
640 divergeset.update(dsts) |
637 for dsts in renamedelete.values(): |
641 for dsts in renamedelete1.values(): |
|
642 renamedeleteset.update(dsts) |
|
643 for dsts in renamedelete2.values(): |
638 renamedeleteset.update(dsts) |
644 renamedeleteset.update(dsts) |
639 |
645 |
640 repo.ui.debug( |
646 repo.ui.debug( |
641 b" all copies found (* = to merge, ! = divergent, " |
647 b" all copies found (* = to merge, ! = divergent, " |
642 b"% = renamed and deleted):\n" |
648 b"% = renamed and deleted):\n" |
643 ) |
649 ) |
644 for f in sorted(fullcopy): |
650 for f in sorted(fullcopy): |
645 note = b"" |
651 note = b"" |
646 if f in copy: |
652 if f in copy1 or f in copy2: |
647 note += b"*" |
653 note += b"*" |
648 if f in divergeset: |
654 if f in divergeset: |
649 note += b"!" |
655 note += b"!" |
650 if f in renamedeleteset: |
656 if f in renamedeleteset: |
651 note += b"%" |
657 note += b"%" |
655 del renamedeleteset |
661 del renamedeleteset |
656 del divergeset |
662 del divergeset |
657 |
663 |
658 repo.ui.debug(b" checking for directory renames\n") |
664 repo.ui.debug(b" checking for directory renames\n") |
659 |
665 |
660 dirmove, movewithdir = _dir_renames(repo, c1, c2, copy, fullcopy, u1, u2) |
666 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2) |
661 |
667 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1) |
662 return copy, movewithdir, diverge, renamedelete, dirmove |
668 |
663 |
669 copy1.update(copy2) |
664 |
670 renamedelete1.update(renamedelete2) |
665 def _dir_renames(repo, c1, c2, copy, fullcopy, u1, u2): |
671 movewithdir1.update(movewithdir2) |
|
672 dirmove1.update(dirmove2) |
|
673 |
|
674 return copy1, movewithdir1, diverge, renamedelete1, dirmove1 |
|
675 |
|
676 |
|
677 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles): |
|
678 """Finds moved directories and files that should move with them. |
|
679 |
|
680 ctx: the context for one of the sides |
|
681 copy: files copied on the same side (as ctx) |
|
682 fullcopy: files copied on the same side (as ctx), including those that |
|
683 merge.manifestmerge() won't care about |
|
684 addedfiles: added files on the other side (compared to ctx) |
|
685 """ |
666 # generate a directory move map |
686 # generate a directory move map |
667 d1, d2 = c1.dirs(), c2.dirs() |
687 d = ctx.dirs() |
668 invalid = set() |
688 invalid = set() |
669 dirmove = {} |
689 dirmove = {} |
670 |
690 |
671 # examine each file copy for a potential directory move, which is |
691 # examine each file copy for a potential directory move, which is |
672 # when all the files in a directory are moved to a new directory |
692 # when all the files in a directory are moved to a new directory |
673 for dst, src in pycompat.iteritems(fullcopy): |
693 for dst, src in pycompat.iteritems(fullcopy): |
674 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst) |
694 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst) |
675 if dsrc in invalid: |
695 if dsrc in invalid: |
676 # already seen to be uninteresting |
696 # already seen to be uninteresting |
677 continue |
697 continue |
678 elif dsrc in d1 and ddst in d1: |
698 elif dsrc in d and ddst in d: |
679 # directory wasn't entirely moved locally |
699 # directory wasn't entirely moved locally |
680 invalid.add(dsrc) |
|
681 elif dsrc in d2 and ddst in d2: |
|
682 # directory wasn't entirely moved remotely |
|
683 invalid.add(dsrc) |
700 invalid.add(dsrc) |
684 elif dsrc in dirmove and dirmove[dsrc] != ddst: |
701 elif dsrc in dirmove and dirmove[dsrc] != ddst: |
685 # files from the same directory moved to two different places |
702 # files from the same directory moved to two different places |
686 invalid.add(dsrc) |
703 invalid.add(dsrc) |
687 else: |
704 else: |