Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/merge.py @ 37115:43ffd9070da1
merge: use constants for actions
We finish up establishing named constants in this file with
actions.
I remember scratching my head trying to figure out what this
code was doing as part of addressing a recent security issue with
subrepos. Having the named constants in place definitely makes
things easier to read.
I'm not convinced the new constants have the best names (I'm not
an expert in this code). But they can be changed easily enough.
Also, since these constants are internal only, we might want
to change their values to something more human readable to
facilitate debugging. Or maybe we could employ an enum type
some day...
Differential Revision: https://phab.mercurial-scm.org/D2701
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 05 Mar 2018 18:10:36 -0800 |
parents | aa5199c7aa42 |
children | 6f570c501e3e |
comparison
equal
deleted
inserted
replaced
37114:aa5199c7aa42 | 37115:43ffd9070da1 |
---|---|
68 MERGE_RECORD_UNRESOLVED = b'u' | 68 MERGE_RECORD_UNRESOLVED = b'u' |
69 MERGE_RECORD_RESOLVED = b'r' | 69 MERGE_RECORD_RESOLVED = b'r' |
70 MERGE_RECORD_UNRESOLVED_PATH = b'pu' | 70 MERGE_RECORD_UNRESOLVED_PATH = b'pu' |
71 MERGE_RECORD_RESOLVED_PATH = b'pr' | 71 MERGE_RECORD_RESOLVED_PATH = b'pr' |
72 MERGE_RECORD_DRIVER_RESOLVED = b'd' | 72 MERGE_RECORD_DRIVER_RESOLVED = b'd' |
73 | |
74 ACTION_FORGET = b'f' | |
75 ACTION_REMOVE = b'r' | |
76 ACTION_ADD = b'a' | |
77 ACTION_GET = b'g' | |
78 ACTION_PATH_CONFLICT = b'p' | |
79 ACTION_PATH_CONFLICT_RESOLVE = b'pr' | |
80 ACTION_ADD_MODIFIED = b'am' | |
81 ACTION_CREATED = b'c' | |
82 ACTION_DELETED_CHANGED = b'dc' | |
83 ACTION_CHANGED_DELETED = b'cd' | |
84 ACTION_MERGE = b'm' | |
85 ACTION_LOCAL_DIR_RENAME_GET = b'dg' | |
86 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm' | |
87 ACTION_KEEP = b'k' | |
88 ACTION_EXEC = b'e' | |
89 ACTION_CREATED_MERGE = b'cm' | |
73 | 90 |
74 class mergestate(object): | 91 class mergestate(object): |
75 '''track 3-way merge state of individual files | 92 '''track 3-way merge state of individual files |
76 | 93 |
77 The merge state is stored on disk when needed. Two files are used: one with | 94 The merge state is stored on disk when needed. Two files are used: one with |
586 action = None | 603 action = None |
587 if deleted: | 604 if deleted: |
588 if fcd.isabsent(): | 605 if fcd.isabsent(): |
589 # dc: local picked. Need to drop if present, which may | 606 # dc: local picked. Need to drop if present, which may |
590 # happen on re-resolves. | 607 # happen on re-resolves. |
591 action = 'f' | 608 action = ACTION_FORGET |
592 else: | 609 else: |
593 # cd: remote picked (or otherwise deleted) | 610 # cd: remote picked (or otherwise deleted) |
594 action = 'r' | 611 action = ACTION_REMOVE |
595 else: | 612 else: |
596 if fcd.isabsent(): # dc: remote picked | 613 if fcd.isabsent(): # dc: remote picked |
597 action = 'g' | 614 action = ACTION_GET |
598 elif fco.isabsent(): # cd: local picked | 615 elif fco.isabsent(): # cd: local picked |
599 if dfile in self.localctx: | 616 if dfile in self.localctx: |
600 action = 'am' | 617 action = ACTION_ADD_MODIFIED |
601 else: | 618 else: |
602 action = 'a' | 619 action = ACTION_ADD |
603 # else: regular merges (no action necessary) | 620 # else: regular merges (no action necessary) |
604 self._results[dfile] = r, action | 621 self._results[dfile] = r, action |
605 | 622 |
606 return complete, r | 623 return complete, r |
607 | 624 |
629 updated, merged, removed = 0, 0, 0 | 646 updated, merged, removed = 0, 0, 0 |
630 for r, action in self._results.itervalues(): | 647 for r, action in self._results.itervalues(): |
631 if r is None: | 648 if r is None: |
632 updated += 1 | 649 updated += 1 |
633 elif r == 0: | 650 elif r == 0: |
634 if action == 'r': | 651 if action == ACTION_REMOVE: |
635 removed += 1 | 652 removed += 1 |
636 else: | 653 else: |
637 merged += 1 | 654 merged += 1 |
638 return updated, merged, removed | 655 return updated, merged, removed |
639 | 656 |
641 """get unresolved count for this merge (persistent)""" | 658 """get unresolved count for this merge (persistent)""" |
642 return len(list(self.unresolved())) | 659 return len(list(self.unresolved())) |
643 | 660 |
644 def actions(self): | 661 def actions(self): |
645 """return lists of actions to perform on the dirstate""" | 662 """return lists of actions to perform on the dirstate""" |
646 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []} | 663 actions = { |
664 ACTION_REMOVE: [], | |
665 ACTION_FORGET: [], | |
666 ACTION_ADD: [], | |
667 ACTION_ADD_MODIFIED: [], | |
668 ACTION_GET: [], | |
669 } | |
647 for f, (r, action) in self._results.iteritems(): | 670 for f, (r, action) in self._results.iteritems(): |
648 if action is not None: | 671 if action is not None: |
649 actions[action].append((f, None, "merge result")) | 672 actions[action].append((f, None, "merge result")) |
650 return actions | 673 return actions |
651 | 674 |
656 | 679 |
657 def queueremove(self, f): | 680 def queueremove(self, f): |
658 """queues a file to be removed from the dirstate | 681 """queues a file to be removed from the dirstate |
659 | 682 |
660 Meant for use by custom merge drivers.""" | 683 Meant for use by custom merge drivers.""" |
661 self._results[f] = 0, 'r' | 684 self._results[f] = 0, ACTION_REMOVE |
662 | 685 |
663 def queueadd(self, f): | 686 def queueadd(self, f): |
664 """queues a file to be added to the dirstate | 687 """queues a file to be added to the dirstate |
665 | 688 |
666 Meant for use by custom merge drivers.""" | 689 Meant for use by custom merge drivers.""" |
667 self._results[f] = 0, 'a' | 690 self._results[f] = 0, ACTION_ADD |
668 | 691 |
669 def queueget(self, f): | 692 def queueget(self, f): |
670 """queues a file to be marked modified in the dirstate | 693 """queues a file to be marked modified in the dirstate |
671 | 694 |
672 Meant for use by custom merge drivers.""" | 695 Meant for use by custom merge drivers.""" |
673 self._results[f] = 0, 'g' | 696 self._results[f] = 0, ACTION_GET |
674 | 697 |
675 def _getcheckunknownconfig(repo, section, name): | 698 def _getcheckunknownconfig(repo, section, name): |
676 config = repo.ui.config(section, name) | 699 config = repo.ui.config(section, name) |
677 valid = ['abort', 'ignore', 'warn'] | 700 valid = ['abort', 'ignore', 'warn'] |
678 if config not in valid: | 701 if config not in valid: |
770 elif config == 'warn': | 793 elif config == 'warn': |
771 warnconflicts.update(conflicts) | 794 warnconflicts.update(conflicts) |
772 | 795 |
773 checkunknowndirs = _unknowndirschecker() | 796 checkunknowndirs = _unknowndirschecker() |
774 for f, (m, args, msg) in actions.iteritems(): | 797 for f, (m, args, msg) in actions.iteritems(): |
775 if m in ('c', 'dc'): | 798 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED): |
776 if _checkunknownfile(repo, wctx, mctx, f): | 799 if _checkunknownfile(repo, wctx, mctx, f): |
777 fileconflicts.add(f) | 800 fileconflicts.add(f) |
778 elif pathconfig and f not in wctx: | 801 elif pathconfig and f not in wctx: |
779 path = checkunknowndirs(repo, wctx, f) | 802 path = checkunknowndirs(repo, wctx, f) |
780 if path is not None: | 803 if path is not None: |
781 pathconflicts.add(path) | 804 pathconflicts.add(path) |
782 elif m == 'dg': | 805 elif m == ACTION_LOCAL_DIR_RENAME_GET: |
783 if _checkunknownfile(repo, wctx, mctx, f, args[0]): | 806 if _checkunknownfile(repo, wctx, mctx, f, args[0]): |
784 fileconflicts.add(f) | 807 fileconflicts.add(f) |
785 | 808 |
786 allconflicts = fileconflicts | pathconflicts | 809 allconflicts = fileconflicts | pathconflicts |
787 ignoredconflicts = set([c for c in allconflicts | 810 ignoredconflicts = set([c for c in allconflicts |
789 unknownconflicts = allconflicts - ignoredconflicts | 812 unknownconflicts = allconflicts - ignoredconflicts |
790 collectconflicts(ignoredconflicts, ignoredconfig) | 813 collectconflicts(ignoredconflicts, ignoredconfig) |
791 collectconflicts(unknownconflicts, unknownconfig) | 814 collectconflicts(unknownconflicts, unknownconfig) |
792 else: | 815 else: |
793 for f, (m, args, msg) in actions.iteritems(): | 816 for f, (m, args, msg) in actions.iteritems(): |
794 if m == 'cm': | 817 if m == ACTION_CREATED_MERGE: |
795 fl2, anc = args | 818 fl2, anc = args |
796 different = _checkunknownfile(repo, wctx, mctx, f) | 819 different = _checkunknownfile(repo, wctx, mctx, f) |
797 if repo.dirstate._ignore(f): | 820 if repo.dirstate._ignore(f): |
798 config = ignoredconfig | 821 config = ignoredconfig |
799 else: | 822 else: |
810 # (1) this is probably the wrong behavior here -- we should | 833 # (1) this is probably the wrong behavior here -- we should |
811 # probably abort, but some actions like rebases currently | 834 # probably abort, but some actions like rebases currently |
812 # don't like an abort happening in the middle of | 835 # don't like an abort happening in the middle of |
813 # merge.update. | 836 # merge.update. |
814 if not different: | 837 if not different: |
815 actions[f] = ('g', (fl2, False), "remote created") | 838 actions[f] = (ACTION_GET, (fl2, False), 'remote created') |
816 elif mergeforce or config == 'abort': | 839 elif mergeforce or config == 'abort': |
817 actions[f] = ('m', (f, f, None, False, anc), | 840 actions[f] = (ACTION_MERGE, (f, f, None, False, anc), |
818 "remote differs from untracked local") | 841 'remote differs from untracked local') |
819 elif config == 'abort': | 842 elif config == 'abort': |
820 abortconflicts.add(f) | 843 abortconflicts.add(f) |
821 else: | 844 else: |
822 if config == 'warn': | 845 if config == 'warn': |
823 warnconflicts.add(f) | 846 warnconflicts.add(f) |
824 actions[f] = ('g', (fl2, True), "remote created") | 847 actions[f] = (ACTION_GET, (fl2, True), 'remote created') |
825 | 848 |
826 for f in sorted(abortconflicts): | 849 for f in sorted(abortconflicts): |
827 warn = repo.ui.warn | 850 warn = repo.ui.warn |
828 if f in pathconflicts: | 851 if f in pathconflicts: |
829 if repo.wvfs.isfileorlink(f): | 852 if repo.wvfs.isfileorlink(f): |
841 repo.ui.warn(_("%s: replacing untracked file\n") % f) | 864 repo.ui.warn(_("%s: replacing untracked file\n") % f) |
842 else: | 865 else: |
843 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f) | 866 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f) |
844 | 867 |
845 for f, (m, args, msg) in actions.iteritems(): | 868 for f, (m, args, msg) in actions.iteritems(): |
846 if m == 'c': | 869 if m == ACTION_CREATED: |
847 backup = (f in fileconflicts or f in pathconflicts or | 870 backup = (f in fileconflicts or f in pathconflicts or |
848 any(p in pathconflicts for p in util.finddirs(f))) | 871 any(p in pathconflicts for p in util.finddirs(f))) |
849 flags, = args | 872 flags, = args |
850 actions[f] = ('g', (flags, backup), msg) | 873 actions[f] = (ACTION_GET, (flags, backup), msg) |
851 | 874 |
852 def _forgetremoved(wctx, mctx, branchmerge): | 875 def _forgetremoved(wctx, mctx, branchmerge): |
853 """ | 876 """ |
854 Forget removed files | 877 Forget removed files |
855 | 878 |
863 that is not present in the working directory, we need to mark it | 886 that is not present in the working directory, we need to mark it |
864 as removed. | 887 as removed. |
865 """ | 888 """ |
866 | 889 |
867 actions = {} | 890 actions = {} |
868 m = 'f' | 891 m = ACTION_FORGET |
869 if branchmerge: | 892 if branchmerge: |
870 m = 'r' | 893 m = ACTION_REMOVE |
871 for f in wctx.deleted(): | 894 for f in wctx.deleted(): |
872 if f not in mctx: | 895 if f not in mctx: |
873 actions[f] = m, None, "forget deleted" | 896 actions[f] = m, None, "forget deleted" |
874 | 897 |
875 if not branchmerge: | 898 if not branchmerge: |
876 for f in wctx.removed(): | 899 for f in wctx.removed(): |
877 if f not in mctx: | 900 if f not in mctx: |
878 actions[f] = 'f', None, "forget removed" | 901 actions[f] = ACTION_FORGET, None, "forget removed" |
879 | 902 |
880 return actions | 903 return actions |
881 | 904 |
882 def _checkcollision(repo, wmf, actions): | 905 def _checkcollision(repo, wmf, actions): |
883 # build provisional merged manifest up | 906 # build provisional merged manifest up |
884 pmmf = set(wmf) | 907 pmmf = set(wmf) |
885 | 908 |
886 if actions: | 909 if actions: |
887 # k, dr, e and rd are no-op | 910 # KEEP and EXEC are no-op |
888 for m in 'a', 'am', 'f', 'g', 'cd', 'dc': | 911 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET, |
912 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED): | |
889 for f, args, msg in actions[m]: | 913 for f, args, msg in actions[m]: |
890 pmmf.add(f) | 914 pmmf.add(f) |
891 for f, args, msg in actions['r']: | 915 for f, args, msg in actions[ACTION_REMOVE]: |
892 pmmf.discard(f) | 916 pmmf.discard(f) |
893 for f, args, msg in actions['dm']: | 917 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: |
894 f2, flags = args | 918 f2, flags = args |
895 pmmf.discard(f2) | 919 pmmf.discard(f2) |
896 pmmf.add(f) | 920 pmmf.add(f) |
897 for f, args, msg in actions['dg']: | 921 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: |
898 pmmf.add(f) | 922 pmmf.add(f) |
899 for f, args, msg in actions['m']: | 923 for f, args, msg in actions[ACTION_MERGE]: |
900 f1, f2, fa, move, anc = args | 924 f1, f2, fa, move, anc = args |
901 if move: | 925 if move: |
902 pmmf.discard(f1) | 926 pmmf.discard(f1) |
903 pmmf.add(f) | 927 pmmf.add(f) |
904 | 928 |
970 | 994 |
971 # The set of files deleted by all the actions. | 995 # The set of files deleted by all the actions. |
972 deletedfiles = set() | 996 deletedfiles = set() |
973 | 997 |
974 for f, (m, args, msg) in actions.items(): | 998 for f, (m, args, msg) in actions.items(): |
975 if m in ('c', 'dc', 'm', 'cm'): | 999 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE, |
1000 ACTION_CREATED_MERGE): | |
976 # This action may create a new local file. | 1001 # This action may create a new local file. |
977 createdfiledirs.update(util.finddirs(f)) | 1002 createdfiledirs.update(util.finddirs(f)) |
978 if mf.hasdir(f): | 1003 if mf.hasdir(f): |
979 # The file aliases a local directory. This might be ok if all | 1004 # The file aliases a local directory. This might be ok if all |
980 # the files in the local directory are being deleted. This | 1005 # the files in the local directory are being deleted. This |
981 # will be checked once we know what all the deleted files are. | 1006 # will be checked once we know what all the deleted files are. |
982 remoteconflicts.add(f) | 1007 remoteconflicts.add(f) |
983 # Track the names of all deleted files. | 1008 # Track the names of all deleted files. |
984 if m == 'r': | 1009 if m == ACTION_REMOVE: |
985 deletedfiles.add(f) | 1010 deletedfiles.add(f) |
986 if m == 'm': | 1011 if m == ACTION_MERGE: |
987 f1, f2, fa, move, anc = args | 1012 f1, f2, fa, move, anc = args |
988 if move: | 1013 if move: |
989 deletedfiles.add(f1) | 1014 deletedfiles.add(f1) |
990 if m == 'dm': | 1015 if m == ACTION_DIR_RENAME_MOVE_LOCAL: |
991 f2, flags = args | 1016 f2, flags = args |
992 deletedfiles.add(f2) | 1017 deletedfiles.add(f2) |
993 | 1018 |
994 # Check all directories that contain created files for path conflicts. | 1019 # Check all directories that contain created files for path conflicts. |
995 for p in createdfiledirs: | 1020 for p in createdfiledirs: |
1001 invalidconflicts.add(p) | 1026 invalidconflicts.add(p) |
1002 else: | 1027 else: |
1003 # A file is in a directory which aliases a local file. | 1028 # A file is in a directory which aliases a local file. |
1004 # We will need to rename the local file. | 1029 # We will need to rename the local file. |
1005 localconflicts.add(p) | 1030 localconflicts.add(p) |
1006 if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'): | 1031 if p in actions and actions[p][0] in (ACTION_CREATED, |
1032 ACTION_DELETED_CHANGED, | |
1033 ACTION_MERGE, | |
1034 ACTION_CREATED_MERGE): | |
1007 # The file is in a directory which aliases a remote file. | 1035 # The file is in a directory which aliases a remote file. |
1008 # This is an internal inconsistency within the remote | 1036 # This is an internal inconsistency within the remote |
1009 # manifest. | 1037 # manifest. |
1010 invalidconflicts.add(p) | 1038 invalidconflicts.add(p) |
1011 | 1039 |
1012 # Rename all local conflicting files that have not been deleted. | 1040 # Rename all local conflicting files that have not been deleted. |
1013 for p in localconflicts: | 1041 for p in localconflicts: |
1014 if p not in deletedfiles: | 1042 if p not in deletedfiles: |
1015 ctxname = bytes(wctx).rstrip('+') | 1043 ctxname = bytes(wctx).rstrip('+') |
1016 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) | 1044 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1017 actions[pnew] = ('pr', (p,), "local path conflict") | 1045 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,), |
1018 actions[p] = ('p', (pnew, 'l'), "path conflict") | 1046 'local path conflict') |
1047 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'), | |
1048 'path conflict') | |
1019 | 1049 |
1020 if remoteconflicts: | 1050 if remoteconflicts: |
1021 # Check if all files in the conflicting directories have been removed. | 1051 # Check if all files in the conflicting directories have been removed. |
1022 ctxname = bytes(mctx).rstrip('+') | 1052 ctxname = bytes(mctx).rstrip('+') |
1023 for f, p in _filesindirs(repo, mf, remoteconflicts): | 1053 for f, p in _filesindirs(repo, mf, remoteconflicts): |
1024 if f not in deletedfiles: | 1054 if f not in deletedfiles: |
1025 m, args, msg = actions[p] | 1055 m, args, msg = actions[p] |
1026 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) | 1056 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1027 if m in ('dc', 'm'): | 1057 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE): |
1028 # Action was merge, just update target. | 1058 # Action was merge, just update target. |
1029 actions[pnew] = (m, args, msg) | 1059 actions[pnew] = (m, args, msg) |
1030 else: | 1060 else: |
1031 # Action was create, change to renamed get action. | 1061 # Action was create, change to renamed get action. |
1032 fl = args[0] | 1062 fl = args[0] |
1033 actions[pnew] = ('dg', (p, fl), "remote path conflict") | 1063 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl), |
1034 actions[p] = ('p', (pnew, 'r'), "path conflict") | 1064 'remote path conflict') |
1065 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE), | |
1066 'path conflict') | |
1035 remoteconflicts.remove(p) | 1067 remoteconflicts.remove(p) |
1036 break | 1068 break |
1037 | 1069 |
1038 if invalidconflicts: | 1070 if invalidconflicts: |
1039 for p in invalidconflicts: | 1071 for p in invalidconflicts: |
1107 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): | 1139 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): |
1108 if n1 and n2: # file exists on both local and remote side | 1140 if n1 and n2: # file exists on both local and remote side |
1109 if f not in ma: | 1141 if f not in ma: |
1110 fa = copy.get(f, None) | 1142 fa = copy.get(f, None) |
1111 if fa is not None: | 1143 if fa is not None: |
1112 actions[f] = ('m', (f, f, fa, False, pa.node()), | 1144 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()), |
1113 "both renamed from " + fa) | 1145 'both renamed from %s' % fa) |
1114 else: | 1146 else: |
1115 actions[f] = ('m', (f, f, None, False, pa.node()), | 1147 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()), |
1116 "both created") | 1148 'both created') |
1117 else: | 1149 else: |
1118 a = ma[f] | 1150 a = ma[f] |
1119 fla = ma.flags(f) | 1151 fla = ma.flags(f) |
1120 nol = 'l' not in fl1 + fl2 + fla | 1152 nol = 'l' not in fl1 + fl2 + fla |
1121 if n2 == a and fl2 == fla: | 1153 if n2 == a and fl2 == fla: |
1122 actions[f] = ('k', (), "remote unchanged") | 1154 actions[f] = (ACTION_KEEP, (), 'remote unchanged') |
1123 elif n1 == a and fl1 == fla: # local unchanged - use remote | 1155 elif n1 == a and fl1 == fla: # local unchanged - use remote |
1124 if n1 == n2: # optimization: keep local content | 1156 if n1 == n2: # optimization: keep local content |
1125 actions[f] = ('e', (fl2,), "update permissions") | 1157 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions') |
1126 else: | 1158 else: |
1127 actions[f] = ('g', (fl2, False), "remote is newer") | 1159 actions[f] = (ACTION_GET, (fl2, False), |
1160 'remote is newer') | |
1128 elif nol and n2 == a: # remote only changed 'x' | 1161 elif nol and n2 == a: # remote only changed 'x' |
1129 actions[f] = ('e', (fl2,), "update permissions") | 1162 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions') |
1130 elif nol and n1 == a: # local only changed 'x' | 1163 elif nol and n1 == a: # local only changed 'x' |
1131 actions[f] = ('g', (fl1, False), "remote is newer") | 1164 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer') |
1132 else: # both changed something | 1165 else: # both changed something |
1133 actions[f] = ('m', (f, f, f, False, pa.node()), | 1166 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()), |
1134 "versions differ") | 1167 'versions differ') |
1135 elif n1: # file exists only on local side | 1168 elif n1: # file exists only on local side |
1136 if f in copied: | 1169 if f in copied: |
1137 pass # we'll deal with it on m2 side | 1170 pass # we'll deal with it on m2 side |
1138 elif f in movewithdir: # directory rename, move local | 1171 elif f in movewithdir: # directory rename, move local |
1139 f2 = movewithdir[f] | 1172 f2 = movewithdir[f] |
1140 if f2 in m2: | 1173 if f2 in m2: |
1141 actions[f2] = ('m', (f, f2, None, True, pa.node()), | 1174 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()), |
1142 "remote directory rename, both created") | 1175 'remote directory rename, both created') |
1143 else: | 1176 else: |
1144 actions[f2] = ('dm', (f, fl1), | 1177 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1), |
1145 "remote directory rename - move from " + f) | 1178 'remote directory rename - move from %s' % f) |
1146 elif f in copy: | 1179 elif f in copy: |
1147 f2 = copy[f] | 1180 f2 = copy[f] |
1148 actions[f] = ('m', (f, f2, f2, False, pa.node()), | 1181 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()), |
1149 "local copied/moved from " + f2) | 1182 'local copied/moved from %s' % f2) |
1150 elif f in ma: # clean, a different, no remote | 1183 elif f in ma: # clean, a different, no remote |
1151 if n1 != ma[f]: | 1184 if n1 != ma[f]: |
1152 if acceptremote: | 1185 if acceptremote: |
1153 actions[f] = ('r', None, "remote delete") | 1186 actions[f] = (ACTION_REMOVE, None, 'remote delete') |
1154 else: | 1187 else: |
1155 actions[f] = ('cd', (f, None, f, False, pa.node()), | 1188 actions[f] = (ACTION_CHANGED_DELETED, |
1156 "prompt changed/deleted") | 1189 (f, None, f, False, pa.node()), |
1190 'prompt changed/deleted') | |
1157 elif n1 == addednodeid: | 1191 elif n1 == addednodeid: |
1158 # This extra 'a' is added by working copy manifest to mark | 1192 # This extra 'a' is added by working copy manifest to mark |
1159 # the file as locally added. We should forget it instead of | 1193 # the file as locally added. We should forget it instead of |
1160 # deleting it. | 1194 # deleting it. |
1161 actions[f] = ('f', None, "remote deleted") | 1195 actions[f] = (ACTION_FORGET, None, 'remote deleted') |
1162 else: | 1196 else: |
1163 actions[f] = ('r', None, "other deleted") | 1197 actions[f] = (ACTION_REMOVE, None, 'other deleted') |
1164 elif n2: # file exists only on remote side | 1198 elif n2: # file exists only on remote side |
1165 if f in copied: | 1199 if f in copied: |
1166 pass # we'll deal with it on m1 side | 1200 pass # we'll deal with it on m1 side |
1167 elif f in movewithdir: | 1201 elif f in movewithdir: |
1168 f2 = movewithdir[f] | 1202 f2 = movewithdir[f] |
1169 if f2 in m1: | 1203 if f2 in m1: |
1170 actions[f2] = ('m', (f2, f, None, False, pa.node()), | 1204 actions[f2] = (ACTION_MERGE, |
1171 "local directory rename, both created") | 1205 (f2, f, None, False, pa.node()), |
1206 'local directory rename, both created') | |
1172 else: | 1207 else: |
1173 actions[f2] = ('dg', (f, fl2), | 1208 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2), |
1174 "local directory rename - get from " + f) | 1209 'local directory rename - get from %s' % f) |
1175 elif f in copy: | 1210 elif f in copy: |
1176 f2 = copy[f] | 1211 f2 = copy[f] |
1177 if f2 in m2: | 1212 if f2 in m2: |
1178 actions[f] = ('m', (f2, f, f2, False, pa.node()), | 1213 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()), |
1179 "remote copied from " + f2) | 1214 'remote copied from %s' % f2) |
1180 else: | 1215 else: |
1181 actions[f] = ('m', (f2, f, f2, True, pa.node()), | 1216 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()), |
1182 "remote moved from " + f2) | 1217 'remote moved from %s' % f2) |
1183 elif f not in ma: | 1218 elif f not in ma: |
1184 # local unknown, remote created: the logic is described by the | 1219 # local unknown, remote created: the logic is described by the |
1185 # following table: | 1220 # following table: |
1186 # | 1221 # |
1187 # force branchmerge different | action | 1222 # force branchmerge different | action |
1191 # y y y | merge | 1226 # y y y | merge |
1192 # | 1227 # |
1193 # Checking whether the files are different is expensive, so we | 1228 # Checking whether the files are different is expensive, so we |
1194 # don't do that when we can avoid it. | 1229 # don't do that when we can avoid it. |
1195 if not force: | 1230 if not force: |
1196 actions[f] = ('c', (fl2,), "remote created") | 1231 actions[f] = (ACTION_CREATED, (fl2,), 'remote created') |
1197 elif not branchmerge: | 1232 elif not branchmerge: |
1198 actions[f] = ('c', (fl2,), "remote created") | 1233 actions[f] = (ACTION_CREATED, (fl2,), 'remote created') |
1199 else: | 1234 else: |
1200 actions[f] = ('cm', (fl2, pa.node()), | 1235 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()), |
1201 "remote created, get or merge") | 1236 'remote created, get or merge') |
1202 elif n2 != ma[f]: | 1237 elif n2 != ma[f]: |
1203 df = None | 1238 df = None |
1204 for d in dirmove: | 1239 for d in dirmove: |
1205 if f.startswith(d): | 1240 if f.startswith(d): |
1206 # new file added in a directory that was moved | 1241 # new file added in a directory that was moved |
1207 df = dirmove[d] + f[len(d):] | 1242 df = dirmove[d] + f[len(d):] |
1208 break | 1243 break |
1209 if df is not None and df in m1: | 1244 if df is not None and df in m1: |
1210 actions[df] = ('m', (df, f, f, False, pa.node()), | 1245 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()), |
1211 "local directory rename - respect move from " + f) | 1246 'local directory rename - respect move ' |
1247 'from %s' % f) | |
1212 elif acceptremote: | 1248 elif acceptremote: |
1213 actions[f] = ('c', (fl2,), "remote recreating") | 1249 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating') |
1214 else: | 1250 else: |
1215 actions[f] = ('dc', (None, f, f, False, pa.node()), | 1251 actions[f] = (ACTION_DELETED_CHANGED, |
1216 "prompt deleted/changed") | 1252 (None, f, f, False, pa.node()), |
1253 'prompt deleted/changed') | |
1217 | 1254 |
1218 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'): | 1255 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'): |
1219 # If we are merging, look for path conflicts. | 1256 # If we are merging, look for path conflicts. |
1220 checkpathconflicts(repo, wctx, p2, actions) | 1257 checkpathconflicts(repo, wctx, p2, actions) |
1221 | 1258 |
1225 """Resolves false conflicts where the nodeid changed but the content | 1262 """Resolves false conflicts where the nodeid changed but the content |
1226 remained the same.""" | 1263 remained the same.""" |
1227 # We force a copy of actions.items() because we're going to mutate | 1264 # We force a copy of actions.items() because we're going to mutate |
1228 # actions as we resolve trivial conflicts. | 1265 # actions as we resolve trivial conflicts. |
1229 for f, (m, args, msg) in list(actions.items()): | 1266 for f, (m, args, msg) in list(actions.items()): |
1230 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]): | 1267 if (m == ACTION_CHANGED_DELETED and f in ancestor |
1268 and not wctx[f].cmp(ancestor[f])): | |
1231 # local did change but ended up with same content | 1269 # local did change but ended up with same content |
1232 actions[f] = 'r', None, "prompt same" | 1270 actions[f] = ACTION_REMOVE, None, 'prompt same' |
1233 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]): | 1271 elif (m == ACTION_DELETED_CHANGED and f in ancestor |
1272 and not mctx[f].cmp(ancestor[f])): | |
1234 # remote did change but ended up with same content | 1273 # remote did change but ended up with same content |
1235 del actions[f] # don't get = keep local deleted | 1274 del actions[f] # don't get = keep local deleted |
1236 | 1275 |
1237 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, | 1276 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, |
1238 acceptremote, followcopies, matcher=None, | 1277 acceptremote, followcopies, matcher=None, |
1292 if len(bids) == 1: # all bids are the same kind of method | 1331 if len(bids) == 1: # all bids are the same kind of method |
1293 m, l = list(bids.items())[0] | 1332 m, l = list(bids.items())[0] |
1294 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 | 1333 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 |
1295 repo.ui.note(_(" %s: consensus for %s\n") % (f, m)) | 1334 repo.ui.note(_(" %s: consensus for %s\n") % (f, m)) |
1296 actions[f] = l[0] | 1335 actions[f] = l[0] |
1297 if m == 'dm': | 1336 if m == ACTION_DIR_RENAME_MOVE_LOCAL: |
1298 dms.append(f) | 1337 dms.append(f) |
1299 continue | 1338 continue |
1300 # If keep is an option, just do it. | 1339 # If keep is an option, just do it. |
1301 if 'k' in bids: | 1340 if ACTION_KEEP in bids: |
1302 repo.ui.note(_(" %s: picking 'keep' action\n") % f) | 1341 repo.ui.note(_(" %s: picking 'keep' action\n") % f) |
1303 actions[f] = bids['k'][0] | 1342 actions[f] = bids[ACTION_KEEP][0] |
1304 continue | 1343 continue |
1305 # If there are gets and they all agree [how could they not?], do it. | 1344 # If there are gets and they all agree [how could they not?], do it. |
1306 if 'g' in bids: | 1345 if ACTION_GET in bids: |
1307 ga0 = bids['g'][0] | 1346 ga0 = bids[ACTION_GET][0] |
1308 if all(a == ga0 for a in bids['g'][1:]): | 1347 if all(a == ga0 for a in bids[ACTION_GET][1:]): |
1309 repo.ui.note(_(" %s: picking 'get' action\n") % f) | 1348 repo.ui.note(_(" %s: picking 'get' action\n") % f) |
1310 actions[f] = ga0 | 1349 actions[f] = ga0 |
1311 continue | 1350 continue |
1312 # TODO: Consider other simple actions such as mode changes | 1351 # TODO: Consider other simple actions such as mode changes |
1313 # Handle inefficient democrazy. | 1352 # Handle inefficient democrazy. |
1318 # Pick random action. TODO: Instead, prompt user when resolving | 1357 # Pick random action. TODO: Instead, prompt user when resolving |
1319 m, l = list(bids.items())[0] | 1358 m, l = list(bids.items())[0] |
1320 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') % | 1359 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') % |
1321 (f, m)) | 1360 (f, m)) |
1322 actions[f] = l[0] | 1361 actions[f] = l[0] |
1323 if m == 'dm': | 1362 if m == ACTION_DIR_RENAME_MOVE_LOCAL: |
1324 dms.append(f) | 1363 dms.append(f) |
1325 continue | 1364 continue |
1326 # Work around 'dm' that can cause multiple actions for the same file | 1365 # Work around 'dm' that can cause multiple actions for the same file |
1327 for f in dms: | 1366 for f in dms: |
1328 dm, (f0, flags), msg = actions[f] | 1367 dm, (f0, flags), msg = actions[f] |
1329 assert dm == 'dm', dm | 1368 assert dm == ACTION_DIR_RENAME_MOVE_LOCAL, dm |
1330 if f0 in actions and actions[f0][0] == 'r': | 1369 if f0 in actions and actions[f0][0] == ACTION_REMOVE: |
1331 # We have one bid for removing a file and another for moving it. | 1370 # We have one bid for removing a file and another for moving it. |
1332 # These two could be merged as first move and then delete ... | 1371 # These two could be merged as first move and then delete ... |
1333 # but instead drop moving and just delete. | 1372 # but instead drop moving and just delete. |
1334 del actions[f] | 1373 del actions[f] |
1335 repo.ui.note(_('end of auction\n\n')) | 1374 repo.ui.note(_('end of auction\n\n')) |
1430 of merge actions. ``ctx`` is the context being merged in.""" | 1469 of merge actions. ``ctx`` is the context being merged in.""" |
1431 | 1470 |
1432 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they | 1471 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they |
1433 # don't touch the context to be merged in. 'cd' is skipped, because | 1472 # don't touch the context to be merged in. 'cd' is skipped, because |
1434 # changed/deleted never resolves to something from the remote side. | 1473 # changed/deleted never resolves to something from the remote side. |
1435 oplist = [actions[a] for a in 'g dc dg m'.split()] | 1474 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED, |
1475 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)] | |
1436 prefetch = scmutil.fileprefetchhooks | 1476 prefetch = scmutil.fileprefetchhooks |
1437 prefetch(repo, ctx, [f for sublist in oplist for f, args, msg in sublist]) | 1477 prefetch(repo, ctx, [f for sublist in oplist for f, args, msg in sublist]) |
1438 | 1478 |
1439 @attr.s(frozen=True) | 1479 @attr.s(frozen=True) |
1440 class updateresult(object): | 1480 class updateresult(object): |
1477 moves = [] | 1517 moves = [] |
1478 for m, l in actions.items(): | 1518 for m, l in actions.items(): |
1479 l.sort() | 1519 l.sort() |
1480 | 1520 |
1481 # 'cd' and 'dc' actions are treated like other merge conflicts | 1521 # 'cd' and 'dc' actions are treated like other merge conflicts |
1482 mergeactions = sorted(actions['cd']) | 1522 mergeactions = sorted(actions[ACTION_CHANGED_DELETED]) |
1483 mergeactions.extend(sorted(actions['dc'])) | 1523 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED])) |
1484 mergeactions.extend(actions['m']) | 1524 mergeactions.extend(actions[ACTION_MERGE]) |
1485 for f, args, msg in mergeactions: | 1525 for f, args, msg in mergeactions: |
1486 f1, f2, fa, move, anc = args | 1526 f1, f2, fa, move, anc = args |
1487 if f == '.hgsubstate': # merged internally | 1527 if f == '.hgsubstate': # merged internally |
1488 continue | 1528 continue |
1489 if f1 is None: | 1529 if f1 is None: |
1514 if wctx[f].lexists(): | 1554 if wctx[f].lexists(): |
1515 repo.ui.debug("removing %s\n" % f) | 1555 repo.ui.debug("removing %s\n" % f) |
1516 wctx[f].audit() | 1556 wctx[f].audit() |
1517 wctx[f].remove() | 1557 wctx[f].remove() |
1518 | 1558 |
1519 numupdates = sum(len(l) for m, l in actions.items() if m != 'k') | 1559 numupdates = sum(len(l) for m, l in actions.items() |
1560 if m != ACTION_KEEP) | |
1520 z = 0 | 1561 z = 0 |
1521 | 1562 |
1522 if [a for a in actions['r'] if a[0] == '.hgsubstate']: | 1563 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']: |
1523 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) | 1564 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1524 | 1565 |
1525 # record path conflicts | 1566 # record path conflicts |
1526 for f, args, msg in actions['p']: | 1567 for f, args, msg in actions[ACTION_PATH_CONFLICT]: |
1527 f1, fo = args | 1568 f1, fo = args |
1528 s = repo.ui.status | 1569 s = repo.ui.status |
1529 s(_("%s: path conflict - a file or link has the same name as a " | 1570 s(_("%s: path conflict - a file or link has the same name as a " |
1530 "directory\n") % f) | 1571 "directory\n") % f) |
1531 if fo == 'l': | 1572 if fo == 'l': |
1541 # per-item cost at 0 in that case. | 1582 # per-item cost at 0 in that case. |
1542 cost = 0 if wctx.isinmemory() else 0.001 | 1583 cost = 0 if wctx.isinmemory() else 0.001 |
1543 | 1584 |
1544 # remove in parallel (must come before resolving path conflicts and getting) | 1585 # remove in parallel (must come before resolving path conflicts and getting) |
1545 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx), | 1586 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx), |
1546 actions['r']) | 1587 actions[ACTION_REMOVE]) |
1547 for i, item in prog: | 1588 for i, item in prog: |
1548 z += i | 1589 z += i |
1549 progress(_updating, z, item=item, total=numupdates, unit=_files) | 1590 progress(_updating, z, item=item, total=numupdates, unit=_files) |
1550 removed = len(actions['r']) | 1591 removed = len(actions[ACTION_REMOVE]) |
1551 | 1592 |
1552 # resolve path conflicts (must come before getting) | 1593 # resolve path conflicts (must come before getting) |
1553 for f, args, msg in actions['pr']: | 1594 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]: |
1554 repo.ui.debug(" %s: %s -> pr\n" % (f, msg)) | 1595 repo.ui.debug(" %s: %s -> pr\n" % (f, msg)) |
1555 f0, = args | 1596 f0, = args |
1556 if wctx[f0].lexists(): | 1597 if wctx[f0].lexists(): |
1557 repo.ui.note(_("moving %s to %s\n") % (f0, f)) | 1598 repo.ui.note(_("moving %s to %s\n") % (f0, f)) |
1558 wctx[f].audit() | 1599 wctx[f].audit() |
1561 z += 1 | 1602 z += 1 |
1562 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1603 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1563 | 1604 |
1564 # get in parallel | 1605 # get in parallel |
1565 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx), | 1606 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx), |
1566 actions['g']) | 1607 actions[ACTION_GET]) |
1567 for i, item in prog: | 1608 for i, item in prog: |
1568 z += i | 1609 z += i |
1569 progress(_updating, z, item=item, total=numupdates, unit=_files) | 1610 progress(_updating, z, item=item, total=numupdates, unit=_files) |
1570 updated = len(actions['g']) | 1611 updated = len(actions[ACTION_GET]) |
1571 | 1612 |
1572 if [a for a in actions['g'] if a[0] == '.hgsubstate']: | 1613 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']: |
1573 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) | 1614 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1574 | 1615 |
1575 # forget (manifest only, just log it) (must come first) | 1616 # forget (manifest only, just log it) (must come first) |
1576 for f, args, msg in actions['f']: | 1617 for f, args, msg in actions[ACTION_FORGET]: |
1577 repo.ui.debug(" %s: %s -> f\n" % (f, msg)) | 1618 repo.ui.debug(" %s: %s -> f\n" % (f, msg)) |
1578 z += 1 | 1619 z += 1 |
1579 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1620 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1580 | 1621 |
1581 # re-add (manifest only, just log it) | 1622 # re-add (manifest only, just log it) |
1582 for f, args, msg in actions['a']: | 1623 for f, args, msg in actions[ACTION_ADD]: |
1583 repo.ui.debug(" %s: %s -> a\n" % (f, msg)) | 1624 repo.ui.debug(" %s: %s -> a\n" % (f, msg)) |
1584 z += 1 | 1625 z += 1 |
1585 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1626 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1586 | 1627 |
1587 # re-add/mark as modified (manifest only, just log it) | 1628 # re-add/mark as modified (manifest only, just log it) |
1588 for f, args, msg in actions['am']: | 1629 for f, args, msg in actions[ACTION_ADD_MODIFIED]: |
1589 repo.ui.debug(" %s: %s -> am\n" % (f, msg)) | 1630 repo.ui.debug(" %s: %s -> am\n" % (f, msg)) |
1590 z += 1 | 1631 z += 1 |
1591 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1632 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1592 | 1633 |
1593 # keep (noop, just log it) | 1634 # keep (noop, just log it) |
1594 for f, args, msg in actions['k']: | 1635 for f, args, msg in actions[ACTION_KEEP]: |
1595 repo.ui.debug(" %s: %s -> k\n" % (f, msg)) | 1636 repo.ui.debug(" %s: %s -> k\n" % (f, msg)) |
1596 # no progress | 1637 # no progress |
1597 | 1638 |
1598 # directory rename, move local | 1639 # directory rename, move local |
1599 for f, args, msg in actions['dm']: | 1640 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: |
1600 repo.ui.debug(" %s: %s -> dm\n" % (f, msg)) | 1641 repo.ui.debug(" %s: %s -> dm\n" % (f, msg)) |
1601 z += 1 | 1642 z += 1 |
1602 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1643 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1603 f0, flags = args | 1644 f0, flags = args |
1604 repo.ui.note(_("moving %s to %s\n") % (f0, f)) | 1645 repo.ui.note(_("moving %s to %s\n") % (f0, f)) |
1606 wctx[f].write(wctx.filectx(f0).data(), flags) | 1647 wctx[f].write(wctx.filectx(f0).data(), flags) |
1607 wctx[f0].remove() | 1648 wctx[f0].remove() |
1608 updated += 1 | 1649 updated += 1 |
1609 | 1650 |
1610 # local directory rename, get | 1651 # local directory rename, get |
1611 for f, args, msg in actions['dg']: | 1652 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: |
1612 repo.ui.debug(" %s: %s -> dg\n" % (f, msg)) | 1653 repo.ui.debug(" %s: %s -> dg\n" % (f, msg)) |
1613 z += 1 | 1654 z += 1 |
1614 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1655 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1615 f0, flags = args | 1656 f0, flags = args |
1616 repo.ui.note(_("getting %s to %s\n") % (f0, f)) | 1657 repo.ui.note(_("getting %s to %s\n") % (f0, f)) |
1617 wctx[f].write(mctx.filectx(f0).data(), flags) | 1658 wctx[f].write(mctx.filectx(f0).data(), flags) |
1618 updated += 1 | 1659 updated += 1 |
1619 | 1660 |
1620 # exec | 1661 # exec |
1621 for f, args, msg in actions['e']: | 1662 for f, args, msg in actions[ACTION_EXEC]: |
1622 repo.ui.debug(" %s: %s -> e\n" % (f, msg)) | 1663 repo.ui.debug(" %s: %s -> e\n" % (f, msg)) |
1623 z += 1 | 1664 z += 1 |
1624 progress(_updating, z, item=f, total=numupdates, unit=_files) | 1665 progress(_updating, z, item=f, total=numupdates, unit=_files) |
1625 flags, = args | 1666 flags, = args |
1626 wctx[f].audit() | 1667 wctx[f].audit() |
1694 merged += msmerged | 1735 merged += msmerged |
1695 removed += msremoved | 1736 removed += msremoved |
1696 | 1737 |
1697 extraactions = ms.actions() | 1738 extraactions = ms.actions() |
1698 if extraactions: | 1739 if extraactions: |
1699 mfiles = set(a[0] for a in actions['m']) | 1740 mfiles = set(a[0] for a in actions[ACTION_MERGE]) |
1700 for k, acts in extraactions.iteritems(): | 1741 for k, acts in extraactions.iteritems(): |
1701 actions[k].extend(acts) | 1742 actions[k].extend(acts) |
1702 # Remove these files from actions['m'] as well. This is important | 1743 # Remove these files from actions[ACTION_MERGE] as well. This is |
1703 # because in recordupdates, files in actions['m'] are processed | 1744 # important because in recordupdates, files in actions[ACTION_MERGE] |
1704 # after files in other actions, and the merge driver might add | 1745 # are processed after files in other actions, and the merge driver |
1705 # files to those actions via extraactions above. This can lead to a | 1746 # might add files to those actions via extraactions above. This can |
1706 # file being recorded twice, with poor results. This is especially | 1747 # lead to a file being recorded twice, with poor results. This is |
1707 # problematic for actions['r'] (currently only possible with the | 1748 # especially problematic for actions[ACTION_REMOVE] (currently only |
1708 # merge driver in the initial merge process; interrupted merges | 1749 # possible with the merge driver in the initial merge process; |
1709 # don't go through this flow). | 1750 # interrupted merges don't go through this flow). |
1710 # | 1751 # |
1711 # The real fix here is to have indexes by both file and action so | 1752 # The real fix here is to have indexes by both file and action so |
1712 # that when the action for a file is changed it is automatically | 1753 # that when the action for a file is changed it is automatically |
1713 # reflected in the other action lists. But that involves a more | 1754 # reflected in the other action lists. But that involves a more |
1714 # complex data structure, so this will do for now. | 1755 # complex data structure, so this will do for now. |
1715 # | 1756 # |
1716 # We don't need to do the same operation for 'dc' and 'cd' because | 1757 # We don't need to do the same operation for 'dc' and 'cd' because |
1717 # those lists aren't consulted again. | 1758 # those lists aren't consulted again. |
1718 mfiles.difference_update(a[0] for a in acts) | 1759 mfiles.difference_update(a[0] for a in acts) |
1719 | 1760 |
1720 actions['m'] = [a for a in actions['m'] if a[0] in mfiles] | 1761 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE] |
1762 if a[0] in mfiles] | |
1721 | 1763 |
1722 progress(_updating, None, total=numupdates, unit=_files) | 1764 progress(_updating, None, total=numupdates, unit=_files) |
1723 return updateresult(updated, merged, removed, unresolved) | 1765 return updateresult(updated, merged, removed, unresolved) |
1724 | 1766 |
1725 def recordupdates(repo, actions, branchmerge): | 1767 def recordupdates(repo, actions, branchmerge): |
1726 "record merge actions to the dirstate" | 1768 "record merge actions to the dirstate" |
1727 # remove (must come first) | 1769 # remove (must come first) |
1728 for f, args, msg in actions.get('r', []): | 1770 for f, args, msg in actions.get(ACTION_REMOVE, []): |
1729 if branchmerge: | 1771 if branchmerge: |
1730 repo.dirstate.remove(f) | 1772 repo.dirstate.remove(f) |
1731 else: | 1773 else: |
1732 repo.dirstate.drop(f) | 1774 repo.dirstate.drop(f) |
1733 | 1775 |
1734 # forget (must come first) | 1776 # forget (must come first) |
1735 for f, args, msg in actions.get('f', []): | 1777 for f, args, msg in actions.get(ACTION_FORGET, []): |
1736 repo.dirstate.drop(f) | 1778 repo.dirstate.drop(f) |
1737 | 1779 |
1738 # resolve path conflicts | 1780 # resolve path conflicts |
1739 for f, args, msg in actions.get('pr', []): | 1781 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []): |
1740 f0, = args | 1782 f0, = args |
1741 origf0 = repo.dirstate.copied(f0) or f0 | 1783 origf0 = repo.dirstate.copied(f0) or f0 |
1742 repo.dirstate.add(f) | 1784 repo.dirstate.add(f) |
1743 repo.dirstate.copy(origf0, f) | 1785 repo.dirstate.copy(origf0, f) |
1744 if f0 == origf0: | 1786 if f0 == origf0: |
1745 repo.dirstate.remove(f0) | 1787 repo.dirstate.remove(f0) |
1746 else: | 1788 else: |
1747 repo.dirstate.drop(f0) | 1789 repo.dirstate.drop(f0) |
1748 | 1790 |
1749 # re-add | 1791 # re-add |
1750 for f, args, msg in actions.get('a', []): | 1792 for f, args, msg in actions.get(ACTION_ADD, []): |
1751 repo.dirstate.add(f) | 1793 repo.dirstate.add(f) |
1752 | 1794 |
1753 # re-add/mark as modified | 1795 # re-add/mark as modified |
1754 for f, args, msg in actions.get('am', []): | 1796 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []): |
1755 if branchmerge: | 1797 if branchmerge: |
1756 repo.dirstate.normallookup(f) | 1798 repo.dirstate.normallookup(f) |
1757 else: | 1799 else: |
1758 repo.dirstate.add(f) | 1800 repo.dirstate.add(f) |
1759 | 1801 |
1760 # exec change | 1802 # exec change |
1761 for f, args, msg in actions.get('e', []): | 1803 for f, args, msg in actions.get(ACTION_EXEC, []): |
1762 repo.dirstate.normallookup(f) | 1804 repo.dirstate.normallookup(f) |
1763 | 1805 |
1764 # keep | 1806 # keep |
1765 for f, args, msg in actions.get('k', []): | 1807 for f, args, msg in actions.get(ACTION_KEEP, []): |
1766 pass | 1808 pass |
1767 | 1809 |
1768 # get | 1810 # get |
1769 for f, args, msg in actions.get('g', []): | 1811 for f, args, msg in actions.get(ACTION_GET, []): |
1770 if branchmerge: | 1812 if branchmerge: |
1771 repo.dirstate.otherparent(f) | 1813 repo.dirstate.otherparent(f) |
1772 else: | 1814 else: |
1773 repo.dirstate.normal(f) | 1815 repo.dirstate.normal(f) |
1774 | 1816 |
1775 # merge | 1817 # merge |
1776 for f, args, msg in actions.get('m', []): | 1818 for f, args, msg in actions.get(ACTION_MERGE, []): |
1777 f1, f2, fa, move, anc = args | 1819 f1, f2, fa, move, anc = args |
1778 if branchmerge: | 1820 if branchmerge: |
1779 # We've done a branch merge, mark this file as merged | 1821 # We've done a branch merge, mark this file as merged |
1780 # so that we properly record the merger later | 1822 # so that we properly record the merger later |
1781 repo.dirstate.merge(f) | 1823 repo.dirstate.merge(f) |
1796 repo.dirstate.normallookup(f) | 1838 repo.dirstate.normallookup(f) |
1797 if move: | 1839 if move: |
1798 repo.dirstate.drop(f1) | 1840 repo.dirstate.drop(f1) |
1799 | 1841 |
1800 # directory rename, move local | 1842 # directory rename, move local |
1801 for f, args, msg in actions.get('dm', []): | 1843 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []): |
1802 f0, flag = args | 1844 f0, flag = args |
1803 if branchmerge: | 1845 if branchmerge: |
1804 repo.dirstate.add(f) | 1846 repo.dirstate.add(f) |
1805 repo.dirstate.remove(f0) | 1847 repo.dirstate.remove(f0) |
1806 repo.dirstate.copy(f0, f) | 1848 repo.dirstate.copy(f0, f) |
1807 else: | 1849 else: |
1808 repo.dirstate.normal(f) | 1850 repo.dirstate.normal(f) |
1809 repo.dirstate.drop(f0) | 1851 repo.dirstate.drop(f0) |
1810 | 1852 |
1811 # directory rename, get | 1853 # directory rename, get |
1812 for f, args, msg in actions.get('dg', []): | 1854 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []): |
1813 f0, flag = args | 1855 f0, flag = args |
1814 if branchmerge: | 1856 if branchmerge: |
1815 repo.dirstate.add(f) | 1857 repo.dirstate.add(f) |
1816 repo.dirstate.copy(f0, f) | 1858 repo.dirstate.copy(f0, f) |
1817 else: | 1859 else: |
1980 repo, wc, p2, pas, branchmerge, force, mergeancestor, | 2022 repo, wc, p2, pas, branchmerge, force, mergeancestor, |
1981 followcopies, matcher=matcher, mergeforce=mergeforce) | 2023 followcopies, matcher=matcher, mergeforce=mergeforce) |
1982 | 2024 |
1983 if updatecheck == 'noconflict': | 2025 if updatecheck == 'noconflict': |
1984 for f, (m, args, msg) in actionbyfile.iteritems(): | 2026 for f, (m, args, msg) in actionbyfile.iteritems(): |
1985 if m not in ('g', 'k', 'e', 'r', 'pr'): | 2027 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC, |
2028 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE): | |
1986 msg = _("conflicting changes") | 2029 msg = _("conflicting changes") |
1987 hint = _("commit or update --clean to discard changes") | 2030 hint = _("commit or update --clean to discard changes") |
1988 raise error.Abort(msg, hint=hint) | 2031 raise error.Abort(msg, hint=hint) |
1989 | 2032 |
1990 # Prompt and create actions. Most of this is in the resolve phase | 2033 # Prompt and create actions. Most of this is in the resolve phase |
1993 if '.hgsubstate' in actionbyfile: | 2036 if '.hgsubstate' in actionbyfile: |
1994 f = '.hgsubstate' | 2037 f = '.hgsubstate' |
1995 m, args, msg = actionbyfile[f] | 2038 m, args, msg = actionbyfile[f] |
1996 prompts = filemerge.partextras(labels) | 2039 prompts = filemerge.partextras(labels) |
1997 prompts['f'] = f | 2040 prompts['f'] = f |
1998 if m == 'cd': | 2041 if m == ACTION_CHANGED_DELETED: |
1999 if repo.ui.promptchoice( | 2042 if repo.ui.promptchoice( |
2000 _("local%(l)s changed %(f)s which other%(o)s deleted\n" | 2043 _("local%(l)s changed %(f)s which other%(o)s deleted\n" |
2001 "use (c)hanged version or (d)elete?" | 2044 "use (c)hanged version or (d)elete?" |
2002 "$$ &Changed $$ &Delete") % prompts, 0): | 2045 "$$ &Changed $$ &Delete") % prompts, 0): |
2003 actionbyfile[f] = ('r', None, "prompt delete") | 2046 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete') |
2004 elif f in p1: | 2047 elif f in p1: |
2005 actionbyfile[f] = ('am', None, "prompt keep") | 2048 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep') |
2006 else: | 2049 else: |
2007 actionbyfile[f] = ('a', None, "prompt keep") | 2050 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep') |
2008 elif m == 'dc': | 2051 elif m == ACTION_DELETED_CHANGED: |
2009 f1, f2, fa, move, anc = args | 2052 f1, f2, fa, move, anc = args |
2010 flags = p2[f2].flags() | 2053 flags = p2[f2].flags() |
2011 if repo.ui.promptchoice( | 2054 if repo.ui.promptchoice( |
2012 _("other%(o)s changed %(f)s which local%(l)s deleted\n" | 2055 _("other%(o)s changed %(f)s which local%(l)s deleted\n" |
2013 "use (c)hanged version or leave (d)eleted?" | 2056 "use (c)hanged version or leave (d)eleted?" |
2014 "$$ &Changed $$ &Deleted") % prompts, 0) == 0: | 2057 "$$ &Changed $$ &Deleted") % prompts, 0) == 0: |
2015 actionbyfile[f] = ('g', (flags, False), "prompt recreating") | 2058 actionbyfile[f] = (ACTION_GET, (flags, False), |
2059 'prompt recreating') | |
2016 else: | 2060 else: |
2017 del actionbyfile[f] | 2061 del actionbyfile[f] |
2018 | 2062 |
2019 # Convert to dictionary-of-lists format | 2063 # Convert to dictionary-of-lists format |
2020 actions = dict((m, []) | 2064 actions = dict((m, []) |
2021 for m in 'a am f g cd dc r dm dg m e k p pr'.split()) | 2065 for m in ( |
2066 ACTION_ADD, | |
2067 ACTION_ADD_MODIFIED, | |
2068 ACTION_FORGET, | |
2069 ACTION_GET, | |
2070 ACTION_CHANGED_DELETED, | |
2071 ACTION_DELETED_CHANGED, | |
2072 ACTION_REMOVE, | |
2073 ACTION_DIR_RENAME_MOVE_LOCAL, | |
2074 ACTION_LOCAL_DIR_RENAME_GET, | |
2075 ACTION_MERGE, | |
2076 ACTION_EXEC, | |
2077 ACTION_KEEP, | |
2078 ACTION_PATH_CONFLICT, | |
2079 ACTION_PATH_CONFLICT_RESOLVE)) | |
2022 for f, (m, args, msg) in actionbyfile.iteritems(): | 2080 for f, (m, args, msg) in actionbyfile.iteritems(): |
2023 if m not in actions: | 2081 if m not in actions: |
2024 actions[m] = [] | 2082 actions[m] = [] |
2025 actions[m].append((f, args, msg)) | 2083 actions[m].append((f, args, msg)) |
2026 | 2084 |
2079 fsmonitorenabled = False | 2137 fsmonitorenabled = False |
2080 | 2138 |
2081 if (fsmonitorwarning | 2139 if (fsmonitorwarning |
2082 and not fsmonitorenabled | 2140 and not fsmonitorenabled |
2083 and p1.node() == nullid | 2141 and p1.node() == nullid |
2084 and len(actions['g']) >= fsmonitorthreshold | 2142 and len(actions[ACTION_GET]) >= fsmonitorthreshold |
2085 and pycompat.sysplatform.startswith(('linux', 'darwin'))): | 2143 and pycompat.sysplatform.startswith(('linux', 'darwin'))): |
2086 repo.ui.warn( | 2144 repo.ui.warn( |
2087 _('(warning: large working directory being used without ' | 2145 _('(warning: large working directory being used without ' |
2088 'fsmonitor enabled; enable fsmonitor to improve performance; ' | 2146 'fsmonitor enabled; enable fsmonitor to improve performance; ' |
2089 'see "hg help -e fsmonitor")\n')) | 2147 'see "hg help -e fsmonitor")\n')) |