Mercurial > public > mercurial-scm > hg-stable
changeset 52768:a7dcb7c1ff5a
typing: add a transaction protocol
This allow us to remove the "external" import from
mercurial/interfaces/dirstate.py, cutting one of the circular import route.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Fri, 07 Feb 2025 13:48:50 +0100 |
parents | ae2848198462 |
children | 1b7a57a5b47a |
files | hgext/git/dirstate.py mercurial/dirstate.py mercurial/interfaces/dirstate.py mercurial/interfaces/transaction.py mercurial/interfaces/types.py mercurial/transaction.py |
diffstat | 6 files changed, 280 insertions(+), 24 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/git/dirstate.py Fri Feb 07 16:40:49 2025 +0100 +++ b/hgext/git/dirstate.py Fri Feb 07 13:48:50 2025 +0100 @@ -13,7 +13,10 @@ Tuple, ) -from mercurial.interfaces.types import MatcherT +from mercurial.interfaces.types import ( + MatcherT, + TransactionT, +) from mercurial.node import sha1nodeconstants from mercurial import ( dirstatemap, @@ -317,7 +320,7 @@ ) -> None: raise NotImplementedError - def write(self, tr: Optional[intdirstate.TransactionT]) -> None: + def write(self, tr: Optional[TransactionT]) -> None: # TODO: call parent change callbacks if tr: @@ -456,7 +459,7 @@ self._plchangecallbacks[category] = callback def setbranch( - self, branch: bytes, transaction: Optional[intdirstate.TransactionT] + self, branch: bytes, transaction: Optional[TransactionT] ) -> None: raise error.Abort( b'git repos do not support branches. try using bookmarks'
--- a/mercurial/dirstate.py Fri Feb 07 16:40:49 2025 +0100 +++ b/mercurial/dirstate.py Fri Feb 07 13:48:50 2025 +0100 @@ -24,7 +24,10 @@ ) from .i18n import _ -from .interfaces.types import MatcherT +from .interfaces.types import ( + MatcherT, + TransactionT, +) from hgdemandimport import tracing @@ -669,7 +672,7 @@ return self._map.setparents(p1, p2, fold_p2=fold_p2) def setbranch( - self, branch: bytes, transaction: Optional[intdirstate.TransactionT] + self, branch: bytes, transaction: Optional[TransactionT] ) -> None: self.__class__._branch.set(self, encoding.fromlocal(branch)) if transaction is not None: @@ -1102,7 +1105,7 @@ on_abort, ) - def write(self, tr: Optional[intdirstate.TransactionT]) -> None: + def write(self, tr: Optional[TransactionT]) -> None: if not self._dirty: return # make sure we don't request a write of invalidated content
--- a/mercurial/interfaces/dirstate.py Fri Feb 07 16:40:49 2025 +0100 +++ b/mercurial/interfaces/dirstate.py Fri Feb 07 13:48:50 2025 +0100 @@ -20,13 +20,10 @@ if typing.TYPE_CHECKING: # Almost all mercurial modules are only imported in the type checking phase # to avoid circular imports - from .. import ( - transaction as txnmod, - ) - from . import ( matcher, status as istatus, + transaction, ) # TODO: finish adding type hints @@ -55,8 +52,7 @@ StatusReturnT = Tuple[Any, istatus.Status, Any] """The return type of dirstate.status().""" - # TODO: probably doesn't belong here. - TransactionT = txnmod.transaction + TransactionT = transaction.ITransaction """The type for a transaction used with dirstate. This is meant to help callers avoid having to remember to delay the import
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/interfaces/transaction.py Fri Feb 07 13:48:50 2025 +0100 @@ -0,0 +1,253 @@ +# transaction.py - simple journaling scheme for mercurial +# +# This transaction scheme is intended to gracefully handle program +# errors and interruptions. More serious failures like system crashes +# can be recovered with an fsck-like tool. As the whole repository is +# effectively log-structured, this should amount to simply truncating +# anything that isn't referenced in the changelog. +# +# Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import annotations + +import abc + +from typing import ( + Callable, + Collection, + List, + Optional, + Protocol, + Tuple, + Union, +) + +from ._basetypes import ( + CallbackCategoryT, + HgPathT, + VfsKeyT, +) + +JournalEntryT = Tuple[HgPathT, int] + + +class ITransaction(Protocol): + @property + @abc.abstractmethod + def finalized(self) -> bool: + ... + + @abc.abstractmethod + def startgroup(self) -> None: + """delay registration of file entry + + This is used by strip to delay vision of strip offset. The transaction + sees either none or all of the strip actions to be done.""" + + @abc.abstractmethod + def endgroup(self) -> None: + """apply delayed registration of file entry. + + This is used by strip to delay vision of strip offset. The transaction + sees either none or all of the strip actions to be done.""" + + @abc.abstractmethod + def add(self, file: HgPathT, offset: int) -> None: + """record the state of an append-only file before update""" + + @abc.abstractmethod + def addbackup( + self, + file: HgPathT, + hardlink: bool = True, + location: VfsKeyT = b'', + for_offset: Union[bool, int] = False, + ) -> None: + """Adds a backup of the file to the transaction + + Calling addbackup() creates a hardlink backup of the specified file + that is used to recover the file in the event of the transaction + aborting. + + * `file`: the file path, relative to .hg/store + * `hardlink`: use a hardlink to quickly create the backup + + If `for_offset` is set, we expect a offset for this file to have been + previously recorded + """ + + @abc.abstractmethod + def registertmp(self, tmpfile: HgPathT, location: VfsKeyT = b'') -> None: + """register a temporary transaction file + + Such files will be deleted when the transaction exits (on both + failure and success). + """ + + @abc.abstractmethod + def addfilegenerator( + self, + genid: bytes, + filenames: Collection[HgPathT], + genfunc: Callable, + order: int = 0, + location: VfsKeyT = b'', + post_finalize: bool = False, + ) -> None: + """add a function to generates some files at transaction commit + + The `genfunc` argument is a function capable of generating proper + content of each entry in the `filename` tuple. + + At transaction close time, `genfunc` will be called with one file + object argument per entries in `filenames`. + + The transaction itself is responsible for the backup, creation and + final write of such file. + + The `genid` argument is used to ensure the same set of file is only + generated once. Call to `addfilegenerator` for a `genid` already + present will overwrite the old entry. + + The `order` argument may be used to control the order in which multiple + generator will be executed. + + The `location` arguments may be used to indicate the files are located + outside of the the standard directory for transaction. It should match + one of the key of the `transaction.vfsmap` dictionary. + + The `post_finalize` argument can be set to `True` for file generation + that must be run after the transaction has been finalized. + """ + + @abc.abstractmethod + def removefilegenerator(self, genid: bytes) -> None: + """reverse of addfilegenerator, remove a file generator function""" + + @abc.abstractmethod + def findoffset(self, file: HgPathT) -> Optional[int]: + ... + + @abc.abstractmethod + def readjournal(self) -> List[JournalEntryT]: + ... + + @abc.abstractmethod + def replace(self, file: HgPathT, offset: int) -> None: + """ + replace can only replace already committed entries + that are not pending in the queue + """ + + @abc.abstractmethod + def nest(self, name: bytes = b'<unnamed>') -> ITransaction: + ... + + @abc.abstractmethod + def release(self) -> None: + ... + + @abc.abstractmethod + def running(self) -> bool: + ... + + @abc.abstractmethod + def addpending( + self, + category: CallbackCategoryT, + callback: Callable[[ITransaction], None], + ) -> None: + """add a callback to be called when the transaction is pending + + The transaction will be given as callback's first argument. + + Category is a unique identifier to allow overwriting an old callback + with a newer callback. + """ + + @abc.abstractmethod + def writepending(self) -> bool: + """write pending file to temporary version + + This is used to allow hooks to view a transaction before commit""" + + @abc.abstractmethod + def hasfinalize(self, category: CallbackCategoryT) -> bool: + """check is a callback already exist for a category""" + + @abc.abstractmethod + def addfinalize( + self, + category: CallbackCategoryT, + callback: Callable[[ITransaction], None], + ) -> None: + """add a callback to be called when the transaction is closed + + The transaction will be given as callback's first argument. + + Category is a unique identifier to allow overwriting old callbacks with + newer callbacks. + """ + + @abc.abstractmethod + def addpostclose( + self, + category: CallbackCategoryT, + callback: Callable[[ITransaction], None], + ) -> None: + """add or replace a callback to be called after the transaction closed + + The transaction will be given as callback's first argument. + + Category is a unique identifier to allow overwriting an old callback + with a newer callback. + """ + + @abc.abstractmethod + def getpostclose( + self, category: CallbackCategoryT + ) -> Optional[Callable[[ITransaction], None]]: + """return a postclose callback added before, or None""" + + @abc.abstractmethod + def addabort( + self, + category: CallbackCategoryT, + callback: Callable[[ITransaction], None], + ) -> None: + """add a callback to be called when the transaction is aborted. + + The transaction will be given as the first argument to the callback. + + Category is a unique identifier to allow overwriting an old callback + with a newer callback. + """ + + @abc.abstractmethod + def addvalidator( + self, + category: CallbackCategoryT, + callback: Callable[[ITransaction], None], + ) -> None: + """adds a callback to be called when validating the transaction. + + The transaction will be given as the first argument to the callback. + + callback should raise exception if to abort transaction""" + + @abc.abstractmethod + def close(self) -> None: + '''commit the transaction''' + + @abc.abstractmethod + def abort(self) -> None: + """abort the transaction (generally called on error, or when the + transaction is not explicitly committed before going out of + scope)""" + + @abc.abstractmethod + def add_journal(self, vfs_id: VfsKeyT, path: HgPathT) -> None: + ...
--- a/mercurial/interfaces/types.py Fri Feb 07 16:40:49 2025 +0100 +++ b/mercurial/interfaces/types.py Fri Feb 07 13:48:50 2025 +0100 @@ -21,6 +21,8 @@ from . import ( matcher, + transaction, ) MatcherT = matcher.IMatcher +TransactionT = transaction.ITransaction
--- a/mercurial/transaction.py Fri Feb 07 16:40:49 2025 +0100 +++ b/mercurial/transaction.py Fri Feb 07 13:48:50 2025 +0100 @@ -21,7 +21,6 @@ Collection, List, Optional, - Tuple, Union, ) @@ -29,6 +28,7 @@ from .interfaces.types import ( CallbackCategoryT, HgPathT, + TransactionT, VfsKeyT, ) from . import ( @@ -38,6 +38,7 @@ util, ) from .utils import stringutil +from .interfaces import transaction as itxn version = 2 @@ -45,8 +46,6 @@ GEN_GROUP_PRE_FINALIZE = b'prefinalize' GEN_GROUP_POST_FINALIZE = b'postfinalize' -JournalEntryT = Tuple[HgPathT, int] - def active(func): def _active(self, *args, **kwds): @@ -240,7 +239,7 @@ pass -class transaction(util.transactional): +class transaction(util.transactional, itxn.ITransaction): def __init__( self, report, @@ -573,7 +572,7 @@ return self._offsetmap.get(file) @active - def readjournal(self) -> List[JournalEntryT]: + def readjournal(self) -> List[itxn.JournalEntryT]: self._file.seek(0) entries = [] for l in self._file.readlines(): @@ -604,7 +603,7 @@ self._file.flush() @active - def nest(self, name: bytes = b'<unnamed>') -> transaction: + def nest(self, name: bytes = b'<unnamed>') -> TransactionT: self._count += 1 self._usages += 1 self._names.append(name) @@ -625,7 +624,7 @@ def addpending( self, category: CallbackCategoryT, - callback: Callable[[transaction], None], + callback: Callable[[TransactionT], None], ) -> None: """add a callback to be called when the transaction is pending @@ -658,7 +657,7 @@ def addfinalize( self, category: CallbackCategoryT, - callback: Callable[[transaction], None], + callback: Callable[[TransactionT], None], ) -> None: """add a callback to be called when the transaction is closed @@ -673,7 +672,7 @@ def addpostclose( self, category: CallbackCategoryT, - callback: Callable[[transaction], None], + callback: Callable[[TransactionT], None], ) -> None: """add or replace a callback to be called after the transaction closed @@ -688,7 +687,7 @@ def getpostclose( self, category: CallbackCategoryT, - ) -> Optional[Callable[[transaction], None]]: + ) -> Optional[Callable[[TransactionT], None]]: """return a postclose callback added before, or None""" return self._postclosecallback.get(category, None) @@ -696,7 +695,7 @@ def addabort( self, category: CallbackCategoryT, - callback: Callable[[transaction], None], + callback: Callable[[TransactionT], None], ) -> None: """add a callback to be called when the transaction is aborted. @@ -711,7 +710,7 @@ def addvalidator( self, category: CallbackCategoryT, - callback: Callable[[transaction], None], + callback: Callable[[TransactionT], None], ) -> None: """adds a callback to be called when validating the transaction.