# HG changeset patch # User Matt Harbison <matt_harbison@yahoo.com> # Date 1736789073 18000 # Mon Jan 13 12:24:33 2025 -0500 # Node ID f3762eafed66e8d202aecaba4d77c1f74cf55bd7 # Parent 10e7adbffa8c800c80ca1514095ad0e54e192afb typing: add some type annotations to the `merge.mergeresult` class The generated type annotations around `filemap()` and `files()` were slightly modified by the pyupgrade series culminating in 70a75d379daf. This module is way more complicated than the other changes, but these weren't too bad to figure out. The typing caught a trivial issue in `sparse`- it was passing an empty data list to `addfile()` for the `ACTION_REMOVE` case, instead of a tuple or None. `merge.manifestmerge()` calls this function with None for the data, so 1) it has to be typed as optional, and 2) is safe to pass None in the sparse code. diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -11,7 +11,7 @@ import os import struct import typing -from typing import Dict, Iterator, Optional, Tuple +from typing import Dict, Iterable, Iterator, Optional, Tuple from .i18n import _ from .node import nullrev @@ -41,6 +41,18 @@ worker, ) +if typing.TYPE_CHECKING: + # TODO: figure out what exactly is in this tuple + MergeResultData = tuple + MergeResultAction = tuple[bytes, Optional[MergeResultData], bytes] + """The filename, data about the merge, and message about the merge.""" + + FileMappingValue = tuple[ + mergestatemod.MergeAction, Optional[MergeResultData], bytes + ] + """The merge action, data about the merge, and message about the merge, for + the keyed file.""" + rust_update_mod = policy.importrust("update") _pack = struct.pack @@ -132,7 +144,9 @@ return None -def _checkunknownfiles(repo, wctx, mctx, force, mresult, mergeforce): +def _checkunknownfiles( + repo, wctx, mctx, force, mresult: mergeresult, mergeforce +): """ Considers any actions that care about the presence of conflicting unknown files. For some actions, the result is to abort; for others, it is to @@ -277,7 +291,7 @@ ) -def _forgetremoved(wctx, mctx, branchmerge, mresult): +def _forgetremoved(wctx, mctx, branchmerge, mresult: mergeresult) -> None: """ Forget removed files @@ -310,7 +324,7 @@ ) -def _checkcollision(repo, wmf, mresult): +def _checkcollision(repo, wmf, mresult: mergeresult | None) -> None: """ Check for case-folding collisions. """ @@ -378,7 +392,7 @@ lastfull = f -def _filesindirs(repo, manifest, dirs): +def _filesindirs(repo, manifest, dirs) -> Iterator[tuple[bytes, bytes]]: """ Generator that yields pairs of all the files in the manifest that are found inside the directories listed in dirs, and which directory they are found @@ -391,7 +405,7 @@ break -def checkpathconflicts(repo, wctx, mctx, mresult): +def checkpathconflicts(repo, wctx, mctx, mresult: mergeresult) -> None: """ Check if any actions introduce path conflicts in the repository, updating actions to record or handle the path conflict accordingly. @@ -492,7 +506,13 @@ ctxname = bytes(mctx).rstrip(b'+') for f, p in _filesindirs(repo, mf, remoteconflicts): if f not in deletedfiles: - m, args, msg = mresult.getfile(p) + mapping_value = mresult.getfile(p) + + # Help pytype- in theory, this could be None since no default + # value is passed to getfile() above. + assert mapping_value is not None + + m, args, msg = mapping_value pnew = util.safename(p, ctxname, wctx, set(mresult.files())) if m in ( mergestatemod.ACTION_DELETED_CHANGED, @@ -526,7 +546,9 @@ ) -def _filternarrowactions(narrowmatch, branchmerge, mresult): +def _filternarrowactions( + narrowmatch, branchmerge, mresult: mergeresult +) -> None: """ Filters out actions that can ignored because the repo is narrowed. @@ -567,7 +589,12 @@ It has information about what actions need to be performed on dirstate mapping of divergent renames and other such cases.""" - def __init__(self): + _filemapping: dict[bytes, FileMappingValue] + _actionmapping: dict[ + mergestatemod.MergeAction, dict[bytes, tuple[MergeResultData, bytes]] + ] + + def __init__(self) -> None: """ filemapping: dict of filename as keys and action related info as values diverge: mapping of source name -> list of dest name for @@ -589,7 +616,13 @@ self._diverge = diverge self._renamedelete = renamedelete - def addfile(self, filename, action, data, message): + def addfile( + self, + filename: bytes, + action: mergestatemod.MergeAction, + data: MergeResultData | None, + message, + ) -> None: """adds a new file to the mergeresult object filename: file which we are adding @@ -606,7 +639,12 @@ self._filemapping[filename] = (action, data, message) self._actionmapping[action][filename] = (data, message) - def mapaction(self, actionfrom, actionto, transform): + def mapaction( + self, + actionfrom: mergestatemod.MergeAction, + actionto: mergestatemod.MergeAction, + transform, + ): """changes all occurrences of action `actionfrom` into `actionto`, transforming its args with the function `transform`. """ @@ -618,7 +656,9 @@ self._filemapping[f] = (actionto, data, msg) dest[f] = (data, msg) - def getfile(self, filename, default_return=None): + def getfile( + self, filename: bytes, default_return: FileMappingValue | None = None + ) -> FileMappingValue | None: """returns (action, args, msg) about this file returns default_return if the file is not present""" @@ -626,7 +666,9 @@ return self._filemapping[filename] return default_return - def files(self, actions=None): + def files( + self, actions: Iterable[mergestatemod.MergeAction] | None = None + ) -> Iterator[bytes]: """returns files on which provided action needs to perfromed If actions is None, all files are returned @@ -640,14 +682,16 @@ for a in actions: yield from self._actionmapping[a] - def removefile(self, filename): + def removefile(self, filename: bytes) -> None: """removes a file from the mergeresult object as the file might not merging anymore""" action, data, message = self._filemapping[filename] del self._filemapping[filename] del self._actionmapping[action][filename] - def getactions(self, actions, sort=False): + def getactions( + self, actions: Iterable[mergestatemod.MergeAction], sort: bool = False + ) -> Iterator[MergeResultAction]: """get list of files which are marked with these actions if sort is true, files for each action is sorted and then added @@ -662,7 +706,9 @@ for f, (args, msg) in self._actionmapping[a].items(): yield f, args, msg - def len(self, actions=None): + def len( + self, actions: Iterable[mergestatemod.MergeAction] | None = None + ) -> int: """returns number of files which needs actions if actions is passed, total of number of files in that action @@ -673,13 +719,15 @@ return sum(len(self._actionmapping[a]) for a in actions) - def filemap(self, sort=False) -> Iterator[tuple]: # TODO: fill out tuple + def filemap( + self, sort: bool = False + ) -> Iterator[tuple[bytes, MergeResultData]]: if sort: yield from sorted(self._filemapping.items()) else: yield from self._filemapping.items() - def addcommitinfo(self, filename, key, value): + def addcommitinfo(self, filename: bytes, key, value) -> None: """adds key-value information about filename which will be required while committing this merge""" self._commitinfo[filename][key] = value @@ -697,7 +745,9 @@ return self._commitinfo @property - def actionsdict(self): + def actionsdict( + self, + ) -> dict[mergestatemod.MergeAction, list[MergeResultAction]]: """returns a dictionary of actions to be perfomed with action as key and a list of files and related arguments as values""" res = collections.defaultdict(list) @@ -706,13 +756,13 @@ res[a].append((f, args, msg)) return res - def setactions(self, actions): + def setactions(self, actions) -> None: self._filemapping = actions self._actionmapping = collections.defaultdict(dict) for f, (act, data, msg) in self._filemapping.items(): self._actionmapping[act][f] = data, msg - def hasconflicts(self): + def hasconflicts(self) -> bool: """tells whether this merge resulted in some actions which can result in conflicts or not""" for a in self._actionmapping.keys(): @@ -743,7 +793,7 @@ acceptremote, followcopies, forcefulldiff=False, -): +) -> mergeresult: """ Merge wctx and p2 with ancestor pa and generate merge action list @@ -1117,7 +1167,7 @@ return mresult -def _resolvetrivial(repo, wctx, mctx, ancestor, mresult): +def _resolvetrivial(repo, wctx, mctx, ancestor, mresult: mergeresult) -> None: """Resolves false conflicts where the nodeid changed but the content remained the same.""" # We force a copy of actions.items() because we're going to mutate @@ -1146,7 +1196,7 @@ followcopies, matcher=None, mergeforce=False, -): +) -> mergeresult: """ Calculate the actions needed to merge mctx into wctx using ancestors @@ -1469,7 +1519,7 @@ yield True, filedata -def _prefetchfiles(repo, ctx, mresult): +def _prefetchfiles(repo, ctx, mresult: mergeresult) -> None: """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict of merge actions. ``ctx`` is the context being merged in.""" @@ -1516,7 +1566,7 @@ def applyupdates( repo, - mresult, + mresult: mergeresult, wctx, mctx, overwrite, diff --git a/mercurial/sparse.py b/mercurial/sparse.py --- a/mercurial/sparse.py +++ b/mercurial/sparse.py @@ -548,7 +548,7 @@ elif (old and not new) or (not old and not new and file in dirstate): dropped.append(file) if file not in pending: - mresult.addfile(file, mergestatemod.ACTION_REMOVE, [], b'') + mresult.addfile(file, mergestatemod.ACTION_REMOVE, None, b'') # Verify there are no pending changes in newly included files abort = False