dirstate.py
338 lines
| 10.5 KiB
| text/x-python
|
PythonLexer
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Matt Harbison
|
r53328 | import abc | ||
Augie Fackler
|
r43197 | import contextlib | ||
Matt Harbison
|
r52822 | import os | ||
Matt Harbison
|
r52816 | import typing | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52814 | from typing import ( | ||
Matt Harbison
|
r52822 | Any, | ||
Matt Harbison
|
r52816 | Callable, | ||
Matt Harbison
|
r52822 | Dict, | ||
Iterable, | ||||
Iterator, | ||||
List, | ||||
Optional, | ||||
Matt Harbison
|
r52814 | Protocol, | ||
Matt Harbison
|
r52822 | Tuple, | ||
Matt Harbison
|
r52814 | ) | ||
Matt Harbison
|
r52816 | if typing.TYPE_CHECKING: | ||
# Almost all mercurial modules are only imported in the type checking phase | ||||
# to avoid circular imports | ||||
from .. import ( | ||||
match as matchmod, | ||||
Matt Harbison
|
r52822 | transaction as txnmod, | ||
Matt Harbison
|
r52816 | ) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r53349 | from . import status as istatus | ||
Matt Harbison
|
r52822 | # TODO: finish adding type hints | ||
AddParentChangeCallbackT = Callable[ | ||||
["idirstate", Tuple[Any, Any], Tuple[Any, Any]], Any | ||||
] | ||||
"""The callback type for dirstate.addparentchangecallback().""" | ||||
# TODO: add a Protocol for dirstatemap.DirStateItem? (It is | ||||
# conditionalized with python or rust implementations. Also, | ||||
# git.dirstate needs to yield non-None from ``items()``.) | ||||
DirstateItemT = Any # dirstatemap.DirstateItem | ||||
IgnoreFileAndLineT = Tuple[Optional[bytes], int, bytes] | ||||
"""The return type of dirstate._ignorefileandline(), which holds | ||||
``(file, lineno, originalline)``. | ||||
""" | ||||
FlagFuncFallbackT = Callable[[], "FlagFuncReturnT"] | ||||
"""The type for the dirstate.flagfunc() fallback function.""" | ||||
FlagFuncReturnT = Callable[[bytes], bytes] | ||||
"""The return type of dirstate.flagfunc().""" | ||||
# TODO: verify and complete this- it came from a pytype *.pyi file | ||||
Matt Harbison
|
r53349 | StatusReturnT = Tuple[Any, istatus.Status, Any] | ||
Matt Harbison
|
r52822 | """The return type of dirstate.status().""" | ||
# TODO: probably doesn't belong here. | ||||
TransactionT = txnmod.transaction | ||||
"""The type for a transaction used with dirstate. | ||||
This is meant to help callers avoid having to remember to delay the import | ||||
of the transaction module. | ||||
""" | ||||
# TODO: The value can also be mercurial.osutil.stat | ||||
WalkReturnT = Dict[bytes, Optional[os.stat_result]] | ||||
"""The return type of dirstate.walk(). | ||||
The matched files are keyed in the dictionary, mapped to a stat-like object | ||||
if the file exists. | ||||
""" | ||||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52814 | class idirstate(Protocol): | ||
# TODO: convert these constructor args to fields? | ||||
# def __init__( | ||||
# self, | ||||
# opener, | ||||
# ui, | ||||
# root, | ||||
# validate, | ||||
# sparsematchfn, | ||||
# nodeconstants, | ||||
# use_dirstate_v2, | ||||
# use_tracked_hint=False, | ||||
# ): | ||||
# """Create a new dirstate object. | ||||
# | ||||
# opener is an open()-like callable that can be used to open the | ||||
# dirstate file; root is the root of the directory tracked by | ||||
# the dirstate. | ||||
# """ | ||||
Augie Fackler
|
r43197 | |||
Augie Fackler
|
r43199 | # TODO: all these private methods and attributes should be made | ||
# public or removed from the interface. | ||||
Matt Harbison
|
r52816 | |||
# TODO: decorate with `@rootcache(b'.hgignore')` like dirstate class? | ||||
Matt Harbison
|
r52821 | @property | ||
def _ignore(self) -> matchmod.basematcher: | ||||
"""Matcher for ignored files.""" | ||||
Matt Harbison
|
r52816 | |||
@property | ||||
def is_changing_any(self) -> bool: | ||||
r50918 | """True if any changes in progress.""" | |||
Matt Harbison
|
r52816 | |||
@property | ||||
def is_changing_parents(self) -> bool: | ||||
r50917 | """True if parents changes in progress.""" | |||
Matt Harbison
|
r52816 | |||
@property | ||||
def is_changing_files(self) -> bool: | ||||
r50921 | """True if file tracking changes in progress.""" | |||
Augie Fackler
|
r43199 | |||
Matt Harbison
|
r52822 | def _ignorefiles(self) -> List[bytes]: | ||
Augie Fackler
|
r43199 | """Return a list of files containing patterns to ignore.""" | ||
Matt Harbison
|
r52822 | def _ignorefileandline(self, f: bytes) -> IgnoreFileAndLineT: | ||
Matt Harbison
|
r44226 | """Given a file `f`, return the ignore file and line that ignores it.""" | ||
Augie Fackler
|
r43199 | |||
Matt Harbison
|
r52816 | # TODO: decorate with `@util.propertycache` like dirstate class? | ||
# (can't because circular import) | ||||
Matt Harbison
|
r52821 | @property | ||
def _checklink(self) -> bool: | ||||
"""Callable for checking symlinks.""" # TODO: this comment looks stale | ||||
Matt Harbison
|
r52816 | |||
# TODO: decorate with `@util.propertycache` like dirstate class? | ||||
# (can't because circular import) | ||||
Matt Harbison
|
r52821 | @property | ||
def _checkexec(self) -> bool: | ||||
"""Callable for checking exec bits.""" # TODO: this comment looks stale | ||||
Augie Fackler
|
r43199 | |||
Augie Fackler
|
r43197 | @contextlib.contextmanager | ||
Matt Harbison
|
r53328 | @abc.abstractmethod | ||
Matt Harbison
|
r52822 | def changing_parents(self, repo) -> Iterator: # TODO: typehint this | ||
Augie Fackler
|
r46554 | """Context manager for handling dirstate parents. | ||
Augie Fackler
|
r43197 | |||
If an exception occurs in the scope of the context manager, | ||||
the incoherent dirstate won't be written when wlock is | ||||
released. | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
r50921 | @contextlib.contextmanager | |||
Matt Harbison
|
r53328 | @abc.abstractmethod | ||
Matt Harbison
|
r52822 | def changing_files(self, repo) -> Iterator: # TODO: typehint this | ||
r50921 | """Context manager for handling dirstate files. | |||
If an exception occurs in the scope of the context manager, | ||||
the incoherent dirstate won't be written when wlock is | ||||
released. | ||||
""" | ||||
Matt Harbison
|
r52822 | def hasdir(self, d: bytes) -> bool: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def flagfunc(self, buildfallback: FlagFuncFallbackT) -> FlagFuncReturnT: | ||
r50775 | """build a callable that returns flags associated with a filename | |||
The information is extracted from three possible layers: | ||||
1. the file system if it supports the information | ||||
2. the "fallback" information stored in the dirstate if any | ||||
3. a more expensive mechanism inferring the flags from the parents. | ||||
""" | ||||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | def getcwd(self) -> bytes: | ||
Augie Fackler
|
r46554 | """Return the path from which a canonical path is calculated. | ||
Augie Fackler
|
r43197 | |||
This path should be used to resolve file patterns or to convert | ||||
canonical paths back to file paths for display. It shouldn't be | ||||
used to get real file paths. Use vfs functions instead. | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes: | ||
r50776 | pass | |||
Matt Harbison
|
r52822 | def get_entry(self, path: bytes) -> DirstateItemT: | ||
Matt Harbison
|
r49967 | """return a DirstateItem for the associated path""" | ||
Matt Harbison
|
r52822 | def __contains__(self, key: Any) -> bool: | ||
Augie Fackler
|
r43197 | """Check if bytestring `key` is known to the dirstate.""" | ||
Matt Harbison
|
r52822 | def __iter__(self) -> Iterator[bytes]: | ||
Augie Fackler
|
r43197 | """Iterate the dirstate's contained filenames as bytestrings.""" | ||
Matt Harbison
|
r52822 | def items(self) -> Iterator[Tuple[bytes, DirstateItemT]]: | ||
r48328 | """Iterate the dirstate's entries as (filename, DirstateItem. | |||
Augie Fackler
|
r43197 | |||
As usual, filename is a bytestring. | ||||
""" | ||||
iteritems = items | ||||
Matt Harbison
|
r52822 | def parents(self) -> List[bytes]: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def p1(self) -> bytes: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def p2(self) -> bytes: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def branch(self) -> bytes: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | # TODO: typehint the return. It's a copies Map of some sort. | ||
def setparents(self, p1: bytes, p2: Optional[bytes] = None): | ||||
Augie Fackler
|
r43197 | """Set dirstate parents to p1 and p2. | ||
r50775 | When moving from two parents to one, "merged" entries a | |||
Augie Fackler
|
r43197 | adjusted to normal and previous copy records discarded and | ||
returned by the call. | ||||
See localrepo.setparents() | ||||
""" | ||||
Matt Harbison
|
r52822 | def setbranch( | ||
self, branch: bytes, transaction: Optional[TransactionT] | ||||
) -> None: | ||||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def invalidate(self) -> None: | ||
Augie Fackler
|
r46554 | """Causes the next access to reread the dirstate. | ||
Augie Fackler
|
r43197 | |||
This is different from localrepo.invalidatedirstate() because it always | ||||
rereads the dirstate. Use localrepo.invalidatedirstate() if you want to | ||||
Augie Fackler
|
r46554 | check whether the dirstate has changed before rereading it.""" | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | def copy(self, source: Optional[bytes], dest: bytes) -> None: | ||
Augie Fackler
|
r43197 | """Mark dest as a copy of source. Unmark dest if source is None.""" | ||
Matt Harbison
|
r52822 | def copied(self, file: bytes) -> Optional[bytes]: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def copies(self) -> Dict[bytes, bytes]: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def normalize( | ||
self, path: bytes, isknown: bool = False, ignoremissing: bool = False | ||||
) -> bytes: | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | normalize the case of a pathname when on a casefolding filesystem | ||
isknown specifies whether the filename came from walking the | ||||
disk, to avoid extra filesystem access. | ||||
If ignoremissing is True, missing path are returned | ||||
unchanged. Otherwise, we try harder to normalize possibly | ||||
existing path components. | ||||
The normalized case is determined based on the following precedence: | ||||
- version of name already stored in the dirstate | ||||
- version of name stored on disk | ||||
- version provided via command arguments | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | def clear(self) -> None: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def rebuild( | ||
self, | ||||
parent: bytes, | ||||
allfiles: Iterable[bytes], # TODO: more than iterable? (uses len()) | ||||
changedfiles: Optional[Iterable[bytes]] = None, | ||||
) -> None: | ||||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def write(self, tr: Optional[TransactionT]) -> None: | ||
Augie Fackler
|
r43197 | pass | ||
Matt Harbison
|
r52822 | def addparentchangecallback( | ||
self, category: bytes, callback: AddParentChangeCallbackT | ||||
) -> None: | ||||
Augie Fackler
|
r43197 | """add a callback to be called when the wd parents are changed | ||
Callback will be called with the following arguments: | ||||
dirstate, (oldp1, oldp2), (newp1, newp2) | ||||
Category is a unique identifier to allow overwriting an old callback | ||||
with a newer callback. | ||||
""" | ||||
Matt Harbison
|
r52822 | def walk( | ||
self, | ||||
match: matchmod.basematcher, | ||||
subrepos: Any, # TODO: figure out what this is | ||||
unknown: bool, | ||||
ignored: bool, | ||||
full: bool = True, | ||||
) -> WalkReturnT: | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | Walk recursively through the directory tree, finding all files | ||
matched by match. | ||||
If full is False, maybe skip some known-clean files. | ||||
Return a dict mapping filename to stat-like object (either | ||||
mercurial.osutil.stat instance or return value of os.stat()). | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | def status( | ||
self, | ||||
match: matchmod.basematcher, | ||||
subrepos: bool, | ||||
ignored: bool, | ||||
clean: bool, | ||||
unknown: bool, | ||||
) -> StatusReturnT: | ||||
Augie Fackler
|
r46554 | """Determine the status of the working copy relative to the | ||
Augie Fackler
|
r43197 | dirstate and return a pair of (unsure, status), where status is of type | ||
scmutil.status and: | ||||
unsure: | ||||
files that might have been modified since the dirstate was | ||||
written, but need to be read to be sure (size is the same | ||||
but mtime differs) | ||||
status.modified: | ||||
files that have definitely been modified since the dirstate | ||||
was written (different size or mode) | ||||
status.clean: | ||||
files that have definitely not been modified since the | ||||
dirstate was written | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | # TODO: could return a list, except git.dirstate is a generator | ||
def matches(self, match: matchmod.basematcher) -> Iterable[bytes]: | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | return files in the dirstate (in whatever state) filtered by match | ||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43197 | |||
Matt Harbison
|
r52822 | # TODO: finish adding typehints here, and to subclasses | ||
def verify( | ||||
self, m1, m2, p1: bytes, narrow_matcher: Optional[Any] = None | ||||
) -> Iterator[bytes]: | ||||
r50777 | """ | |||
check the dirstate contents against the parent manifest and yield errors | ||||
""" | ||||