224 RECORD_MERGED, |
224 RECORD_MERGED, |
225 RECORD_CHANGEDELETE_CONFLICT, |
225 RECORD_CHANGEDELETE_CONFLICT, |
226 RECORD_PATH_CONFLICT, |
226 RECORD_PATH_CONFLICT, |
227 RECORD_MERGE_DRIVER_MERGE, |
227 RECORD_MERGE_DRIVER_MERGE, |
228 ): |
228 ): |
229 bits = record.split('\0') |
229 bits = record.split(b'\0') |
230 self._state[bits[0]] = bits[1:] |
230 self._state[bits[0]] = bits[1:] |
231 elif rtype == RECORD_FILE_VALUES: |
231 elif rtype == RECORD_FILE_VALUES: |
232 filename, rawextras = record.split('\0', 1) |
232 filename, rawextras = record.split(b'\0', 1) |
233 extraparts = rawextras.split('\0') |
233 extraparts = rawextras.split(b'\0') |
234 extras = {} |
234 extras = {} |
235 i = 0 |
235 i = 0 |
236 while i < len(extraparts): |
236 while i < len(extraparts): |
237 extras[extraparts[i]] = extraparts[i + 1] |
237 extras[extraparts[i]] = extraparts[i + 1] |
238 i += 2 |
238 i += 2 |
239 |
239 |
240 self._stateextras[filename] = extras |
240 self._stateextras[filename] = extras |
241 elif rtype == RECORD_LABELS: |
241 elif rtype == RECORD_LABELS: |
242 labels = record.split('\0', 2) |
242 labels = record.split(b'\0', 2) |
243 self._labels = [l for l in labels if len(l) > 0] |
243 self._labels = [l for l in labels if len(l) > 0] |
244 elif not rtype.islower(): |
244 elif not rtype.islower(): |
245 unsupported.add(rtype) |
245 unsupported.add(rtype) |
246 self._results = {} |
246 self._results = {} |
247 self._dirty = False |
247 self._dirty = False |
367 # - A edits their hgrc to remove references to the merge driver |
367 # - A edits their hgrc to remove references to the merge driver |
368 # - A gives a copy of their entire repo, including .hg, to B |
368 # - A gives a copy of their entire repo, including .hg, to B |
369 # - B inspects .hgrc and finds it to be clean |
369 # - B inspects .hgrc and finds it to be clean |
370 # - B then continues the merge and the malicious merge driver |
370 # - B then continues the merge and the malicious merge driver |
371 # gets invoked |
371 # gets invoked |
372 configmergedriver = self._repo.ui.config('experimental', 'mergedriver') |
372 configmergedriver = self._repo.ui.config( |
|
373 b'experimental', b'mergedriver' |
|
374 ) |
373 if ( |
375 if ( |
374 self._readmergedriver is not None |
376 self._readmergedriver is not None |
375 and self._readmergedriver != configmergedriver |
377 and self._readmergedriver != configmergedriver |
376 ): |
378 ): |
377 raise error.ConfigError( |
379 raise error.ConfigError( |
378 _("merge driver changed since merge started"), |
380 _(b"merge driver changed since merge started"), |
379 hint=_("revert merge driver change or abort merge"), |
381 hint=_(b"revert merge driver change or abort merge"), |
380 ) |
382 ) |
381 |
383 |
382 return configmergedriver |
384 return configmergedriver |
383 |
385 |
384 @util.propertycache |
386 @util.propertycache |
385 def localctx(self): |
387 def localctx(self): |
386 if self._local is None: |
388 if self._local is None: |
387 msg = "localctx accessed but self._local isn't set" |
389 msg = b"localctx accessed but self._local isn't set" |
388 raise error.ProgrammingError(msg) |
390 raise error.ProgrammingError(msg) |
389 return self._repo[self._local] |
391 return self._repo[self._local] |
390 |
392 |
391 @util.propertycache |
393 @util.propertycache |
392 def otherctx(self): |
394 def otherctx(self): |
393 if self._other is None: |
395 if self._other is None: |
394 msg = "otherctx accessed but self._other isn't set" |
396 msg = b"otherctx accessed but self._other isn't set" |
395 raise error.ProgrammingError(msg) |
397 raise error.ProgrammingError(msg) |
396 return self._repo[self._other] |
398 return self._repo[self._other] |
397 |
399 |
398 def active(self): |
400 def active(self): |
399 """Whether mergestate is active. |
401 """Whether mergestate is active. |
435 # from loading them. |
437 # from loading them. |
436 for filename, v in self._state.iteritems(): |
438 for filename, v in self._state.iteritems(): |
437 if v[0] == MERGE_RECORD_DRIVER_RESOLVED: |
439 if v[0] == MERGE_RECORD_DRIVER_RESOLVED: |
438 # Driver-resolved merge. These are stored in 'D' records. |
440 # Driver-resolved merge. These are stored in 'D' records. |
439 records.append( |
441 records.append( |
440 (RECORD_MERGE_DRIVER_MERGE, '\0'.join([filename] + v)) |
442 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v)) |
441 ) |
443 ) |
442 elif v[0] in ( |
444 elif v[0] in ( |
443 MERGE_RECORD_UNRESOLVED_PATH, |
445 MERGE_RECORD_UNRESOLVED_PATH, |
444 MERGE_RECORD_RESOLVED_PATH, |
446 MERGE_RECORD_RESOLVED_PATH, |
445 ): |
447 ): |
446 # Path conflicts. These are stored in 'P' records. The current |
448 # Path conflicts. These are stored in 'P' records. The current |
447 # resolution state ('pu' or 'pr') is stored within the record. |
449 # resolution state ('pu' or 'pr') is stored within the record. |
448 records.append( |
450 records.append( |
449 (RECORD_PATH_CONFLICT, '\0'.join([filename] + v)) |
451 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v)) |
450 ) |
452 ) |
451 elif v[1] == nullhex or v[6] == nullhex: |
453 elif v[1] == nullhex or v[6] == nullhex: |
452 # Change/Delete or Delete/Change conflicts. These are stored in |
454 # Change/Delete or Delete/Change conflicts. These are stored in |
453 # 'C' records. v[1] is the local file, and is nullhex when the |
455 # 'C' records. v[1] is the local file, and is nullhex when the |
454 # file is deleted locally ('dc'). v[6] is the remote file, and |
456 # file is deleted locally ('dc'). v[6] is the remote file, and |
455 # is nullhex when the file is deleted remotely ('cd'). |
457 # is nullhex when the file is deleted remotely ('cd'). |
456 records.append( |
458 records.append( |
457 (RECORD_CHANGEDELETE_CONFLICT, '\0'.join([filename] + v)) |
459 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v)) |
458 ) |
460 ) |
459 else: |
461 else: |
460 # Normal files. These are stored in 'F' records. |
462 # Normal files. These are stored in 'F' records. |
461 records.append((RECORD_MERGED, '\0'.join([filename] + v))) |
463 records.append((RECORD_MERGED, b'\0'.join([filename] + v))) |
462 for filename, extras in sorted(self._stateextras.iteritems()): |
464 for filename, extras in sorted(self._stateextras.iteritems()): |
463 rawextras = '\0'.join( |
465 rawextras = b'\0'.join( |
464 '%s\0%s' % (k, v) for k, v in extras.iteritems() |
466 b'%s\0%s' % (k, v) for k, v in extras.iteritems() |
465 ) |
467 ) |
466 records.append( |
468 records.append( |
467 (RECORD_FILE_VALUES, '%s\0%s' % (filename, rawextras)) |
469 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras)) |
468 ) |
470 ) |
469 if self._labels is not None: |
471 if self._labels is not None: |
470 labels = '\0'.join(self._labels) |
472 labels = b'\0'.join(self._labels) |
471 records.append((RECORD_LABELS, labels)) |
473 records.append((RECORD_LABELS, labels)) |
472 return records |
474 return records |
473 |
475 |
474 def _writerecords(self, records): |
476 def _writerecords(self, records): |
475 """Write current state on disk (both v1 and v2)""" |
477 """Write current state on disk (both v1 and v2)""" |
476 self._writerecordsv1(records) |
478 self._writerecordsv1(records) |
477 self._writerecordsv2(records) |
479 self._writerecordsv2(records) |
478 |
480 |
479 def _writerecordsv1(self, records): |
481 def _writerecordsv1(self, records): |
480 """Write current state on disk in a version 1 file""" |
482 """Write current state on disk in a version 1 file""" |
481 f = self._repo.vfs(self.statepathv1, 'wb') |
483 f = self._repo.vfs(self.statepathv1, b'wb') |
482 irecords = iter(records) |
484 irecords = iter(records) |
483 lrecords = next(irecords) |
485 lrecords = next(irecords) |
484 assert lrecords[0] == RECORD_LOCAL |
486 assert lrecords[0] == RECORD_LOCAL |
485 f.write(hex(self._local) + '\n') |
487 f.write(hex(self._local) + b'\n') |
486 for rtype, data in irecords: |
488 for rtype, data in irecords: |
487 if rtype == RECORD_MERGED: |
489 if rtype == RECORD_MERGED: |
488 f.write('%s\n' % _droponode(data)) |
490 f.write(b'%s\n' % _droponode(data)) |
489 f.close() |
491 f.close() |
490 |
492 |
491 def _writerecordsv2(self, records): |
493 def _writerecordsv2(self, records): |
492 """Write current state on disk in a version 2 file |
494 """Write current state on disk in a version 2 file |
493 |
495 |
494 See the docstring for _readrecordsv2 for why we use 't'.""" |
496 See the docstring for _readrecordsv2 for why we use 't'.""" |
495 # these are the records that all version 2 clients can read |
497 # these are the records that all version 2 clients can read |
496 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
498 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
497 f = self._repo.vfs(self.statepathv2, 'wb') |
499 f = self._repo.vfs(self.statepathv2, b'wb') |
498 for key, data in records: |
500 for key, data in records: |
499 assert len(key) == 1 |
501 assert len(key) == 1 |
500 if key not in allowlist: |
502 if key not in allowlist: |
501 key, data = RECORD_OVERRIDE, '%s%s' % (key, data) |
503 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data) |
502 format = '>sI%is' % len(data) |
504 format = b'>sI%is' % len(data) |
503 f.write(_pack(format, key, len(data), data)) |
505 f.write(_pack(format, key, len(data), data)) |
504 f.close() |
506 f.close() |
505 |
507 |
506 @staticmethod |
508 @staticmethod |
507 def getlocalkey(path): |
509 def getlocalkey(path): |
846 """ |
848 """ |
847 fileconflicts = set() |
849 fileconflicts = set() |
848 pathconflicts = set() |
850 pathconflicts = set() |
849 warnconflicts = set() |
851 warnconflicts = set() |
850 abortconflicts = set() |
852 abortconflicts = set() |
851 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown') |
853 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown') |
852 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored') |
854 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored') |
853 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts') |
855 pathconfig = repo.ui.configbool( |
|
856 b'experimental', b'merge.checkpathconflicts' |
|
857 ) |
854 if not force: |
858 if not force: |
855 |
859 |
856 def collectconflicts(conflicts, config): |
860 def collectconflicts(conflicts, config): |
857 if config == 'abort': |
861 if config == b'abort': |
858 abortconflicts.update(conflicts) |
862 abortconflicts.update(conflicts) |
859 elif config == 'warn': |
863 elif config == b'warn': |
860 warnconflicts.update(conflicts) |
864 warnconflicts.update(conflicts) |
861 |
865 |
862 checkunknowndirs = _unknowndirschecker() |
866 checkunknowndirs = _unknowndirschecker() |
863 for f, (m, args, msg) in actions.iteritems(): |
867 for f, (m, args, msg) in actions.iteritems(): |
864 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED): |
868 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED): |
898 # (1) this is probably the wrong behavior here -- we should |
902 # (1) this is probably the wrong behavior here -- we should |
899 # probably abort, but some actions like rebases currently |
903 # probably abort, but some actions like rebases currently |
900 # don't like an abort happening in the middle of |
904 # don't like an abort happening in the middle of |
901 # merge.update. |
905 # merge.update. |
902 if not different: |
906 if not different: |
903 actions[f] = (ACTION_GET, (fl2, False), 'remote created') |
907 actions[f] = (ACTION_GET, (fl2, False), b'remote created') |
904 elif mergeforce or config == 'abort': |
908 elif mergeforce or config == b'abort': |
905 actions[f] = ( |
909 actions[f] = ( |
906 ACTION_MERGE, |
910 ACTION_MERGE, |
907 (f, f, None, False, anc), |
911 (f, f, None, False, anc), |
908 'remote differs from untracked local', |
912 b'remote differs from untracked local', |
909 ) |
913 ) |
910 elif config == 'abort': |
914 elif config == b'abort': |
911 abortconflicts.add(f) |
915 abortconflicts.add(f) |
912 else: |
916 else: |
913 if config == 'warn': |
917 if config == b'warn': |
914 warnconflicts.add(f) |
918 warnconflicts.add(f) |
915 actions[f] = (ACTION_GET, (fl2, True), 'remote created') |
919 actions[f] = (ACTION_GET, (fl2, True), b'remote created') |
916 |
920 |
917 for f in sorted(abortconflicts): |
921 for f in sorted(abortconflicts): |
918 warn = repo.ui.warn |
922 warn = repo.ui.warn |
919 if f in pathconflicts: |
923 if f in pathconflicts: |
920 if repo.wvfs.isfileorlink(f): |
924 if repo.wvfs.isfileorlink(f): |
921 warn(_("%s: untracked file conflicts with directory\n") % f) |
925 warn(_(b"%s: untracked file conflicts with directory\n") % f) |
922 else: |
926 else: |
923 warn(_("%s: untracked directory conflicts with file\n") % f) |
927 warn(_(b"%s: untracked directory conflicts with file\n") % f) |
924 else: |
928 else: |
925 warn(_("%s: untracked file differs\n") % f) |
929 warn(_(b"%s: untracked file differs\n") % f) |
926 if abortconflicts: |
930 if abortconflicts: |
927 raise error.Abort( |
931 raise error.Abort( |
928 _( |
932 _( |
929 "untracked files in working directory " |
933 b"untracked files in working directory " |
930 "differ from files in requested revision" |
934 b"differ from files in requested revision" |
931 ) |
935 ) |
932 ) |
936 ) |
933 |
937 |
934 for f in sorted(warnconflicts): |
938 for f in sorted(warnconflicts): |
935 if repo.wvfs.isfileorlink(f): |
939 if repo.wvfs.isfileorlink(f): |
936 repo.ui.warn(_("%s: replacing untracked file\n") % f) |
940 repo.ui.warn(_(b"%s: replacing untracked file\n") % f) |
937 else: |
941 else: |
938 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f) |
942 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f) |
939 |
943 |
940 for f, (m, args, msg) in actions.iteritems(): |
944 for f, (m, args, msg) in actions.iteritems(): |
941 if m == ACTION_CREATED: |
945 if m == ACTION_CREATED: |
942 backup = ( |
946 backup = ( |
943 f in fileconflicts |
947 f in fileconflicts |
1152 invalidconflicts.add(p) |
1156 invalidconflicts.add(p) |
1153 |
1157 |
1154 # Rename all local conflicting files that have not been deleted. |
1158 # Rename all local conflicting files that have not been deleted. |
1155 for p in localconflicts: |
1159 for p in localconflicts: |
1156 if p not in deletedfiles: |
1160 if p not in deletedfiles: |
1157 ctxname = bytes(wctx).rstrip('+') |
1161 ctxname = bytes(wctx).rstrip(b'+') |
1158 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1162 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1159 actions[pnew] = ( |
1163 actions[pnew] = ( |
1160 ACTION_PATH_CONFLICT_RESOLVE, |
1164 ACTION_PATH_CONFLICT_RESOLVE, |
1161 (p,), |
1165 (p,), |
1162 'local path conflict', |
1166 b'local path conflict', |
1163 ) |
1167 ) |
1164 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'), 'path conflict') |
1168 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict') |
1165 |
1169 |
1166 if remoteconflicts: |
1170 if remoteconflicts: |
1167 # Check if all files in the conflicting directories have been removed. |
1171 # Check if all files in the conflicting directories have been removed. |
1168 ctxname = bytes(mctx).rstrip('+') |
1172 ctxname = bytes(mctx).rstrip(b'+') |
1169 for f, p in _filesindirs(repo, mf, remoteconflicts): |
1173 for f, p in _filesindirs(repo, mf, remoteconflicts): |
1170 if f not in deletedfiles: |
1174 if f not in deletedfiles: |
1171 m, args, msg = actions[p] |
1175 m, args, msg = actions[p] |
1172 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1176 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) |
1173 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE): |
1177 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE): |
1177 # Action was create, change to renamed get action. |
1181 # Action was create, change to renamed get action. |
1178 fl = args[0] |
1182 fl = args[0] |
1179 actions[pnew] = ( |
1183 actions[pnew] = ( |
1180 ACTION_LOCAL_DIR_RENAME_GET, |
1184 ACTION_LOCAL_DIR_RENAME_GET, |
1181 (p, fl), |
1185 (p, fl), |
1182 'remote path conflict', |
1186 b'remote path conflict', |
1183 ) |
1187 ) |
1184 actions[p] = ( |
1188 actions[p] = ( |
1185 ACTION_PATH_CONFLICT, |
1189 ACTION_PATH_CONFLICT, |
1186 (pnew, ACTION_REMOVE), |
1190 (pnew, ACTION_REMOVE), |
1187 'path conflict', |
1191 b'path conflict', |
1188 ) |
1192 ) |
1189 remoteconflicts.remove(p) |
1193 remoteconflicts.remove(p) |
1190 break |
1194 break |
1191 |
1195 |
1192 if invalidconflicts: |
1196 if invalidconflicts: |
1193 for p in invalidconflicts: |
1197 for p in invalidconflicts: |
1194 repo.ui.warn(_("%s: is both a file and a directory\n") % p) |
1198 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p) |
1195 raise error.Abort(_("destination manifest contains path conflicts")) |
1199 raise error.Abort(_(b"destination manifest contains path conflicts")) |
1196 |
1200 |
1197 |
1201 |
1198 def _filternarrowactions(narrowmatch, branchmerge, actions): |
1202 def _filternarrowactions(narrowmatch, branchmerge, actions): |
1199 """ |
1203 """ |
1200 Filters out actions that can ignored because the repo is narrowed. |
1204 Filters out actions that can ignored because the repo is narrowed. |
1201 |
1205 |
1202 Raise an exception if the merge cannot be completed because the repo is |
1206 Raise an exception if the merge cannot be completed because the repo is |
1203 narrowed. |
1207 narrowed. |
1204 """ |
1208 """ |
1205 nooptypes = {'k'} # TODO: handle with nonconflicttypes |
1209 nooptypes = {b'k'} # TODO: handle with nonconflicttypes |
1206 nonconflicttypes = set('a am c cm f g r e'.split()) |
1210 nonconflicttypes = set(b'a am c cm f g r e'.split()) |
1207 # We mutate the items in the dict during iteration, so iterate |
1211 # We mutate the items in the dict during iteration, so iterate |
1208 # over a copy. |
1212 # over a copy. |
1209 for f, action in list(actions.items()): |
1213 for f, action in list(actions.items()): |
1210 if narrowmatch(f): |
1214 if narrowmatch(f): |
1211 pass |
1215 pass |
1263 copy, movewithdir, diverge, renamedelete, dirmove = ret |
1267 copy, movewithdir, diverge, renamedelete, dirmove = ret |
1264 |
1268 |
1265 boolbm = pycompat.bytestr(bool(branchmerge)) |
1269 boolbm = pycompat.bytestr(bool(branchmerge)) |
1266 boolf = pycompat.bytestr(bool(force)) |
1270 boolf = pycompat.bytestr(bool(force)) |
1267 boolm = pycompat.bytestr(bool(matcher)) |
1271 boolm = pycompat.bytestr(bool(matcher)) |
1268 repo.ui.note(_("resolving manifests\n")) |
1272 repo.ui.note(_(b"resolving manifests\n")) |
1269 repo.ui.debug( |
1273 repo.ui.debug( |
1270 " branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm) |
1274 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm) |
1271 ) |
1275 ) |
1272 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) |
1276 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) |
1273 |
1277 |
1274 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() |
1278 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() |
1275 copied = set(copy.values()) |
1279 copied = set(copy.values()) |
1276 copied.update(movewithdir.values()) |
1280 copied.update(movewithdir.values()) |
1277 |
1281 |
1278 if '.hgsubstate' in m1 and wctx.rev() is None: |
1282 if b'.hgsubstate' in m1 and wctx.rev() is None: |
1279 # Check whether sub state is modified, and overwrite the manifest |
1283 # Check whether sub state is modified, and overwrite the manifest |
1280 # to flag the change. If wctx is a committed revision, we shouldn't |
1284 # to flag the change. If wctx is a committed revision, we shouldn't |
1281 # care for the dirty state of the working directory. |
1285 # care for the dirty state of the working directory. |
1282 if any(wctx.sub(s).dirty() for s in wctx.substate): |
1286 if any(wctx.sub(s).dirty() for s in wctx.substate): |
1283 m1['.hgsubstate'] = modifiednodeid |
1287 m1[b'.hgsubstate'] = modifiednodeid |
1284 |
1288 |
1285 # Don't use m2-vs-ma optimization if: |
1289 # Don't use m2-vs-ma optimization if: |
1286 # - ma is the same as m1 or m2, which we're just going to diff again later |
1290 # - ma is the same as m1 or m2, which we're just going to diff again later |
1287 # - The caller specifically asks for a full diff, which is useful during bid |
1291 # - The caller specifically asks for a full diff, which is useful during bid |
1288 # merge. |
1292 # merge. |
1310 fa = copy.get(f, None) |
1314 fa = copy.get(f, None) |
1311 if fa is not None: |
1315 if fa is not None: |
1312 actions[f] = ( |
1316 actions[f] = ( |
1313 ACTION_MERGE, |
1317 ACTION_MERGE, |
1314 (f, f, fa, False, pa.node()), |
1318 (f, f, fa, False, pa.node()), |
1315 'both renamed from %s' % fa, |
1319 b'both renamed from %s' % fa, |
1316 ) |
1320 ) |
1317 else: |
1321 else: |
1318 actions[f] = ( |
1322 actions[f] = ( |
1319 ACTION_MERGE, |
1323 ACTION_MERGE, |
1320 (f, f, None, False, pa.node()), |
1324 (f, f, None, False, pa.node()), |
1321 'both created', |
1325 b'both created', |
1322 ) |
1326 ) |
1323 else: |
1327 else: |
1324 a = ma[f] |
1328 a = ma[f] |
1325 fla = ma.flags(f) |
1329 fla = ma.flags(f) |
1326 nol = 'l' not in fl1 + fl2 + fla |
1330 nol = b'l' not in fl1 + fl2 + fla |
1327 if n2 == a and fl2 == fla: |
1331 if n2 == a and fl2 == fla: |
1328 actions[f] = (ACTION_KEEP, (), 'remote unchanged') |
1332 actions[f] = (ACTION_KEEP, (), b'remote unchanged') |
1329 elif n1 == a and fl1 == fla: # local unchanged - use remote |
1333 elif n1 == a and fl1 == fla: # local unchanged - use remote |
1330 if n1 == n2: # optimization: keep local content |
1334 if n1 == n2: # optimization: keep local content |
1331 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions') |
1335 actions[f] = ( |
|
1336 ACTION_EXEC, |
|
1337 (fl2,), |
|
1338 b'update permissions', |
|
1339 ) |
1332 else: |
1340 else: |
1333 actions[f] = ( |
1341 actions[f] = ( |
1334 ACTION_GET, |
1342 ACTION_GET, |
1335 (fl2, False), |
1343 (fl2, False), |
1336 'remote is newer', |
1344 b'remote is newer', |
1337 ) |
1345 ) |
1338 elif nol and n2 == a: # remote only changed 'x' |
1346 elif nol and n2 == a: # remote only changed 'x' |
1339 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions') |
1347 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions') |
1340 elif nol and n1 == a: # local only changed 'x' |
1348 elif nol and n1 == a: # local only changed 'x' |
1341 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer') |
1349 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer') |
1342 else: # both changed something |
1350 else: # both changed something |
1343 actions[f] = ( |
1351 actions[f] = ( |
1344 ACTION_MERGE, |
1352 ACTION_MERGE, |
1345 (f, f, f, False, pa.node()), |
1353 (f, f, f, False, pa.node()), |
1346 'versions differ', |
1354 b'versions differ', |
1347 ) |
1355 ) |
1348 elif n1: # file exists only on local side |
1356 elif n1: # file exists only on local side |
1349 if f in copied: |
1357 if f in copied: |
1350 pass # we'll deal with it on m2 side |
1358 pass # we'll deal with it on m2 side |
1351 elif f in movewithdir: # directory rename, move local |
1359 elif f in movewithdir: # directory rename, move local |
1352 f2 = movewithdir[f] |
1360 f2 = movewithdir[f] |
1353 if f2 in m2: |
1361 if f2 in m2: |
1354 actions[f2] = ( |
1362 actions[f2] = ( |
1355 ACTION_MERGE, |
1363 ACTION_MERGE, |
1356 (f, f2, None, True, pa.node()), |
1364 (f, f2, None, True, pa.node()), |
1357 'remote directory rename, both created', |
1365 b'remote directory rename, both created', |
1358 ) |
1366 ) |
1359 else: |
1367 else: |
1360 actions[f2] = ( |
1368 actions[f2] = ( |
1361 ACTION_DIR_RENAME_MOVE_LOCAL, |
1369 ACTION_DIR_RENAME_MOVE_LOCAL, |
1362 (f, fl1), |
1370 (f, fl1), |
1363 'remote directory rename - move from %s' % f, |
1371 b'remote directory rename - move from %s' % f, |
1364 ) |
1372 ) |
1365 elif f in copy: |
1373 elif f in copy: |
1366 f2 = copy[f] |
1374 f2 = copy[f] |
1367 actions[f] = ( |
1375 actions[f] = ( |
1368 ACTION_MERGE, |
1376 ACTION_MERGE, |
1369 (f, f2, f2, False, pa.node()), |
1377 (f, f2, f2, False, pa.node()), |
1370 'local copied/moved from %s' % f2, |
1378 b'local copied/moved from %s' % f2, |
1371 ) |
1379 ) |
1372 elif f in ma: # clean, a different, no remote |
1380 elif f in ma: # clean, a different, no remote |
1373 if n1 != ma[f]: |
1381 if n1 != ma[f]: |
1374 if acceptremote: |
1382 if acceptremote: |
1375 actions[f] = (ACTION_REMOVE, None, 'remote delete') |
1383 actions[f] = (ACTION_REMOVE, None, b'remote delete') |
1376 else: |
1384 else: |
1377 actions[f] = ( |
1385 actions[f] = ( |
1378 ACTION_CHANGED_DELETED, |
1386 ACTION_CHANGED_DELETED, |
1379 (f, None, f, False, pa.node()), |
1387 (f, None, f, False, pa.node()), |
1380 'prompt changed/deleted', |
1388 b'prompt changed/deleted', |
1381 ) |
1389 ) |
1382 elif n1 == addednodeid: |
1390 elif n1 == addednodeid: |
1383 # This extra 'a' is added by working copy manifest to mark |
1391 # This extra 'a' is added by working copy manifest to mark |
1384 # the file as locally added. We should forget it instead of |
1392 # the file as locally added. We should forget it instead of |
1385 # deleting it. |
1393 # deleting it. |
1386 actions[f] = (ACTION_FORGET, None, 'remote deleted') |
1394 actions[f] = (ACTION_FORGET, None, b'remote deleted') |
1387 else: |
1395 else: |
1388 actions[f] = (ACTION_REMOVE, None, 'other deleted') |
1396 actions[f] = (ACTION_REMOVE, None, b'other deleted') |
1389 elif n2: # file exists only on remote side |
1397 elif n2: # file exists only on remote side |
1390 if f in copied: |
1398 if f in copied: |
1391 pass # we'll deal with it on m1 side |
1399 pass # we'll deal with it on m1 side |
1392 elif f in movewithdir: |
1400 elif f in movewithdir: |
1393 f2 = movewithdir[f] |
1401 f2 = movewithdir[f] |
1394 if f2 in m1: |
1402 if f2 in m1: |
1395 actions[f2] = ( |
1403 actions[f2] = ( |
1396 ACTION_MERGE, |
1404 ACTION_MERGE, |
1397 (f2, f, None, False, pa.node()), |
1405 (f2, f, None, False, pa.node()), |
1398 'local directory rename, both created', |
1406 b'local directory rename, both created', |
1399 ) |
1407 ) |
1400 else: |
1408 else: |
1401 actions[f2] = ( |
1409 actions[f2] = ( |
1402 ACTION_LOCAL_DIR_RENAME_GET, |
1410 ACTION_LOCAL_DIR_RENAME_GET, |
1403 (f, fl2), |
1411 (f, fl2), |
1404 'local directory rename - get from %s' % f, |
1412 b'local directory rename - get from %s' % f, |
1405 ) |
1413 ) |
1406 elif f in copy: |
1414 elif f in copy: |
1407 f2 = copy[f] |
1415 f2 = copy[f] |
1408 if f2 in m2: |
1416 if f2 in m2: |
1409 actions[f] = ( |
1417 actions[f] = ( |
1410 ACTION_MERGE, |
1418 ACTION_MERGE, |
1411 (f2, f, f2, False, pa.node()), |
1419 (f2, f, f2, False, pa.node()), |
1412 'remote copied from %s' % f2, |
1420 b'remote copied from %s' % f2, |
1413 ) |
1421 ) |
1414 else: |
1422 else: |
1415 actions[f] = ( |
1423 actions[f] = ( |
1416 ACTION_MERGE, |
1424 ACTION_MERGE, |
1417 (f2, f, f2, True, pa.node()), |
1425 (f2, f, f2, True, pa.node()), |
1418 'remote moved from %s' % f2, |
1426 b'remote moved from %s' % f2, |
1419 ) |
1427 ) |
1420 elif f not in ma: |
1428 elif f not in ma: |
1421 # local unknown, remote created: the logic is described by the |
1429 # local unknown, remote created: the logic is described by the |
1422 # following table: |
1430 # following table: |
1423 # |
1431 # |
1448 break |
1456 break |
1449 if df is not None and df in m1: |
1457 if df is not None and df in m1: |
1450 actions[df] = ( |
1458 actions[df] = ( |
1451 ACTION_MERGE, |
1459 ACTION_MERGE, |
1452 (df, f, f, False, pa.node()), |
1460 (df, f, f, False, pa.node()), |
1453 'local directory rename - respect move ' 'from %s' % f, |
1461 b'local directory rename - respect move ' |
|
1462 b'from %s' % f, |
1454 ) |
1463 ) |
1455 elif acceptremote: |
1464 elif acceptremote: |
1456 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating') |
1465 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating') |
1457 else: |
1466 else: |
1458 actions[f] = ( |
1467 actions[f] = ( |
1459 ACTION_DELETED_CHANGED, |
1468 ACTION_DELETED_CHANGED, |
1460 (None, f, f, False, pa.node()), |
1469 (None, f, f, False, pa.node()), |
1461 'prompt deleted/changed', |
1470 b'prompt deleted/changed', |
1462 ) |
1471 ) |
1463 |
1472 |
1464 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'): |
1473 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'): |
1465 # If we are merging, look for path conflicts. |
1474 # If we are merging, look for path conflicts. |
1466 checkpathconflicts(repo, wctx, p2, actions) |
1475 checkpathconflicts(repo, wctx, p2, actions) |
1467 |
1476 |
1468 narrowmatch = repo.narrowmatch() |
1477 narrowmatch = repo.narrowmatch() |
1469 if not narrowmatch.always(): |
1478 if not narrowmatch.always(): |
1563 if renamedelete is None or len(renamedelete) < len(renamedelete1): |
1572 if renamedelete is None or len(renamedelete) < len(renamedelete1): |
1564 renamedelete = renamedelete1 |
1573 renamedelete = renamedelete1 |
1565 |
1574 |
1566 for f, a in sorted(actions.iteritems()): |
1575 for f, a in sorted(actions.iteritems()): |
1567 m, args, msg = a |
1576 m, args, msg = a |
1568 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m)) |
1577 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m)) |
1569 if f in fbids: |
1578 if f in fbids: |
1570 d = fbids[f] |
1579 d = fbids[f] |
1571 if m in d: |
1580 if m in d: |
1572 d[m].append(a) |
1581 d[m].append(a) |
1573 else: |
1582 else: |
1574 d[m] = [a] |
1583 d[m] = [a] |
1575 else: |
1584 else: |
1576 fbids[f] = {m: [a]} |
1585 fbids[f] = {m: [a]} |
1577 |
1586 |
1578 # Pick the best bid for each file |
1587 # Pick the best bid for each file |
1579 repo.ui.note(_('\nauction for merging merge bids\n')) |
1588 repo.ui.note(_(b'\nauction for merging merge bids\n')) |
1580 actions = {} |
1589 actions = {} |
1581 for f, bids in sorted(fbids.items()): |
1590 for f, bids in sorted(fbids.items()): |
1582 # bids is a mapping from action method to list af actions |
1591 # bids is a mapping from action method to list af actions |
1583 # Consensus? |
1592 # Consensus? |
1584 if len(bids) == 1: # all bids are the same kind of method |
1593 if len(bids) == 1: # all bids are the same kind of method |
1585 m, l = list(bids.items())[0] |
1594 m, l = list(bids.items())[0] |
1586 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 |
1595 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 |
1587 repo.ui.note(_(" %s: consensus for %s\n") % (f, m)) |
1596 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m)) |
1588 actions[f] = l[0] |
1597 actions[f] = l[0] |
1589 continue |
1598 continue |
1590 # If keep is an option, just do it. |
1599 # If keep is an option, just do it. |
1591 if ACTION_KEEP in bids: |
1600 if ACTION_KEEP in bids: |
1592 repo.ui.note(_(" %s: picking 'keep' action\n") % f) |
1601 repo.ui.note(_(b" %s: picking 'keep' action\n") % f) |
1593 actions[f] = bids[ACTION_KEEP][0] |
1602 actions[f] = bids[ACTION_KEEP][0] |
1594 continue |
1603 continue |
1595 # If there are gets and they all agree [how could they not?], do it. |
1604 # If there are gets and they all agree [how could they not?], do it. |
1596 if ACTION_GET in bids: |
1605 if ACTION_GET in bids: |
1597 ga0 = bids[ACTION_GET][0] |
1606 ga0 = bids[ACTION_GET][0] |
1598 if all(a == ga0 for a in bids[ACTION_GET][1:]): |
1607 if all(a == ga0 for a in bids[ACTION_GET][1:]): |
1599 repo.ui.note(_(" %s: picking 'get' action\n") % f) |
1608 repo.ui.note(_(b" %s: picking 'get' action\n") % f) |
1600 actions[f] = ga0 |
1609 actions[f] = ga0 |
1601 continue |
1610 continue |
1602 # TODO: Consider other simple actions such as mode changes |
1611 # TODO: Consider other simple actions such as mode changes |
1603 # Handle inefficient democrazy. |
1612 # Handle inefficient democrazy. |
1604 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f) |
1613 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f) |
1605 for m, l in sorted(bids.items()): |
1614 for m, l in sorted(bids.items()): |
1606 for _f, args, msg in l: |
1615 for _f, args, msg in l: |
1607 repo.ui.note(' %s -> %s\n' % (msg, m)) |
1616 repo.ui.note(b' %s -> %s\n' % (msg, m)) |
1608 # Pick random action. TODO: Instead, prompt user when resolving |
1617 # Pick random action. TODO: Instead, prompt user when resolving |
1609 m, l = list(bids.items())[0] |
1618 m, l = list(bids.items())[0] |
1610 repo.ui.warn( |
1619 repo.ui.warn( |
1611 _(' %s: ambiguous merge - picked %s action\n') % (f, m) |
1620 _(b' %s: ambiguous merge - picked %s action\n') % (f, m) |
1612 ) |
1621 ) |
1613 actions[f] = l[0] |
1622 actions[f] = l[0] |
1614 continue |
1623 continue |
1615 repo.ui.note(_('end of auction\n\n')) |
1624 repo.ui.note(_(b'end of auction\n\n')) |
1616 |
1625 |
1617 if wctx.rev() is None: |
1626 if wctx.rev() is None: |
1618 fractions = _forgetremoved(wctx, mctx, branchmerge) |
1627 fractions = _forgetremoved(wctx, mctx, branchmerge) |
1619 actions.update(fractions) |
1628 actions.update(fractions) |
1620 |
1629 |
1844 moves.append(f1) |
1853 moves.append(f1) |
1845 |
1854 |
1846 # remove renamed files after safely stored |
1855 # remove renamed files after safely stored |
1847 for f in moves: |
1856 for f in moves: |
1848 if wctx[f].lexists(): |
1857 if wctx[f].lexists(): |
1849 repo.ui.debug("removing %s\n" % f) |
1858 repo.ui.debug(b"removing %s\n" % f) |
1850 wctx[f].audit() |
1859 wctx[f].audit() |
1851 wctx[f].remove() |
1860 wctx[f].remove() |
1852 |
1861 |
1853 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP) |
1862 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP) |
1854 progress = repo.ui.makeprogress( |
1863 progress = repo.ui.makeprogress( |
1855 _('updating'), unit=_('files'), total=numupdates |
1864 _(b'updating'), unit=_(b'files'), total=numupdates |
1856 ) |
1865 ) |
1857 |
1866 |
1858 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']: |
1867 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']: |
1859 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1868 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1860 |
1869 |
1861 # record path conflicts |
1870 # record path conflicts |
1862 for f, args, msg in actions[ACTION_PATH_CONFLICT]: |
1871 for f, args, msg in actions[ACTION_PATH_CONFLICT]: |
1863 f1, fo = args |
1872 f1, fo = args |
1864 s = repo.ui.status |
1873 s = repo.ui.status |
1865 s( |
1874 s( |
1866 _( |
1875 _( |
1867 "%s: path conflict - a file or link has the same name as a " |
1876 b"%s: path conflict - a file or link has the same name as a " |
1868 "directory\n" |
1877 b"directory\n" |
1869 ) |
1878 ) |
1870 % f |
1879 % f |
1871 ) |
1880 ) |
1872 if fo == 'l': |
1881 if fo == b'l': |
1873 s(_("the local file has been renamed to %s\n") % f1) |
1882 s(_(b"the local file has been renamed to %s\n") % f1) |
1874 else: |
1883 else: |
1875 s(_("the remote file has been renamed to %s\n") % f1) |
1884 s(_(b"the remote file has been renamed to %s\n") % f1) |
1876 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f) |
1885 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f) |
1877 ms.addpath(f, f1, fo) |
1886 ms.addpath(f, f1, fo) |
1878 progress.increment(item=f) |
1887 progress.increment(item=f) |
1879 |
1888 |
1880 # When merging in-memory, we can't support worker processes, so set the |
1889 # When merging in-memory, we can't support worker processes, so set the |
1881 # per-item cost at 0 in that case. |
1890 # per-item cost at 0 in that case. |
1920 else: |
1929 else: |
1921 i, item = res |
1930 i, item = res |
1922 progress.increment(step=i, item=item) |
1931 progress.increment(step=i, item=item) |
1923 updated = len(actions[ACTION_GET]) |
1932 updated = len(actions[ACTION_GET]) |
1924 |
1933 |
1925 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']: |
1934 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']: |
1926 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1935 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) |
1927 |
1936 |
1928 # forget (manifest only, just log it) (must come first) |
1937 # forget (manifest only, just log it) (must come first) |
1929 for f, args, msg in actions[ACTION_FORGET]: |
1938 for f, args, msg in actions[ACTION_FORGET]: |
1930 repo.ui.debug(" %s: %s -> f\n" % (f, msg)) |
1939 repo.ui.debug(b" %s: %s -> f\n" % (f, msg)) |
1931 progress.increment(item=f) |
1940 progress.increment(item=f) |
1932 |
1941 |
1933 # re-add (manifest only, just log it) |
1942 # re-add (manifest only, just log it) |
1934 for f, args, msg in actions[ACTION_ADD]: |
1943 for f, args, msg in actions[ACTION_ADD]: |
1935 repo.ui.debug(" %s: %s -> a\n" % (f, msg)) |
1944 repo.ui.debug(b" %s: %s -> a\n" % (f, msg)) |
1936 progress.increment(item=f) |
1945 progress.increment(item=f) |
1937 |
1946 |
1938 # re-add/mark as modified (manifest only, just log it) |
1947 # re-add/mark as modified (manifest only, just log it) |
1939 for f, args, msg in actions[ACTION_ADD_MODIFIED]: |
1948 for f, args, msg in actions[ACTION_ADD_MODIFIED]: |
1940 repo.ui.debug(" %s: %s -> am\n" % (f, msg)) |
1949 repo.ui.debug(b" %s: %s -> am\n" % (f, msg)) |
1941 progress.increment(item=f) |
1950 progress.increment(item=f) |
1942 |
1951 |
1943 # keep (noop, just log it) |
1952 # keep (noop, just log it) |
1944 for f, args, msg in actions[ACTION_KEEP]: |
1953 for f, args, msg in actions[ACTION_KEEP]: |
1945 repo.ui.debug(" %s: %s -> k\n" % (f, msg)) |
1954 repo.ui.debug(b" %s: %s -> k\n" % (f, msg)) |
1946 # no progress |
1955 # no progress |
1947 |
1956 |
1948 # directory rename, move local |
1957 # directory rename, move local |
1949 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: |
1958 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: |
1950 repo.ui.debug(" %s: %s -> dm\n" % (f, msg)) |
1959 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg)) |
1951 progress.increment(item=f) |
1960 progress.increment(item=f) |
1952 f0, flags = args |
1961 f0, flags = args |
1953 repo.ui.note(_("moving %s to %s\n") % (f0, f)) |
1962 repo.ui.note(_(b"moving %s to %s\n") % (f0, f)) |
1954 wctx[f].audit() |
1963 wctx[f].audit() |
1955 wctx[f].write(wctx.filectx(f0).data(), flags) |
1964 wctx[f].write(wctx.filectx(f0).data(), flags) |
1956 wctx[f0].remove() |
1965 wctx[f0].remove() |
1957 updated += 1 |
1966 updated += 1 |
1958 |
1967 |
1959 # local directory rename, get |
1968 # local directory rename, get |
1960 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: |
1969 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: |
1961 repo.ui.debug(" %s: %s -> dg\n" % (f, msg)) |
1970 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg)) |
1962 progress.increment(item=f) |
1971 progress.increment(item=f) |
1963 f0, flags = args |
1972 f0, flags = args |
1964 repo.ui.note(_("getting %s to %s\n") % (f0, f)) |
1973 repo.ui.note(_(b"getting %s to %s\n") % (f0, f)) |
1965 wctx[f].write(mctx.filectx(f0).data(), flags) |
1974 wctx[f].write(mctx.filectx(f0).data(), flags) |
1966 updated += 1 |
1975 updated += 1 |
1967 |
1976 |
1968 # exec |
1977 # exec |
1969 for f, args, msg in actions[ACTION_EXEC]: |
1978 for f, args, msg in actions[ACTION_EXEC]: |
1970 repo.ui.debug(" %s: %s -> e\n" % (f, msg)) |
1979 repo.ui.debug(b" %s: %s -> e\n" % (f, msg)) |
1971 progress.increment(item=f) |
1980 progress.increment(item=f) |
1972 (flags,) = args |
1981 (flags,) = args |
1973 wctx[f].audit() |
1982 wctx[f].audit() |
1974 wctx[f].setflags('l' in flags, 'x' in flags) |
1983 wctx[f].setflags(b'l' in flags, b'x' in flags) |
1975 updated += 1 |
1984 updated += 1 |
1976 |
1985 |
1977 # the ordering is important here -- ms.mergedriver will raise if the merge |
1986 # the ordering is important here -- ms.mergedriver will raise if the merge |
1978 # driver has changed, and we want to be able to bypass it when overwrite is |
1987 # driver has changed, and we want to be able to bypass it when overwrite is |
1979 # True |
1988 # True |
1980 usemergedriver = not overwrite and mergeactions and ms.mergedriver |
1989 usemergedriver = not overwrite and mergeactions and ms.mergedriver |
1981 |
1990 |
1982 if usemergedriver: |
1991 if usemergedriver: |
1983 if wctx.isinmemory(): |
1992 if wctx.isinmemory(): |
1984 raise error.InMemoryMergeConflictsError( |
1993 raise error.InMemoryMergeConflictsError( |
1985 "in-memory merge does not " "support mergedriver" |
1994 b"in-memory merge does not " b"support mergedriver" |
1986 ) |
1995 ) |
1987 ms.commit() |
1996 ms.commit() |
1988 proceed = driverpreprocess(repo, ms, wctx, labels=labels) |
1997 proceed = driverpreprocess(repo, ms, wctx, labels=labels) |
1989 # the driver might leave some files unresolved |
1998 # the driver might leave some files unresolved |
1990 unresolvedf = set(ms.unresolved()) |
1999 unresolvedf = set(ms.unresolved()) |
2303 |
2312 |
2304 overwrite = force and not branchmerge |
2313 overwrite = force and not branchmerge |
2305 ### check phase |
2314 ### check phase |
2306 if not overwrite: |
2315 if not overwrite: |
2307 if len(pl) > 1: |
2316 if len(pl) > 1: |
2308 raise error.Abort(_("outstanding uncommitted merge")) |
2317 raise error.Abort(_(b"outstanding uncommitted merge")) |
2309 ms = mergestate.read(repo) |
2318 ms = mergestate.read(repo) |
2310 if list(ms.unresolved()): |
2319 if list(ms.unresolved()): |
2311 raise error.Abort( |
2320 raise error.Abort( |
2312 _("outstanding merge conflicts"), |
2321 _(b"outstanding merge conflicts"), |
2313 hint=_("use 'hg resolve' to resolve"), |
2322 hint=_(b"use 'hg resolve' to resolve"), |
2314 ) |
2323 ) |
2315 if branchmerge: |
2324 if branchmerge: |
2316 if pas == [p2]: |
2325 if pas == [p2]: |
2317 raise error.Abort( |
2326 raise error.Abort( |
2318 _( |
2327 _( |
2319 "merging with a working directory ancestor" |
2328 b"merging with a working directory ancestor" |
2320 " has no effect" |
2329 b" has no effect" |
2321 ) |
2330 ) |
2322 ) |
2331 ) |
2323 elif pas == [p1]: |
2332 elif pas == [p1]: |
2324 if not mergeancestor and wc.branch() == p2.branch(): |
2333 if not mergeancestor and wc.branch() == p2.branch(): |
2325 raise error.Abort( |
2334 raise error.Abort( |
2326 _("nothing to merge"), |
2335 _(b"nothing to merge"), |
2327 hint=_("use 'hg update' " "or check 'hg heads'"), |
2336 hint=_(b"use 'hg update' " b"or check 'hg heads'"), |
2328 ) |
2337 ) |
2329 if not force and (wc.files() or wc.deleted()): |
2338 if not force and (wc.files() or wc.deleted()): |
2330 raise error.Abort( |
2339 raise error.Abort( |
2331 _("uncommitted changes"), |
2340 _(b"uncommitted changes"), |
2332 hint=_("use 'hg status' to list changes"), |
2341 hint=_(b"use 'hg status' to list changes"), |
2333 ) |
2342 ) |
2334 if not wc.isinmemory(): |
2343 if not wc.isinmemory(): |
2335 for s in sorted(wc.substate): |
2344 for s in sorted(wc.substate): |
2336 wc.sub(s).bailifchanged() |
2345 wc.sub(s).bailifchanged() |
2337 |
2346 |
2338 elif not overwrite: |
2347 elif not overwrite: |
2339 if p1 == p2: # no-op update |
2348 if p1 == p2: # no-op update |
2340 # call the hooks and exit early |
2349 # call the hooks and exit early |
2341 repo.hook('preupdate', throw=True, parent1=xp2, parent2='') |
2350 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'') |
2342 repo.hook('update', parent1=xp2, parent2='', error=0) |
2351 repo.hook(b'update', parent1=xp2, parent2=b'', error=0) |
2343 return updateresult(0, 0, 0, 0) |
2352 return updateresult(0, 0, 0, 0) |
2344 |
2353 |
2345 if updatecheck == UPDATECHECK_LINEAR and pas not in ( |
2354 if updatecheck == UPDATECHECK_LINEAR and pas not in ( |
2346 [p1], |
2355 [p1], |
2347 [p2], |
2356 [p2], |
2397 ACTION_KEEP, |
2406 ACTION_KEEP, |
2398 ACTION_EXEC, |
2407 ACTION_EXEC, |
2399 ACTION_REMOVE, |
2408 ACTION_REMOVE, |
2400 ACTION_PATH_CONFLICT_RESOLVE, |
2409 ACTION_PATH_CONFLICT_RESOLVE, |
2401 ): |
2410 ): |
2402 msg = _("conflicting changes") |
2411 msg = _(b"conflicting changes") |
2403 hint = _("commit or update --clean to discard changes") |
2412 hint = _(b"commit or update --clean to discard changes") |
2404 raise error.Abort(msg, hint=hint) |
2413 raise error.Abort(msg, hint=hint) |
2405 |
2414 |
2406 # Prompt and create actions. Most of this is in the resolve phase |
2415 # Prompt and create actions. Most of this is in the resolve phase |
2407 # already, but we can't handle .hgsubstate in filemerge or |
2416 # already, but we can't handle .hgsubstate in filemerge or |
2408 # subrepoutil.submerge yet so we have to keep prompting for it. |
2417 # subrepoutil.submerge yet so we have to keep prompting for it. |
2409 if '.hgsubstate' in actionbyfile: |
2418 if b'.hgsubstate' in actionbyfile: |
2410 f = '.hgsubstate' |
2419 f = b'.hgsubstate' |
2411 m, args, msg = actionbyfile[f] |
2420 m, args, msg = actionbyfile[f] |
2412 prompts = filemerge.partextras(labels) |
2421 prompts = filemerge.partextras(labels) |
2413 prompts['f'] = f |
2422 prompts[b'f'] = f |
2414 if m == ACTION_CHANGED_DELETED: |
2423 if m == ACTION_CHANGED_DELETED: |
2415 if repo.ui.promptchoice( |
2424 if repo.ui.promptchoice( |
2416 _( |
2425 _( |
2417 "local%(l)s changed %(f)s which other%(o)s deleted\n" |
2426 b"local%(l)s changed %(f)s which other%(o)s deleted\n" |
2418 "use (c)hanged version or (d)elete?" |
2427 b"use (c)hanged version or (d)elete?" |
2419 "$$ &Changed $$ &Delete" |
2428 b"$$ &Changed $$ &Delete" |
2420 ) |
2429 ) |
2421 % prompts, |
2430 % prompts, |
2422 0, |
2431 0, |
2423 ): |
2432 ): |
2424 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete') |
2433 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete') |
2425 elif f in p1: |
2434 elif f in p1: |
2426 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep') |
2435 actionbyfile[f] = ( |
|
2436 ACTION_ADD_MODIFIED, |
|
2437 None, |
|
2438 b'prompt keep', |
|
2439 ) |
2427 else: |
2440 else: |
2428 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep') |
2441 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep') |
2429 elif m == ACTION_DELETED_CHANGED: |
2442 elif m == ACTION_DELETED_CHANGED: |
2430 f1, f2, fa, move, anc = args |
2443 f1, f2, fa, move, anc = args |
2431 flags = p2[f2].flags() |
2444 flags = p2[f2].flags() |
2432 if ( |
2445 if ( |
2433 repo.ui.promptchoice( |
2446 repo.ui.promptchoice( |
2434 _( |
2447 _( |
2435 "other%(o)s changed %(f)s which local%(l)s deleted\n" |
2448 b"other%(o)s changed %(f)s which local%(l)s deleted\n" |
2436 "use (c)hanged version or leave (d)eleted?" |
2449 b"use (c)hanged version or leave (d)eleted?" |
2437 "$$ &Changed $$ &Deleted" |
2450 b"$$ &Changed $$ &Deleted" |
2438 ) |
2451 ) |
2439 % prompts, |
2452 % prompts, |
2440 0, |
2453 0, |
2441 ) |
2454 ) |
2442 == 0 |
2455 == 0 |
2443 ): |
2456 ): |
2444 actionbyfile[f] = ( |
2457 actionbyfile[f] = ( |
2445 ACTION_GET, |
2458 ACTION_GET, |
2446 (flags, False), |
2459 (flags, False), |
2447 'prompt recreating', |
2460 b'prompt recreating', |
2448 ) |
2461 ) |
2449 else: |
2462 else: |
2450 del actionbyfile[f] |
2463 del actionbyfile[f] |
2451 |
2464 |
2452 # Convert to dictionary-of-lists format |
2465 # Convert to dictionary-of-lists format |
2467 |
2480 |
2468 # divergent renames |
2481 # divergent renames |
2469 for f, fl in sorted(diverge.iteritems()): |
2482 for f, fl in sorted(diverge.iteritems()): |
2470 repo.ui.warn( |
2483 repo.ui.warn( |
2471 _( |
2484 _( |
2472 "note: possible conflict - %s was renamed " |
2485 b"note: possible conflict - %s was renamed " |
2473 "multiple times to:\n" |
2486 b"multiple times to:\n" |
2474 ) |
2487 ) |
2475 % f |
2488 % f |
2476 ) |
2489 ) |
2477 for nf in sorted(fl): |
2490 for nf in sorted(fl): |
2478 repo.ui.warn(" %s\n" % nf) |
2491 repo.ui.warn(b" %s\n" % nf) |
2479 |
2492 |
2480 # rename and delete |
2493 # rename and delete |
2481 for f, fl in sorted(renamedelete.iteritems()): |
2494 for f, fl in sorted(renamedelete.iteritems()): |
2482 repo.ui.warn( |
2495 repo.ui.warn( |
2483 _( |
2496 _( |
2484 "note: possible conflict - %s was deleted " |
2497 b"note: possible conflict - %s was deleted " |
2485 "and renamed to:\n" |
2498 b"and renamed to:\n" |
2486 ) |
2499 ) |
2487 % f |
2500 % f |
2488 ) |
2501 ) |
2489 for nf in sorted(fl): |
2502 for nf in sorted(fl): |
2490 repo.ui.warn(" %s\n" % nf) |
2503 repo.ui.warn(b" %s\n" % nf) |
2491 |
2504 |
2492 ### apply phase |
2505 ### apply phase |
2493 if not branchmerge: # just jump to the new rev |
2506 if not branchmerge: # just jump to the new rev |
2494 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' |
2507 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b'' |
2495 if not partial and not wc.isinmemory(): |
2508 if not partial and not wc.isinmemory(): |
2496 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) |
2509 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2) |
2497 # note that we're in the middle of an update |
2510 # note that we're in the middle of an update |
2498 repo.vfs.write('updatestate', p2.hex()) |
2511 repo.vfs.write(b'updatestate', p2.hex()) |
2499 |
2512 |
2500 # Advertise fsmonitor when its presence could be useful. |
2513 # Advertise fsmonitor when its presence could be useful. |
2501 # |
2514 # |
2502 # We only advertise when performing an update from an empty working |
2515 # We only advertise when performing an update from an empty working |
2503 # directory. This typically only occurs during initial clone. |
2516 # directory. This typically only occurs during initial clone. |
2505 # We give users a mechanism to disable the warning in case it is |
2518 # We give users a mechanism to disable the warning in case it is |
2506 # annoying. |
2519 # annoying. |
2507 # |
2520 # |
2508 # We only allow on Linux and MacOS because that's where fsmonitor is |
2521 # We only allow on Linux and MacOS because that's where fsmonitor is |
2509 # considered stable. |
2522 # considered stable. |
2510 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused') |
2523 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused') |
2511 fsmonitorthreshold = repo.ui.configint( |
2524 fsmonitorthreshold = repo.ui.configint( |
2512 'fsmonitor', 'warn_update_file_count' |
2525 b'fsmonitor', b'warn_update_file_count' |
2513 ) |
2526 ) |
2514 try: |
2527 try: |
2515 # avoid cycle: extensions -> cmdutil -> merge |
2528 # avoid cycle: extensions -> cmdutil -> merge |
2516 from . import extensions |
2529 from . import extensions |
2517 |
2530 |
2518 extensions.find('fsmonitor') |
2531 extensions.find(b'fsmonitor') |
2519 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off' |
2532 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off' |
2520 # We intentionally don't look at whether fsmonitor has disabled |
2533 # We intentionally don't look at whether fsmonitor has disabled |
2521 # itself because a) fsmonitor may have already printed a warning |
2534 # itself because a) fsmonitor may have already printed a warning |
2522 # b) we only care about the config state here. |
2535 # b) we only care about the config state here. |
2523 except KeyError: |
2536 except KeyError: |
2524 fsmonitorenabled = False |
2537 fsmonitorenabled = False |