mercurial/merge.py
changeset 52697 f3762eafed66
parent 52692 45dc0f874b8c
child 52974 ea9846b8e539
equal deleted inserted replaced
52696:10e7adbffa8c 52697:f3762eafed66
     9 
     9 
    10 import collections
    10 import collections
    11 import os
    11 import os
    12 import struct
    12 import struct
    13 import typing
    13 import typing
    14 from typing import Dict, Iterator, Optional, Tuple
    14 from typing import Dict, Iterable, Iterator, Optional, Tuple
    15 
    15 
    16 from .i18n import _
    16 from .i18n import _
    17 from .node import nullrev
    17 from .node import nullrev
    18 from .thirdparty import attr
    18 from .thirdparty import attr
    19 
    19 
    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
   741     force,
   791     force,
   742     matcher,
   792     matcher,
   743     acceptremote,
   793     acceptremote,
   744     followcopies,
   794     followcopies,
   745     forcefulldiff=False,
   795     forcefulldiff=False,
   746 ):
   796 ) -> mergeresult:
   747     """
   797     """
   748     Merge wctx and p2 with ancestor pa and generate merge action list
   798     Merge wctx and p2 with ancestor pa and generate merge action list
   749 
   799 
   750     branchmerge and force are as passed in to update
   800     branchmerge and force are as passed in to update
   751     matcher = matcher to filter file lists
   801     matcher = matcher to filter file lists
  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,))):
  1144     force,
  1194     force,
  1145     acceptremote,
  1195     acceptremote,
  1146     followcopies,
  1196     followcopies,
  1147     matcher=None,
  1197     matcher=None,
  1148     mergeforce=False,
  1198     mergeforce=False,
  1149 ):
  1199 ) -> mergeresult:
  1150     """
  1200     """
  1151     Calculate the actions needed to merge mctx into wctx using ancestors
  1201     Calculate the actions needed to merge mctx into wctx using ancestors
  1152 
  1202 
  1153     Uses manifestmerge() to merge manifest and get list of actions required to
  1203     Uses manifestmerge() to merge manifest and get list of actions required to
  1154     perform for merging two manifests. If there are multiple ancestors, uses bid
  1204     perform for merging two manifests. If there are multiple ancestors, uses bid
  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
  1514         )
  1564         )
  1515 
  1565 
  1516 
  1566 
  1517 def applyupdates(
  1567 def applyupdates(
  1518     repo,
  1568     repo,
  1519     mresult,
  1569     mresult: mergeresult,
  1520     wctx,
  1570     wctx,
  1521     mctx,
  1571     mctx,
  1522     overwrite,
  1572     overwrite,
  1523     wantfiledata,
  1573     wantfiledata,
  1524     labels=None,
  1574     labels=None,