comparison mercurial/merge.py @ 34555:989e884d1be9

merge: check for path conflicts when merging (issue5628) When merging, check for any path conflicts introduced by the manifest merge and rename the conflicting file to a safe name. Differential Revision: https://phab.mercurial-scm.org/D784
author Mark Thomas <mbthomas@fb.com>
date Mon, 02 Oct 2017 14:05:30 -0700
parents 0217d66846f7
children 1248aa48cac9
comparison
equal deleted inserted replaced
34554:6f11a74d489f 34555:989e884d1be9
848 def driverconclude(repo, ms, wctx, labels=None): 848 def driverconclude(repo, ms, wctx, labels=None):
849 """run the conclude step of the merge driver, if any 849 """run the conclude step of the merge driver, if any
850 850
851 This is currently not implemented -- it's an extension point.""" 851 This is currently not implemented -- it's an extension point."""
852 return True 852 return True
853
854 def _filesindirs(repo, manifest, dirs):
855 """
856 Generator that yields pairs of all the files in the manifest that are found
857 inside the directories listed in dirs, and which directory they are found
858 in.
859 """
860 for f in manifest:
861 for p in util.finddirs(f):
862 if p in dirs:
863 yield f, p
864 break
865
866 def checkpathconflicts(repo, wctx, mctx, actions):
867 """
868 Check if any actions introduce path conflicts in the repository, updating
869 actions to record or handle the path conflict accordingly.
870 """
871 mf = wctx.manifest()
872
873 # The set of local files that conflict with a remote directory.
874 localconflicts = set()
875
876 # The set of directories that conflict with a remote file, and so may cause
877 # conflicts if they still contain any files after the merge.
878 remoteconflicts = set()
879
880 # The set of directories that appear as both a file and a directory in the
881 # remote manifest. These indicate an invalid remote manifest, which
882 # can't be updated to cleanly.
883 invalidconflicts = set()
884
885 # The set of files deleted by all the actions.
886 deletedfiles = set()
887
888 for f, (m, args, msg) in actions.items():
889 if m in ('c', 'dc', 'm', 'cm'):
890 # This action may create a new local file.
891 if mf.hasdir(f):
892 # The file aliases a local directory. This might be ok if all
893 # the files in the local directory are being deleted. This
894 # will be checked once we know what all the deleted files are.
895 remoteconflicts.add(f)
896 for p in util.finddirs(f):
897 if p in mf:
898 if p in mctx:
899 # The file is in a directory which aliases both a local
900 # and a remote file. This is an internal inconsistency
901 # within the remote manifest.
902 invalidconflicts.add(p)
903 else:
904 # The file is in a directory which aliases a local file.
905 # We will need to rename the local file.
906 localconflicts.add(p)
907 if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
908 # The file is in a directory which aliases a remote file.
909 # This is an internal inconsistency within the remote
910 # manifest.
911 invalidconflicts.add(p)
912
913 # Track the names of all deleted files.
914 if m == 'r':
915 deletedfiles.add(f)
916 if m == 'm':
917 f1, f2, fa, move, anc = args
918 if move:
919 deletedfiles.add(f1)
920 if m == 'dm':
921 f2, flags = args
922 deletedfiles.add(f2)
923
924 # Rename all local conflicting files that have not been deleted.
925 for p in localconflicts:
926 if p not in deletedfiles:
927 ctxname = str(wctx).rstrip('+')
928 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
929 actions[pnew] = ('pr', (p,), "local path conflict")
930 actions[p] = ('p', (pnew, 'l'), "path conflict")
931
932 if remoteconflicts:
933 # Check if all files in the conflicting directories have been removed.
934 ctxname = str(mctx).rstrip('+')
935 for f, p in _filesindirs(repo, mf, remoteconflicts):
936 if f not in deletedfiles:
937 m, args, msg = actions[p]
938 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
939 if m in ('dc', 'm'):
940 # Action was merge, just update target.
941 actions[pnew] = (m, args, msg)
942 else:
943 # Action was create, change to renamed get action.
944 fl = args[0]
945 actions[pnew] = ('dg', (p, fl), "remote path conflict")
946 actions[p] = ('p', (pnew, 'r'), "path conflict")
947 remoteconflicts.remove(p)
948 break
949
950 if invalidconflicts:
951 for p in invalidconflicts:
952 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
953 raise error.Abort(_("destination manifest contains path conflicts"))
853 954
854 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher, 955 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
855 acceptremote, followcopies, forcefulldiff=False): 956 acceptremote, followcopies, forcefulldiff=False):
856 """ 957 """
857 Merge wctx and p2 with ancestor pa and generate merge action list 958 Merge wctx and p2 with ancestor pa and generate merge action list
1024 actions[f] = ('c', (fl2,), "remote recreating") 1125 actions[f] = ('c', (fl2,), "remote recreating")
1025 else: 1126 else:
1026 actions[f] = ('dc', (None, f, f, False, pa.node()), 1127 actions[f] = ('dc', (None, f, f, False, pa.node()),
1027 "prompt deleted/changed") 1128 "prompt deleted/changed")
1028 1129
1130 # If we are merging, look for path conflicts.
1131 checkpathconflicts(repo, wctx, p2, actions)
1132
1029 return actions, diverge, renamedelete 1133 return actions, diverge, renamedelete
1030 1134
1031 def _resolvetrivial(repo, wctx, mctx, ancestor, actions): 1135 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1032 """Resolves false conflicts where the nodeid changed but the content 1136 """Resolves false conflicts where the nodeid changed but the content
1033 remained the same.""" 1137 remained the same."""