Mercurial > public > mercurial-scm > hg
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.""" |