comparison mercurial/merge.py @ 45282:b442920ab1de

merge: introduce mergeresult.addfile() and use it We want to use mergeresult object at more and more places instead of this actions dict to simplify code and further add new APIs to mergeresult object. This patch introduces `addfile()` which adds a new file to the internal actions dict for now. Differential Revision: https://phab.mercurial-scm.org/D8820
author Pulkit Goyal <7895pulkit@gmail.com>
date Fri, 24 Jul 2020 16:18:39 +0530
parents fe2040abb183
children f1fb9a079131
comparison
equal deleted inserted replaced
45281:fe2040abb183 45282:b442920ab1de
559 self._actions = {} 559 self._actions = {}
560 self._diverge = {} 560 self._diverge = {}
561 self._renamedelete = {} 561 self._renamedelete = {}
562 self._commitinfo = {} 562 self._commitinfo = {}
563 563
564 def updatevalues(self, actions, diverge, renamedelete, commitinfo): 564 def updatevalues(self, diverge, renamedelete, commitinfo):
565 self._actions = actions
566 self._diverge = diverge 565 self._diverge = diverge
567 self._renamedelete = renamedelete 566 self._renamedelete = renamedelete
568 self._commitinfo = commitinfo 567 self._commitinfo = commitinfo
568
569 def addfile(self, filename, action, data, message):
570 """ adds a new file to the mergeresult object
571
572 filename: file which we are adding
573 action: one of mergestatemod.ACTION_*
574 data: a tuple of information like fctx and ctx related to this merge
575 message: a message about the merge
576 """
577 self._actions[filename] = (action, data, message)
569 578
570 @property 579 @property
571 def actions(self): 580 def actions(self):
572 return self._actions 581 return self._actions
573 582
634 matcher = matcher to filter file lists 643 matcher = matcher to filter file lists
635 acceptremote = accept the incoming changes without prompting 644 acceptremote = accept the incoming changes without prompting
636 645
637 Returns an object of mergeresult class 646 Returns an object of mergeresult class
638 """ 647 """
648 mresult = mergeresult()
639 if matcher is not None and matcher.always(): 649 if matcher is not None and matcher.always():
640 matcher = None 650 matcher = None
641 651
642 # manifests fetched in order are going to be faster, so prime the caches 652 # manifests fetched in order are going to be faster, so prime the caches
643 [ 653 [
698 filesmatcher = scmutil.matchfiles(repo, relevantfiles) 708 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
699 matcher = matchmod.intersectmatchers(matcher, filesmatcher) 709 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
700 710
701 diff = m1.diff(m2, match=matcher) 711 diff = m1.diff(m2, match=matcher)
702 712
703 actions = {}
704 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff): 713 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
705 if n1 and n2: # file exists on both local and remote side 714 if n1 and n2: # file exists on both local and remote side
706 if f not in ma: 715 if f not in ma:
707 # TODO: what if they're renamed from different sources? 716 # TODO: what if they're renamed from different sources?
708 fa = branch_copies1.copy.get( 717 fa = branch_copies1.copy.get(
709 f, None 718 f, None
710 ) or branch_copies2.copy.get(f, None) 719 ) or branch_copies2.copy.get(f, None)
711 if fa is not None: 720 if fa is not None:
712 actions[f] = ( 721 mresult.addfile(
722 f,
713 mergestatemod.ACTION_MERGE, 723 mergestatemod.ACTION_MERGE,
714 (f, f, fa, False, pa.node()), 724 (f, f, fa, False, pa.node()),
715 b'both renamed from %s' % fa, 725 b'both renamed from %s' % fa,
716 ) 726 )
717 else: 727 else:
718 actions[f] = ( 728 mresult.addfile(
729 f,
719 mergestatemod.ACTION_MERGE, 730 mergestatemod.ACTION_MERGE,
720 (f, f, None, False, pa.node()), 731 (f, f, None, False, pa.node()),
721 b'both created', 732 b'both created',
722 ) 733 )
723 else: 734 else:
724 a = ma[f] 735 a = ma[f]
725 fla = ma.flags(f) 736 fla = ma.flags(f)
726 nol = b'l' not in fl1 + fl2 + fla 737 nol = b'l' not in fl1 + fl2 + fla
727 if n2 == a and fl2 == fla: 738 if n2 == a and fl2 == fla:
728 actions[f] = ( 739 mresult.addfile(
729 mergestatemod.ACTION_KEEP, 740 f, mergestatemod.ACTION_KEEP, (), b'remote unchanged',
730 (),
731 b'remote unchanged',
732 ) 741 )
733 elif n1 == a and fl1 == fla: # local unchanged - use remote 742 elif n1 == a and fl1 == fla: # local unchanged - use remote
734 if n1 == n2: # optimization: keep local content 743 if n1 == n2: # optimization: keep local content
735 actions[f] = ( 744 mresult.addfile(
745 f,
736 mergestatemod.ACTION_EXEC, 746 mergestatemod.ACTION_EXEC,
737 (fl2,), 747 (fl2,),
738 b'update permissions', 748 b'update permissions',
739 ) 749 )
740 else: 750 else:
741 actions[f] = ( 751 mresult.addfile(
752 f,
742 mergestatemod.ACTION_GET, 753 mergestatemod.ACTION_GET,
743 (fl2, False), 754 (fl2, False),
744 b'remote is newer', 755 b'remote is newer',
745 ) 756 )
746 if branchmerge: 757 if branchmerge:
747 commitinfo[f] = b'other' 758 commitinfo[f] = b'other'
748 elif nol and n2 == a: # remote only changed 'x' 759 elif nol and n2 == a: # remote only changed 'x'
749 actions[f] = ( 760 mresult.addfile(
761 f,
750 mergestatemod.ACTION_EXEC, 762 mergestatemod.ACTION_EXEC,
751 (fl2,), 763 (fl2,),
752 b'update permissions', 764 b'update permissions',
753 ) 765 )
754 elif nol and n1 == a: # local only changed 'x' 766 elif nol and n1 == a: # local only changed 'x'
755 actions[f] = ( 767 mresult.addfile(
768 f,
756 mergestatemod.ACTION_GET, 769 mergestatemod.ACTION_GET,
757 (fl1, False), 770 (fl1, False),
758 b'remote is newer', 771 b'remote is newer',
759 ) 772 )
760 if branchmerge: 773 if branchmerge:
761 commitinfo[f] = b'other' 774 commitinfo[f] = b'other'
762 else: # both changed something 775 else: # both changed something
763 actions[f] = ( 776 mresult.addfile(
777 f,
764 mergestatemod.ACTION_MERGE, 778 mergestatemod.ACTION_MERGE,
765 (f, f, f, False, pa.node()), 779 (f, f, f, False, pa.node()),
766 b'versions differ', 780 b'versions differ',
767 ) 781 )
768 elif n1: # file exists only on local side 782 elif n1: # file exists only on local side
771 elif ( 785 elif (
772 f in branch_copies1.movewithdir 786 f in branch_copies1.movewithdir
773 ): # directory rename, move local 787 ): # directory rename, move local
774 f2 = branch_copies1.movewithdir[f] 788 f2 = branch_copies1.movewithdir[f]
775 if f2 in m2: 789 if f2 in m2:
776 actions[f2] = ( 790 mresult.addfile(
791 f2,
777 mergestatemod.ACTION_MERGE, 792 mergestatemod.ACTION_MERGE,
778 (f, f2, None, True, pa.node()), 793 (f, f2, None, True, pa.node()),
779 b'remote directory rename, both created', 794 b'remote directory rename, both created',
780 ) 795 )
781 else: 796 else:
782 actions[f2] = ( 797 mresult.addfile(
798 f2,
783 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL, 799 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
784 (f, fl1), 800 (f, fl1),
785 b'remote directory rename - move from %s' % f, 801 b'remote directory rename - move from %s' % f,
786 ) 802 )
787 elif f in branch_copies1.copy: 803 elif f in branch_copies1.copy:
788 f2 = branch_copies1.copy[f] 804 f2 = branch_copies1.copy[f]
789 actions[f] = ( 805 mresult.addfile(
806 f,
790 mergestatemod.ACTION_MERGE, 807 mergestatemod.ACTION_MERGE,
791 (f, f2, f2, False, pa.node()), 808 (f, f2, f2, False, pa.node()),
792 b'local copied/moved from %s' % f2, 809 b'local copied/moved from %s' % f2,
793 ) 810 )
794 elif f in ma: # clean, a different, no remote 811 elif f in ma: # clean, a different, no remote
795 if n1 != ma[f]: 812 if n1 != ma[f]:
796 if acceptremote: 813 if acceptremote:
797 actions[f] = ( 814 mresult.addfile(
815 f,
798 mergestatemod.ACTION_REMOVE, 816 mergestatemod.ACTION_REMOVE,
799 None, 817 None,
800 b'remote delete', 818 b'remote delete',
801 ) 819 )
802 else: 820 else:
803 actions[f] = ( 821 mresult.addfile(
822 f,
804 mergestatemod.ACTION_CHANGED_DELETED, 823 mergestatemod.ACTION_CHANGED_DELETED,
805 (f, None, f, False, pa.node()), 824 (f, None, f, False, pa.node()),
806 b'prompt changed/deleted', 825 b'prompt changed/deleted',
807 ) 826 )
808 elif n1 == addednodeid: 827 elif n1 == addednodeid:
809 # This file was locally added. We should forget it instead of 828 # This file was locally added. We should forget it instead of
810 # deleting it. 829 # deleting it.
811 actions[f] = ( 830 mresult.addfile(
812 mergestatemod.ACTION_FORGET, 831 f, mergestatemod.ACTION_FORGET, None, b'remote deleted',
813 None,
814 b'remote deleted',
815 ) 832 )
816 else: 833 else:
817 actions[f] = ( 834 mresult.addfile(
818 mergestatemod.ACTION_REMOVE, 835 f, mergestatemod.ACTION_REMOVE, None, b'other deleted',
819 None,
820 b'other deleted',
821 ) 836 )
822 elif n2: # file exists only on remote side 837 elif n2: # file exists only on remote side
823 if f in copied1: 838 if f in copied1:
824 pass # we'll deal with it on m1 side 839 pass # we'll deal with it on m1 side
825 elif f in branch_copies2.movewithdir: 840 elif f in branch_copies2.movewithdir:
826 f2 = branch_copies2.movewithdir[f] 841 f2 = branch_copies2.movewithdir[f]
827 if f2 in m1: 842 if f2 in m1:
828 actions[f2] = ( 843 mresult.addfile(
844 f2,
829 mergestatemod.ACTION_MERGE, 845 mergestatemod.ACTION_MERGE,
830 (f2, f, None, False, pa.node()), 846 (f2, f, None, False, pa.node()),
831 b'local directory rename, both created', 847 b'local directory rename, both created',
832 ) 848 )
833 else: 849 else:
834 actions[f2] = ( 850 mresult.addfile(
851 f2,
835 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET, 852 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
836 (f, fl2), 853 (f, fl2),
837 b'local directory rename - get from %s' % f, 854 b'local directory rename - get from %s' % f,
838 ) 855 )
839 elif f in branch_copies2.copy: 856 elif f in branch_copies2.copy:
840 f2 = branch_copies2.copy[f] 857 f2 = branch_copies2.copy[f]
841 if f2 in m2: 858 if f2 in m2:
842 actions[f] = ( 859 mresult.addfile(
860 f,
843 mergestatemod.ACTION_MERGE, 861 mergestatemod.ACTION_MERGE,
844 (f2, f, f2, False, pa.node()), 862 (f2, f, f2, False, pa.node()),
845 b'remote copied from %s' % f2, 863 b'remote copied from %s' % f2,
846 ) 864 )
847 else: 865 else:
848 actions[f] = ( 866 mresult.addfile(
867 f,
849 mergestatemod.ACTION_MERGE, 868 mergestatemod.ACTION_MERGE,
850 (f2, f, f2, True, pa.node()), 869 (f2, f, f2, True, pa.node()),
851 b'remote moved from %s' % f2, 870 b'remote moved from %s' % f2,
852 ) 871 )
853 elif f not in ma: 872 elif f not in ma:
861 # y y y | merge 880 # y y y | merge
862 # 881 #
863 # Checking whether the files are different is expensive, so we 882 # Checking whether the files are different is expensive, so we
864 # don't do that when we can avoid it. 883 # don't do that when we can avoid it.
865 if not force: 884 if not force:
866 actions[f] = ( 885 mresult.addfile(
886 f,
867 mergestatemod.ACTION_CREATED, 887 mergestatemod.ACTION_CREATED,
868 (fl2,), 888 (fl2,),
869 b'remote created', 889 b'remote created',
870 ) 890 )
871 elif not branchmerge: 891 elif not branchmerge:
872 actions[f] = ( 892 mresult.addfile(
893 f,
873 mergestatemod.ACTION_CREATED, 894 mergestatemod.ACTION_CREATED,
874 (fl2,), 895 (fl2,),
875 b'remote created', 896 b'remote created',
876 ) 897 )
877 else: 898 else:
878 actions[f] = ( 899 mresult.addfile(
900 f,
879 mergestatemod.ACTION_CREATED_MERGE, 901 mergestatemod.ACTION_CREATED_MERGE,
880 (fl2, pa.node()), 902 (fl2, pa.node()),
881 b'remote created, get or merge', 903 b'remote created, get or merge',
882 ) 904 )
883 elif n2 != ma[f]: 905 elif n2 != ma[f]:
886 if f.startswith(d): 908 if f.startswith(d):
887 # new file added in a directory that was moved 909 # new file added in a directory that was moved
888 df = branch_copies1.dirmove[d] + f[len(d) :] 910 df = branch_copies1.dirmove[d] + f[len(d) :]
889 break 911 break
890 if df is not None and df in m1: 912 if df is not None and df in m1:
891 actions[df] = ( 913 mresult.addfile(
914 df,
892 mergestatemod.ACTION_MERGE, 915 mergestatemod.ACTION_MERGE,
893 (df, f, f, False, pa.node()), 916 (df, f, f, False, pa.node()),
894 b'local directory rename - respect move ' 917 b'local directory rename - respect move '
895 b'from %s' % f, 918 b'from %s' % f,
896 ) 919 )
897 elif acceptremote: 920 elif acceptremote:
898 actions[f] = ( 921 mresult.addfile(
922 f,
899 mergestatemod.ACTION_CREATED, 923 mergestatemod.ACTION_CREATED,
900 (fl2,), 924 (fl2,),
901 b'remote recreating', 925 b'remote recreating',
902 ) 926 )
903 else: 927 else:
904 actions[f] = ( 928 mresult.addfile(
929 f,
905 mergestatemod.ACTION_DELETED_CHANGED, 930 mergestatemod.ACTION_DELETED_CHANGED,
906 (None, f, f, False, pa.node()), 931 (None, f, f, False, pa.node()),
907 b'prompt deleted/changed', 932 b'prompt deleted/changed',
908 ) 933 )
909 934
910 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'): 935 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
911 # If we are merging, look for path conflicts. 936 # If we are merging, look for path conflicts.
912 checkpathconflicts(repo, wctx, p2, actions) 937 checkpathconflicts(repo, wctx, p2, mresult.actions)
913 938
914 narrowmatch = repo.narrowmatch() 939 narrowmatch = repo.narrowmatch()
915 if not narrowmatch.always(): 940 if not narrowmatch.always():
916 # Updates "actions" in place 941 # Updates "actions" in place
917 _filternarrowactions(narrowmatch, branchmerge, actions) 942 _filternarrowactions(narrowmatch, branchmerge, mresult.actions)
918 943
919 renamedelete = branch_copies1.renamedelete 944 renamedelete = branch_copies1.renamedelete
920 renamedelete.update(branch_copies2.renamedelete) 945 renamedelete.update(branch_copies2.renamedelete)
921 946
922 mresult = mergeresult() 947 mresult.updatevalues(diverge, renamedelete, commitinfo)
923 mresult.updatevalues(actions, diverge, renamedelete, commitinfo)
924 return mresult 948 return mresult
925 949
926 950
927 def _resolvetrivial(repo, wctx, mctx, ancestor, actions): 951 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
928 """Resolves false conflicts where the nodeid changed but the content 952 """Resolves false conflicts where the nodeid changed but the content
1044 fbids[f] = {m: [a]} 1068 fbids[f] = {m: [a]}
1045 1069
1046 # Call for bids 1070 # Call for bids
1047 # Pick the best bid for each file 1071 # Pick the best bid for each file
1048 repo.ui.note(_(b'\nauction for merging merge bids\n')) 1072 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1049 actions = {} 1073 mresult = mergeresult()
1050 for f, bids in sorted(fbids.items()): 1074 for f, bids in sorted(fbids.items()):
1051 # bids is a mapping from action method to list af actions 1075 # bids is a mapping from action method to list af actions
1052 # Consensus? 1076 # Consensus?
1053 if len(bids) == 1: # all bids are the same kind of method 1077 if len(bids) == 1: # all bids are the same kind of method
1054 m, l = list(bids.items())[0] 1078 m, l = list(bids.items())[0]
1055 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 1079 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1056 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m)) 1080 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1057 actions[f] = l[0] 1081 mresult.addfile(f, *l[0])
1058 continue 1082 continue
1059 # If keep is an option, just do it. 1083 # If keep is an option, just do it.
1060 if mergestatemod.ACTION_KEEP in bids: 1084 if mergestatemod.ACTION_KEEP in bids:
1061 repo.ui.note(_(b" %s: picking 'keep' action\n") % f) 1085 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1062 actions[f] = bids[mergestatemod.ACTION_KEEP][0] 1086 mresult.addfile(f, *bids[mergestatemod.ACTION_KEEP][0])
1063 continue 1087 continue
1064 # If there are gets and they all agree [how could they not?], do it. 1088 # If there are gets and they all agree [how could they not?], do it.
1065 if mergestatemod.ACTION_GET in bids: 1089 if mergestatemod.ACTION_GET in bids:
1066 ga0 = bids[mergestatemod.ACTION_GET][0] 1090 ga0 = bids[mergestatemod.ACTION_GET][0]
1067 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]): 1091 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1068 repo.ui.note(_(b" %s: picking 'get' action\n") % f) 1092 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1069 actions[f] = ga0 1093 mresult.addfile(f, *ga0)
1070 continue 1094 continue
1071 # TODO: Consider other simple actions such as mode changes 1095 # TODO: Consider other simple actions such as mode changes
1072 # Handle inefficient democrazy. 1096 # Handle inefficient democrazy.
1073 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f) 1097 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1074 for m, l in sorted(bids.items()): 1098 for m, l in sorted(bids.items()):
1077 # Pick random action. TODO: Instead, prompt user when resolving 1101 # Pick random action. TODO: Instead, prompt user when resolving
1078 m, l = list(bids.items())[0] 1102 m, l = list(bids.items())[0]
1079 repo.ui.warn( 1103 repo.ui.warn(
1080 _(b' %s: ambiguous merge - picked %s action\n') % (f, m) 1104 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1081 ) 1105 )
1082 actions[f] = l[0] 1106 mresult.addfile(f, *l[0])
1083 continue 1107 continue
1084 repo.ui.note(_(b'end of auction\n\n')) 1108 repo.ui.note(_(b'end of auction\n\n'))
1085 # TODO: think about commitinfo when bid merge is used 1109 # TODO: think about commitinfo when bid merge is used
1086 mresult = mergeresult() 1110 mresult.updatevalues(diverge, renamedelete, {})
1087 mresult.updatevalues(actions, diverge, renamedelete, {})
1088 1111
1089 if wctx.rev() is None: 1112 if wctx.rev() is None:
1090 fractions = _forgetremoved(wctx, mctx, branchmerge) 1113 fractions = _forgetremoved(wctx, mctx, branchmerge)
1091 mresult.actions.update(fractions) 1114 mresult.actions.update(fractions)
1092 1115
1868 b"$$ &Changed $$ &Delete" 1891 b"$$ &Changed $$ &Delete"
1869 ) 1892 )
1870 % prompts, 1893 % prompts,
1871 0, 1894 0,
1872 ): 1895 ):
1873 mresult.actions[f] = ( 1896 mresult.addfile(
1874 mergestatemod.ACTION_REMOVE, 1897 f, mergestatemod.ACTION_REMOVE, None, b'prompt delete',
1875 None,
1876 b'prompt delete',
1877 ) 1898 )
1878 elif f in p1: 1899 elif f in p1:
1879 mresult.actions[f] = ( 1900 mresult.addfile(
1901 f,
1880 mergestatemod.ACTION_ADD_MODIFIED, 1902 mergestatemod.ACTION_ADD_MODIFIED,
1881 None, 1903 None,
1882 b'prompt keep', 1904 b'prompt keep',
1883 ) 1905 )
1884 else: 1906 else:
1885 mresult.actions[f] = ( 1907 mresult.addfile(
1886 mergestatemod.ACTION_ADD, 1908 f, mergestatemod.ACTION_ADD, None, b'prompt keep',
1887 None,
1888 b'prompt keep',
1889 ) 1909 )
1890 elif m == mergestatemod.ACTION_DELETED_CHANGED: 1910 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1891 f1, f2, fa, move, anc = args 1911 f1, f2, fa, move, anc = args
1892 flags = p2[f2].flags() 1912 flags = p2[f2].flags()
1893 if ( 1913 if (
1900 % prompts, 1920 % prompts,
1901 0, 1921 0,
1902 ) 1922 )
1903 == 0 1923 == 0
1904 ): 1924 ):
1905 mresult.actions[f] = ( 1925 mresult.addfile(
1926 f,
1906 mergestatemod.ACTION_GET, 1927 mergestatemod.ACTION_GET,
1907 (flags, False), 1928 (flags, False),
1908 b'prompt recreating', 1929 b'prompt recreating',
1909 ) 1930 )
1910 else: 1931 else: