39 subrepoutil, |
39 subrepoutil, |
40 util, |
40 util, |
41 worker, |
41 worker, |
42 ) |
42 ) |
43 |
43 |
|
44 if typing.TYPE_CHECKING: |
|
45 # TODO: figure out what exactly is in this tuple |
|
46 MergeResultData = tuple |
|
47 MergeResultAction = tuple[bytes, Optional[MergeResultData], bytes] |
|
48 """The filename, data about the merge, and message about the merge.""" |
|
49 |
|
50 FileMappingValue = tuple[ |
|
51 mergestatemod.MergeAction, Optional[MergeResultData], bytes |
|
52 ] |
|
53 """The merge action, data about the merge, and message about the merge, for |
|
54 the keyed file.""" |
|
55 |
44 rust_update_mod = policy.importrust("update") |
56 rust_update_mod = policy.importrust("update") |
45 |
57 |
46 _pack = struct.pack |
58 _pack = struct.pack |
47 _unpack = struct.unpack |
59 _unpack = struct.unpack |
48 |
60 |
130 if relf not in repo.dirstate: |
142 if relf not in repo.dirstate: |
131 return f |
143 return f |
132 return None |
144 return None |
133 |
145 |
134 |
146 |
135 def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce): |
147 def _checkunknownfiles( |
|
148 repo, wctx, mctx, force, mresult: mergeresult, mergeforce |
|
149 ): |
136 """ |
150 """ |
137 Considers any actions that care about the presence of conflicting unknown |
151 Considers any actions that care about the presence of conflicting unknown |
138 files. For some actions, the result is to abort; for others, it is to |
152 files. For some actions, the result is to abort; for others, it is to |
139 choose a different action. |
153 choose a different action. |
140 """ |
154 """ |
275 mresult.mapaction( |
289 mresult.mapaction( |
276 mergestatemod.ACTION_CREATED, mergestatemod.ACTION_GET, transformargs |
290 mergestatemod.ACTION_CREATED, mergestatemod.ACTION_GET, transformargs |
277 ) |
291 ) |
278 |
292 |
279 |
293 |
280 def _forgetremoved(wctx, mctx, branchmerge, mresult): |
294 def _forgetremoved(wctx, mctx, branchmerge, mresult: mergeresult) -> None: |
281 """ |
295 """ |
282 Forget removed files |
296 Forget removed files |
283 |
297 |
284 If we're jumping between revisions (as opposed to merging), and if |
298 If we're jumping between revisions (as opposed to merging), and if |
285 neither the working directory nor the target rev has the file, |
299 neither the working directory nor the target rev has the file, |
308 None, |
322 None, |
309 b"forget removed", |
323 b"forget removed", |
310 ) |
324 ) |
311 |
325 |
312 |
326 |
313 def _checkcollision(repo, wmf, mresult): |
327 def _checkcollision(repo, wmf, mresult: mergeresult | None) -> None: |
314 """ |
328 """ |
315 Check for case-folding collisions. |
329 Check for case-folding collisions. |
316 """ |
330 """ |
317 # If the repo is narrowed, filter out files outside the narrowspec. |
331 # If the repo is narrowed, filter out files outside the narrowspec. |
318 narrowmatch = repo.narrowmatch() |
332 narrowmatch = repo.narrowmatch() |
376 foldprefix = fold + b'/' |
390 foldprefix = fold + b'/' |
377 unfoldprefix = f + b'/' |
391 unfoldprefix = f + b'/' |
378 lastfull = f |
392 lastfull = f |
379 |
393 |
380 |
394 |
381 def _filesindirs(repo, manifest, dirs): |
395 def _filesindirs(repo, manifest, dirs) -> Iterator[tuple[bytes, bytes]]: |
382 """ |
396 """ |
383 Generator that yields pairs of all the files in the manifest that are found |
397 Generator that yields pairs of all the files in the manifest that are found |
384 inside the directories listed in dirs, and which directory they are found |
398 inside the directories listed in dirs, and which directory they are found |
385 in. |
399 in. |
386 """ |
400 """ |
389 if p in dirs: |
403 if p in dirs: |
390 yield f, p |
404 yield f, p |
391 break |
405 break |
392 |
406 |
393 |
407 |
394 def checkpathconflicts(repo, wctx, mctx, mresult): |
408 def checkpathconflicts(repo, wctx, mctx, mresult: mergeresult) -> None: |
395 """ |
409 """ |
396 Check if any actions introduce path conflicts in the repository, updating |
410 Check if any actions introduce path conflicts in the repository, updating |
397 actions to record or handle the path conflict accordingly. |
411 actions to record or handle the path conflict accordingly. |
398 """ |
412 """ |
399 mf = wctx.manifest() |
413 mf = wctx.manifest() |
490 if remoteconflicts: |
504 if remoteconflicts: |
491 # Check if all files in the conflicting directories have been removed. |
505 # Check if all files in the conflicting directories have been removed. |
492 ctxname = bytes(mctx).rstrip(b'+') |
506 ctxname = bytes(mctx).rstrip(b'+') |
493 for f, p in _filesindirs(repo, mf, remoteconflicts): |
507 for f, p in _filesindirs(repo, mf, remoteconflicts): |
494 if f not in deletedfiles: |
508 if f not in deletedfiles: |
495 m, args, msg = mresult.getfile(p) |
509 mapping_value = mresult.getfile(p) |
|
510 |
|
511 # Help pytype- in theory, this could be None since no default |
|
512 # value is passed to getfile() above. |
|
513 assert mapping_value is not None |
|
514 |
|
515 m, args, msg = mapping_value |
496 pnew = util.safename(p, ctxname, wctx, set(mresult.files())) |
516 pnew = util.safename(p, ctxname, wctx, set(mresult.files())) |
497 if m in ( |
517 if m in ( |
498 mergestatemod.ACTION_DELETED_CHANGED, |
518 mergestatemod.ACTION_DELETED_CHANGED, |
499 mergestatemod.ACTION_MERGE, |
519 mergestatemod.ACTION_MERGE, |
500 ): |
520 ): |
524 raise error.StateError( |
544 raise error.StateError( |
525 _(b"destination manifest contains path conflicts") |
545 _(b"destination manifest contains path conflicts") |
526 ) |
546 ) |
527 |
547 |
528 |
548 |
529 def _filternarrowactions(narrowmatch, branchmerge, mresult): |
549 def _filternarrowactions( |
|
550 narrowmatch, branchmerge, mresult: mergeresult |
|
551 ) -> None: |
530 """ |
552 """ |
531 Filters out actions that can ignored because the repo is narrowed. |
553 Filters out actions that can ignored because the repo is narrowed. |
532 |
554 |
533 Raise an exception if the merge cannot be completed because the repo is |
555 Raise an exception if the merge cannot be completed because the repo is |
534 narrowed. |
556 narrowed. |
565 """An object representing result of merging manifests. |
587 """An object representing result of merging manifests. |
566 |
588 |
567 It has information about what actions need to be performed on dirstate |
589 It has information about what actions need to be performed on dirstate |
568 mapping of divergent renames and other such cases.""" |
590 mapping of divergent renames and other such cases.""" |
569 |
591 |
570 def __init__(self): |
592 _filemapping: dict[bytes, FileMappingValue] |
|
593 _actionmapping: dict[ |
|
594 mergestatemod.MergeAction, dict[bytes, tuple[MergeResultData, bytes]] |
|
595 ] |
|
596 |
|
597 def __init__(self) -> None: |
571 """ |
598 """ |
572 filemapping: dict of filename as keys and action related info as values |
599 filemapping: dict of filename as keys and action related info as values |
573 diverge: mapping of source name -> list of dest name for |
600 diverge: mapping of source name -> list of dest name for |
574 divergent renames |
601 divergent renames |
575 renamedelete: mapping of source name -> list of destinations for files |
602 renamedelete: mapping of source name -> list of destinations for files |
587 |
614 |
588 def updatevalues(self, diverge, renamedelete): |
615 def updatevalues(self, diverge, renamedelete): |
589 self._diverge = diverge |
616 self._diverge = diverge |
590 self._renamedelete = renamedelete |
617 self._renamedelete = renamedelete |
591 |
618 |
592 def addfile(self, filename, action, data, message): |
619 def addfile( |
|
620 self, |
|
621 filename: bytes, |
|
622 action: mergestatemod.MergeAction, |
|
623 data: MergeResultData | None, |
|
624 message, |
|
625 ) -> None: |
593 """adds a new file to the mergeresult object |
626 """adds a new file to the mergeresult object |
594 |
627 |
595 filename: file which we are adding |
628 filename: file which we are adding |
596 action: one of mergestatemod.ACTION_* |
629 action: one of mergestatemod.ACTION_* |
597 data: a tuple of information like fctx and ctx related to this merge |
630 data: a tuple of information like fctx and ctx related to this merge |
604 del self._actionmapping[a][filename] |
637 del self._actionmapping[a][filename] |
605 |
638 |
606 self._filemapping[filename] = (action, data, message) |
639 self._filemapping[filename] = (action, data, message) |
607 self._actionmapping[action][filename] = (data, message) |
640 self._actionmapping[action][filename] = (data, message) |
608 |
641 |
609 def mapaction(self, actionfrom, actionto, transform): |
642 def mapaction( |
|
643 self, |
|
644 actionfrom: mergestatemod.MergeAction, |
|
645 actionto: mergestatemod.MergeAction, |
|
646 transform, |
|
647 ): |
610 """changes all occurrences of action `actionfrom` into `actionto`, |
648 """changes all occurrences of action `actionfrom` into `actionto`, |
611 transforming its args with the function `transform`. |
649 transforming its args with the function `transform`. |
612 """ |
650 """ |
613 orig = self._actionmapping[actionfrom] |
651 orig = self._actionmapping[actionfrom] |
614 del self._actionmapping[actionfrom] |
652 del self._actionmapping[actionfrom] |
616 for f, (data, msg) in orig.items(): |
654 for f, (data, msg) in orig.items(): |
617 data = transform(f, data) |
655 data = transform(f, data) |
618 self._filemapping[f] = (actionto, data, msg) |
656 self._filemapping[f] = (actionto, data, msg) |
619 dest[f] = (data, msg) |
657 dest[f] = (data, msg) |
620 |
658 |
621 def getfile(self, filename, default_return=None): |
659 def getfile( |
|
660 self, filename: bytes, default_return: FileMappingValue | None = None |
|
661 ) -> FileMappingValue | None: |
622 """returns (action, args, msg) about this file |
662 """returns (action, args, msg) about this file |
623 |
663 |
624 returns default_return if the file is not present""" |
664 returns default_return if the file is not present""" |
625 if filename in self._filemapping: |
665 if filename in self._filemapping: |
626 return self._filemapping[filename] |
666 return self._filemapping[filename] |
627 return default_return |
667 return default_return |
628 |
668 |
629 def files(self, actions=None): |
669 def files( |
|
670 self, actions: Iterable[mergestatemod.MergeAction] | None = None |
|
671 ) -> Iterator[bytes]: |
630 """returns files on which provided action needs to perfromed |
672 """returns files on which provided action needs to perfromed |
631 |
673 |
632 If actions is None, all files are returned |
674 If actions is None, all files are returned |
633 """ |
675 """ |
634 # TODO: think whether we should return renamedelete and |
676 # TODO: think whether we should return renamedelete and |
638 |
680 |
639 else: |
681 else: |
640 for a in actions: |
682 for a in actions: |
641 yield from self._actionmapping[a] |
683 yield from self._actionmapping[a] |
642 |
684 |
643 def removefile(self, filename): |
685 def removefile(self, filename: bytes) -> None: |
644 """removes a file from the mergeresult object as the file might |
686 """removes a file from the mergeresult object as the file might |
645 not merging anymore""" |
687 not merging anymore""" |
646 action, data, message = self._filemapping[filename] |
688 action, data, message = self._filemapping[filename] |
647 del self._filemapping[filename] |
689 del self._filemapping[filename] |
648 del self._actionmapping[action][filename] |
690 del self._actionmapping[action][filename] |
649 |
691 |
650 def getactions(self, actions, sort=False): |
692 def getactions( |
|
693 self, actions: Iterable[mergestatemod.MergeAction], sort: bool = False |
|
694 ) -> Iterator[MergeResultAction]: |
651 """get list of files which are marked with these actions |
695 """get list of files which are marked with these actions |
652 if sort is true, files for each action is sorted and then added |
696 if sort is true, files for each action is sorted and then added |
653 |
697 |
654 Returns a list of tuple of form (filename, data, message) |
698 Returns a list of tuple of form (filename, data, message) |
655 """ |
699 """ |
660 yield f, args, msg |
704 yield f, args, msg |
661 else: |
705 else: |
662 for f, (args, msg) in self._actionmapping[a].items(): |
706 for f, (args, msg) in self._actionmapping[a].items(): |
663 yield f, args, msg |
707 yield f, args, msg |
664 |
708 |
665 def len(self, actions=None): |
709 def len( |
|
710 self, actions: Iterable[mergestatemod.MergeAction] | None = None |
|
711 ) -> int: |
666 """returns number of files which needs actions |
712 """returns number of files which needs actions |
667 |
713 |
668 if actions is passed, total of number of files in that action |
714 if actions is passed, total of number of files in that action |
669 only is returned""" |
715 only is returned""" |
670 |
716 |
671 if actions is None: |
717 if actions is None: |
672 return len(self._filemapping) |
718 return len(self._filemapping) |
673 |
719 |
674 return sum(len(self._actionmapping[a]) for a in actions) |
720 return sum(len(self._actionmapping[a]) for a in actions) |
675 |
721 |
676 def filemap(self, sort=False) -> Iterator[tuple]: # TODO: fill out tuple |
722 def filemap( |
|
723 self, sort: bool = False |
|
724 ) -> Iterator[tuple[bytes, MergeResultData]]: |
677 if sort: |
725 if sort: |
678 yield from sorted(self._filemapping.items()) |
726 yield from sorted(self._filemapping.items()) |
679 else: |
727 else: |
680 yield from self._filemapping.items() |
728 yield from self._filemapping.items() |
681 |
729 |
682 def addcommitinfo(self, filename, key, value): |
730 def addcommitinfo(self, filename: bytes, key, value) -> None: |
683 """adds key-value information about filename which will be required |
731 """adds key-value information about filename which will be required |
684 while committing this merge""" |
732 while committing this merge""" |
685 self._commitinfo[filename][key] = value |
733 self._commitinfo[filename][key] = value |
686 |
734 |
687 @property |
735 @property |
695 @property |
743 @property |
696 def commitinfo(self): |
744 def commitinfo(self): |
697 return self._commitinfo |
745 return self._commitinfo |
698 |
746 |
699 @property |
747 @property |
700 def actionsdict(self): |
748 def actionsdict( |
|
749 self, |
|
750 ) -> dict[mergestatemod.MergeAction, list[MergeResultAction]]: |
701 """returns a dictionary of actions to be perfomed with action as key |
751 """returns a dictionary of actions to be perfomed with action as key |
702 and a list of files and related arguments as values""" |
752 and a list of files and related arguments as values""" |
703 res = collections.defaultdict(list) |
753 res = collections.defaultdict(list) |
704 for a, d in self._actionmapping.items(): |
754 for a, d in self._actionmapping.items(): |
705 for f, (args, msg) in d.items(): |
755 for f, (args, msg) in d.items(): |
706 res[a].append((f, args, msg)) |
756 res[a].append((f, args, msg)) |
707 return res |
757 return res |
708 |
758 |
709 def setactions(self, actions): |
759 def setactions(self, actions) -> None: |
710 self._filemapping = actions |
760 self._filemapping = actions |
711 self._actionmapping = collections.defaultdict(dict) |
761 self._actionmapping = collections.defaultdict(dict) |
712 for f, (act, data, msg) in self._filemapping.items(): |
762 for f, (act, data, msg) in self._filemapping.items(): |
713 self._actionmapping[act][f] = data, msg |
763 self._actionmapping[act][f] = data, msg |
714 |
764 |
715 def hasconflicts(self): |
765 def hasconflicts(self) -> bool: |
716 """tells whether this merge resulted in some actions which can |
766 """tells whether this merge resulted in some actions which can |
717 result in conflicts or not""" |
767 result in conflicts or not""" |
718 for a in self._actionmapping.keys(): |
768 for a in self._actionmapping.keys(): |
719 if ( |
769 if ( |
720 a |
770 a |
1115 |
1165 |
1116 mresult.updatevalues(diverge, renamedelete) |
1166 mresult.updatevalues(diverge, renamedelete) |
1117 return mresult |
1167 return mresult |
1118 |
1168 |
1119 |
1169 |
1120 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult): |
1170 def _resolvetrivial(repo, wctx, mctx, ancestor, mresult: mergeresult) -> None: |
1121 """Resolves false conflicts where the nodeid changed but the content |
1171 """Resolves false conflicts where the nodeid changed but the content |
1122 remained the same.""" |
1172 remained the same.""" |
1123 # We force a copy of actions.items() because we're going to mutate |
1173 # We force a copy of actions.items() because we're going to mutate |
1124 # actions as we resolve trivial conflicts. |
1174 # actions as we resolve trivial conflicts. |
1125 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))): |
1175 for f in list(mresult.files((mergestatemod.ACTION_CHANGED_DELETED,))): |
1467 if i > 0: |
1517 if i > 0: |
1468 yield False, (i, f) |
1518 yield False, (i, f) |
1469 yield True, filedata |
1519 yield True, filedata |
1470 |
1520 |
1471 |
1521 |
1472 def _prefetchfiles(repo, ctx, mresult): |
1522 def _prefetchfiles(repo, ctx, mresult: mergeresult) -> None: |
1473 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict |
1523 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict |
1474 of merge actions. ``ctx`` is the context being merged in.""" |
1524 of merge actions. ``ctx`` is the context being merged in.""" |
1475 |
1525 |
1476 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they |
1526 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they |
1477 # don't touch the context to be merged in. 'cd' is skipped, because |
1527 # don't touch the context to be merged in. 'cd' is skipped, because |