##// END OF EJS Templates
pycompat: drop usage of hasattr/getattr/setattr/delatt proxy...
pycompat: drop usage of hasattr/getattr/setattr/delatt proxy The function remains to ease extensions transition, but we no longer use them in core.

File last commit:

r51761:0a4efb65 stable
r51822:18c8c189 default
Show More
transaction.py
965 lines | 32.7 KiB | text/x-python | PythonLexer
Mads Kiilerich
fix trivial spelling errors
r17424 # transaction.py - simple journaling scheme for mercurial
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 #
# 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.
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
undo-files: move the undo cleanup code in the transaction module...
r51194 import errno
undo-files: add a undoname closure to the _write_undo method...
r51191 import os
Gregory Szorc
transaction: use absolute_import
r25986
from .i18n import _
from . import (
error,
Augie Fackler
transaction: fix an error string with bytestr() on a repr()d value...
r36753 pycompat,
Gregory Szorc
transaction: use absolute_import
r25986 util,
)
Augie Fackler
formatting: blacken the codebase...
r43346 from .utils import stringutil
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289
Pierre-Yves David
transaction: set backupentries version to proper value...
r23313 version = 2
Durham Goode
transactions: add version number to journal.backupfiles...
r23064
transaction: move constant to upper case...
r44886 GEN_GROUP_ALL = b'all'
GEN_GROUP_PRE_FINALIZE = b'prefinalize'
GEN_GROUP_POST_FINALIZE = b'postfinalize'
Augie Fackler
formatting: blacken the codebase...
r43346
Durham Goode
transaction: allow running file generators after finalizers...
r28830
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 def active(func):
def _active(self, *args, **kwds):
Gregory Szorc
transaction: make count and usages private attributes...
r39710 if self._count == 0:
Martin von Zweigbergk
transaction: use ProgrammingError for when an committed transaction is used...
r46330 raise error.ProgrammingError(
b'cannot use transaction when it is already committed/aborted'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 return func(self, *args, **kwds)
Augie Fackler
formatting: blacken the codebase...
r43346
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 return _active
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Augie Fackler
formatting: blacken the codebase...
r43346
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 UNDO_BACKUP = b'%s.backupfiles'
undo-files: move the undo cleanup code in the transaction module...
r51194
undo-files: relies on a explicit list of possible undo files...
r51195 UNDO_FILES_MAY_NEED_CLEANUP = [
undo-files: cleanup legacy files when applicable...
r51199 # legacy entries that might exists on disk from previous version:
(b'store', b'%s.narrowspec'),
(b'plain', b'%s.narrowspec.dirstate'),
(b'plain', b'%s.branch'),
(b'plain', b'%s.bookmarks'),
(b'store', b'%s.phaseroots'),
(b'plain', b'%s.dirstate'),
# files actually in uses today:
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 (b'plain', b'%s.desc'),
undo-files: relies on a explicit list of possible undo files...
r51195 # Always delete undo last to make sure we detect that a clean up is needed if
# the process is interrupted.
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 (b'store', b'%s'),
undo-files: relies on a explicit list of possible undo files...
r51195 ]
undo-files: move the undo cleanup code in the transaction module...
r51194
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 def cleanup_undo_files(report, vfsmap, undo_prefix=b'undo'):
undo-files: move the undo cleanup code in the transaction module...
r51194 """remove "undo" files used by the rollback logic
This is useful to prevent rollback running in situation were it does not
make sense. For example after a strip.
"""
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 backup_listing = UNDO_BACKUP % undo_prefix
undo-files: move the undo cleanup code in the transaction module...
r51194 backup_entries = []
undo_files = []
undo-files: no longer pass the `repo` to `cleanup_undo_files`...
r51196 svfs = vfsmap[b'store']
undo-files: move the undo cleanup code in the transaction module...
r51194 try:
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 with svfs(backup_listing) as f:
undo-files: no longer pass the `repo` to `cleanup_undo_files`...
r51196 backup_entries = read_backup_files(report, f)
undo-files: move the undo cleanup code in the transaction module...
r51194 except OSError as e:
if e.errno != errno.ENOENT:
msg = _(b'could not read %s: %s\n')
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 msg %= (svfs.join(backup_listing), stringutil.forcebytestr(e))
undo-files: no longer pass the `repo` to `cleanup_undo_files`...
r51196 report(msg)
undo-files: move the undo cleanup code in the transaction module...
r51194
for location, f, backup_path, c in backup_entries:
if location in vfsmap and backup_path:
undo_files.append((vfsmap[location], backup_path))
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 undo_files.append((svfs, backup_listing))
undo-files: relies on a explicit list of possible undo files...
r51195 for location, undo_path in UNDO_FILES_MAY_NEED_CLEANUP:
undo-files: make the undo-prefix configurable in `cleanup_undo_files`...
r51197 undo_files.append((vfsmap[location], undo_path % undo_prefix))
undo-files: move the undo cleanup code in the transaction module...
r51194 for undovfs, undofile in undo_files:
try:
undovfs.unlink(undofile)
except OSError as e:
if e.errno != errno.ENOENT:
msg = _(b'error removing %s: %s\n')
msg %= (undovfs.join(undofile), stringutil.forcebytestr(e))
undo-files: no longer pass the `repo` to `cleanup_undo_files`...
r51196 report(msg)
undo-files: move the undo cleanup code in the transaction module...
r51194
Augie Fackler
formatting: blacken the codebase...
r43346 def _playback(
journal,
report,
opener,
vfsmap,
entries,
backupentries,
unlink=True,
checkambigfiles=None,
):
transaction: allow to backup file that already have an offset...
r51236 """rollback a transaction :
- truncate files that have been appended to
- restore file backups
- delete temporary files
"""
transaction: move the restoration of backup file in a small closure...
r51235 backupfiles = []
def restore_one_backup(vfs, f, b, checkambig):
filepath = vfs.join(f)
backuppath = vfs.join(b)
try:
util.copyfile(backuppath, filepath, checkambig=checkambig)
backupfiles.append((vfs, b))
except IOError as exc:
e_msg = stringutil.forcebytestr(exc)
report(_(b"failed to recover %s (%s)\n") % (f, e_msg))
raise
transaction: allow to backup file that already have an offset...
r51236 # gather all backup files that impact the store
# (we need this to detect files that are both backed up and truncated)
store_backup = {}
for entry in backupentries:
location, file_path, backup_path, cache = entry
vfs = vfsmap[location]
is_store = vfs.join(b'') == opener.join(b'')
if is_store and file_path and backup_path:
store_backup[file_path] = entry
copy_done = set()
# truncate all file `f` to offset `o`
Joerg Sonnenberger
recover: only apply last journal record per file (issue6423)...
r48066 for f, o in sorted(dict(entries).items()):
transaction: allow to backup file that already have an offset...
r51236 # if we have a backup for `f`, we should restore it first and truncate
# the restored file
bck_entry = store_backup.get(f)
if bck_entry is not None:
location, file_path, backup_path, cache = bck_entry
checkambig = False
if checkambigfiles:
checkambig = (file_path, location) in checkambigfiles
restore_one_backup(opener, file_path, backup_path, checkambig)
copy_done.add(bck_entry)
# truncate the file to its pre-transaction size
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 if o or not unlink:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 checkambig = checkambigfiles and (f, b'') in checkambigfiles
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = opener(f, b'a', checkambig=checkambig)
Kyle Lippincott
transaction: detect an attempt to truncate-to-extend on playback, raise error...
r43233 if fp.tell() < o:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"attempted to truncate %s to %d bytes, but it was "
b"already %d bytes\n"
Augie Fackler
formatting: blacken the codebase...
r43346 )
% (f, o, fp.tell())
)
Dan Villiom Podlaski Christiansen
explicitly close files...
r13400 fp.truncate(o)
fp.close()
Benoit Boissinot
transaction: more specific exceptions, os.unlink can raise OSError
r9686 except IOError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 report(_(b"failed to truncate %s\n") % f)
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 raise
else:
transaction: allow to backup file that already have an offset...
r51236 # delete empty file
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 try:
FUJIWARA Katsunori
transaction: unlink target file via vfs...
r20084 opener.unlink(f)
Manuel Jacob
py3: catch FileNotFoundError instead of checking errno == ENOENT
r50201 except FileNotFoundError:
pass
transaction: allow to backup file that already have an offset...
r51236 # restore backed up files and clean up temporary files
for entry in backupentries:
if entry in copy_done:
continue
l, f, b, c = entry
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 if l not in vfsmap and c:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
Pierre-Yves David
transaction: use the location value when doing backup...
r23311 vfs = vfsmap[l]
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 try:
transaction: move the restoration of backup file in a small closure...
r51235 checkambig = checkambigfiles and (f, l) in checkambigfiles
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 if f and b:
transaction: move the restoration of backup file in a small closure...
r51235 restore_one_backup(vfs, f, b, checkambig)
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 else:
target = f or b
try:
vfs.unlink(target)
Manuel Jacob
py3: catch FileNotFoundError instead of checking errno == ENOENT
r50201 except FileNotFoundError:
transaction: add clarifying comment about why ignoring some error is fine...
r51233 # This is fine because
#
# either we are trying to delete the main file, and it is
# already deleted.
#
# or we are trying to delete a temporary file and it is
# already deleted.
#
# in both case, our target result (delete the file) is
# already achieved.
Manuel Jacob
py3: catch FileNotFoundError instead of checking errno == ENOENT
r50201 pass
Martin von Zweigbergk
cleanup: delete lots of unused local variables...
r41401 except (IOError, OSError, error.Abort):
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 if not c:
Pierre-Yves David
transaction: handle missing file in backupentries (instead of using entries)...
r23278 raise
Durham Goode
transaction: add support for non-append files...
r20882
transaction: allow to backup file that already have an offset...
r51236 # cleanup transaction state file and the backups file
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 backuppath = b"%s.backupfiles" % journal
Durham Goode
transaction: add support for non-append files...
r20882 if opener.exists(backuppath):
opener.unlink(backuppath)
FUJIWARA Katsunori
transaction: reorder unlinking .hg/journal and .hg/journal.backupfiles...
r26753 opener.unlink(journal)
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 try:
transaction: properly clean up backup file outside of .hg/store/...
r51232 for vfs, f in backupfiles:
if vfs.exists(f):
vfs.unlink(f)
Martin von Zweigbergk
cleanup: delete lots of unused local variables...
r41401 except (IOError, OSError, error.Abort):
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 # only pure backup file remains, it is sage to ignore any error
pass
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294
Augie Fackler
formatting: blacken the codebase...
r43346
Martin von Zweigbergk
util: add base class for transactional context managers...
r33790 class transaction(util.transactional):
Augie Fackler
formatting: blacken the codebase...
r43346 def __init__(
self,
report,
opener,
vfsmap,
journalname,
undoname=None,
after=None,
createmode=None,
validator=None,
releasefn=None,
checkambigfiles=None,
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 name='<unnamed>',
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Durham Goode
transaction: add onclose/onabort hook for pre-close logic...
r20881 """Begin a new transaction
Begins a new transaction that allows rolling back writes in the event of
an exception.
* `after`: called after the transaction has been committed
* `createmode`: the mode of the journal file that will be created
FUJIWARA Katsunori
transaction: add releasefn to notify the end of a transaction scope...
r26576 * `releasefn`: called after releasing (with transaction and result)
FUJIWARA Katsunori
transaction: avoid file stat ambiguity only for files in blacklist...
r33278
`checkambigfiles` is a set of (path, vfs-location) tuples,
which determine whether file stat ambiguity should be avoided
for corresponded files.
Durham Goode
transaction: add onclose/onabort hook for pre-close logic...
r20881 """
Gregory Szorc
transaction: make count and usages private attributes...
r39710 self._count = 1
self._usages = 1
Gregory Szorc
transaction: make report a private attribute...
r39719 self._report = report
Pierre-Yves David
transaction: pass a vfs map to the transaction...
r23310 # a vfs to the store content
Gregory Szorc
transaction: make opener a private attribute...
r39718 self._opener = opener
Pierre-Yves David
transaction: pass a vfs map to the transaction...
r23310 # a map to access file in various {location -> vfs}
vfsmap = vfsmap.copy()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vfsmap[b''] = opener # set default value
Pierre-Yves David
transaction: pass a vfs map to the transaction...
r23310 self._vfsmap = vfsmap
Gregory Szorc
transaction: make after a private attribute...
r39717 self._after = after
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 self._offsetmap = {}
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 self._newfiles = set()
Gregory Szorc
transaction: make journal a private attribute...
r39712 self._journal = journalname
undo-files: have the transaction directly tracks and manages journal rename...
r51192 self._journal_files = []
Gregory Szorc
transaction: make undoname a private attribute...
r39711 self._undoname = undoname
Pierre-Yves David
transaction: gather backupjournal logic together in the __init__...
r23279 self._queue = []
FUJIWARA Katsunori
transaction: add releasefn to notify the end of a transaction scope...
r26576 # A callback to do something just after releasing transaction.
if releasefn is None:
releasefn = lambda tr, success: None
Gregory Szorc
transaction: make releasefn a private attribute...
r39714 self._releasefn = releasefn
FUJIWARA Katsunori
transaction: add releasefn to notify the end of a transaction scope...
r26576
Gregory Szorc
transaction: make checkambigfiles a private attribute...
r39716 self._checkambigfiles = set()
FUJIWARA Katsunori
transaction: avoid file stat ambiguity only for files in blacklist...
r33278 if checkambigfiles:
Gregory Szorc
transaction: make checkambigfiles a private attribute...
r39716 self._checkambigfiles.update(checkambigfiles)
FUJIWARA Katsunori
transaction: avoid file stat ambiguity only for files in blacklist...
r33278
Gregory Szorc
transaction: make names a private attribute...
r39721 self._names = [name]
Martin von Zweigbergk
transaction: add a name and a __repr__ implementation (API)...
r36837
Pierre-Yves David
transaction: introduce "changes" dictionary to precisely track updates...
r32261 # A dict dedicated to precisely tracking the changes introduced in the
# transaction.
self.changes = {}
Pierre-Yves David
transaction: gather backupjournal logic together in the __init__...
r23279 # a dict of arguments to be passed to hooks
self.hookargs = {}
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 self._file = opener.open(self._journal, b"w+")
Pierre-Yves David
transaction: gather backupjournal logic together in the __init__...
r23279
Pierre-Yves David
transaction: change the on disk format for backupentries...
r23309 # a list of ('location', 'path', 'backuppath', cache) entries.
Pierre-Yves David
transaction: use the location value when doing backup...
r23311 # - if 'backuppath' is empty, no file existed at backup time
# - if 'path' is empty, this is a temporary transaction file
# - if 'location' is not empty, the path is outside main opener reach.
# use 'location' value as a key in a vfsmap to find the right 'vfs'
# (cache is currently unused)
Pierre-Yves David
transaction: mark backup-related attributes private...
r23249 self._backupentries = []
self._backupmap = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._backupjournal = b"%s.backupfiles" % self._journal
self._backupsfile = opener.open(self._backupjournal, b'w')
self._backupsfile.write(b'%d\n' % version)
Arseniy Alekseyev
revlog: fix a bug in revlog splitting...
r51535 # the set of temporary files
self._tmp_files = set()
Pierre-Yves David
transaction: gather backupjournal logic together in the __init__...
r23279
Alexis S. L. Carvalho
make the journal/undo files from transactions inherit the mode from .hg/store
r6065 if createmode is not None:
Gregory Szorc
transaction: make journal a private attribute...
r39712 opener.chmod(self._journal, createmode & 0o666)
Gregory Szorc
global: mass rewrite to use modern octal syntax...
r25658 opener.chmod(self._backupjournal, createmode & 0o666)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Pierre-Yves David
transaction: add a file generation mechanism...
r22078 # hold file generations to be performed on commit
self._filegenerators = {}
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23543 # hold callback to write pending data for hooks
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 self._pendingcallback = {}
# True is any pending data have been written ever
self._anypending = False
Pierre-Yves David
transaction: allow registering a finalization callback...
r23204 # holds callback to call when writing the transaction
self._finalizecallback = {}
Pulkit Goyal
transaction: add functionality to have multiple validators...
r45031 # holds callback to call when validating the transaction
# should raise exception if anything is wrong
self._validatecallback = {}
if validator is not None:
self._validatecallback[b'001-userhooks'] = validator
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23543 # hold callback for post transaction close
Pierre-Yves David
transaction: allow registering a post-close callback...
r23220 self._postclosecallback = {}
Gregory Szorc
transaction: support for callbacks during abort...
r23764 # holds callbacks to call during abort
self._abortcallback = {}
Pierre-Yves David
transaction: add a file generation mechanism...
r22078
Martin von Zweigbergk
transaction: add a name and a __repr__ implementation (API)...
r36837 def __repr__(self):
branching: merge with stable
r51579 name = b'/'.join(self._names)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 return '<transaction name=%s, count=%d, usages=%d>' % (
Augie Fackler
formatting: blacken the codebase...
r43346 name,
self._count,
self._usages,
)
Martin von Zweigbergk
transaction: add a name and a __repr__ implementation (API)...
r36837
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 def __del__(self):
Gregory Szorc
transaction: make journal a private attribute...
r39712 if self._journal:
Sune Foldager
transaction: always remove empty journal on abort...
r9693 self._abort()
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
transaction: add a way to know a transaction has been finalized...
r49525 @property
def finalized(self):
return self._finalizecallback is None
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363 def startgroup(self):
Pierre-Yves David
transaction: document startgroup and endgroup...
r23250 """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."""
Pierre-Yves David
transaction: drop backupentries logic from startgroup and endgroup...
r23251 self._queue.append([])
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363
@active
def endgroup(self):
Pierre-Yves David
transaction: document startgroup and endgroup...
r23250 """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."""
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363 q = self._queue.pop()
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 for f, o in q:
self._addentry(f, o)
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363
@active
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 def add(self, file, offset):
Pierre-Yves David
transaction: document `tr.add`
r23252 """record the state of an append-only file before update"""
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 if (
file in self._newfiles
or file in self._offsetmap
or file in self._backupmap
Arseniy Alekseyev
revlog: fix a bug in revlog splitting...
r51535 or file in self._tmp_files
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 ):
Matt Mackall
many, many trivial check-code fixups
r10282 return
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363 if self._queue:
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 self._queue[-1].append((file, offset))
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363 return
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 self._addentry(file, offset)
Pierre-Yves David
transaction: factorise append-only file registration...
r23253
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 def _addentry(self, file, offset):
Pierre-Yves David
transaction: factorise append-only file registration...
r23253 """add a append-only entry to memory and on-disk state"""
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 if (
file in self._newfiles
or file in self._offsetmap
or file in self._backupmap
Arseniy Alekseyev
revlog: fix a bug in revlog splitting...
r51535 or file in self._tmp_files
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 ):
Pierre-Yves David
transaction: factorise append-only file registration...
r23253 return
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 if offset:
self._offsetmap[file] = offset
else:
self._newfiles.add(file)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 # add enough data to the journal to do the truncate
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._file.write(b"%s\0%d\n" % (file, offset))
Gregory Szorc
transaction: make file a private attribute...
r39713 self._file.flush()
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
transaction: allow to backup file that already have an offset...
r51236 def addbackup(self, file, hardlink=True, location=b'', for_offset=False):
Durham Goode
transaction: add support for non-append files...
r20882 """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
transaction: allow to backup file that already have an offset...
r51236
If `for_offset` is set, we expect a offset for this file to have been previously recorded
Durham Goode
transaction: add support for non-append files...
r20882 """
Pierre-Yves David
transaction: drop backupentries logic from startgroup and endgroup...
r23251 if self._queue:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = b'cannot use transaction.addbackup inside "group"'
Jun Wu
transaction: use ProgrammingError
r31648 raise error.ProgrammingError(msg)
Durham Goode
transaction: add support for non-append files...
r20882
transaction: allow to backup file that already have an offset...
r51236 if file in self._newfiles or file in self._backupmap:
return
elif file in self._offsetmap and not for_offset:
Durham Goode
transaction: add support for non-append files...
r20882 return
transaction: allow to backup file that already have an offset...
r51236 elif for_offset and file not in self._offsetmap:
msg = (
'calling `addbackup` with `for_offmap=True`, '
'but no offset recorded: [%r] %r'
)
msg %= (location, file)
raise error.ProgrammingError(msg)
Pierre-Yves David
vfs: add a 'split' method...
r23582 vfs = self._vfsmap[location]
dirname, filename = vfs.split(file)
branching: merge with stable
r51579 backupfilename = b"%s.backup.%s.bck" % (self._journal, filename)
Pierre-Yves David
vfs: add a 'reljoin' function for joining relative paths...
r23581 backupfile = vfs.reljoin(dirname, backupfilename)
Pierre-Yves David
transaction: allow generating file outside of store...
r22663 if vfs.exists(file):
filepath = vfs.join(file)
Pierre-Yves David
addbackup: use the vfs for the backup destination too...
r23314 backuppath = vfs.join(backupfile)
backup: fix issue when the backup end up in a different directory...
r51348 # store encoding may result in different directory here.
# so we have to ensure the destination directory exist
final_dir_name = os.path.dirname(backuppath)
util.makedirs(final_dir_name, mode=vfs.createmode, notindexed=True)
# then we can copy the backup
Pierre-Yves David
transaction: use 'util.copyfile' for creating backup...
r23900 util.copyfile(filepath, backuppath, hardlink=hardlink)
Durham Goode
transaction: add support for non-append files...
r20882 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 backupfile = b''
Durham Goode
transaction: add support for non-append files...
r20882
Pierre-Yves David
transaction: use 'location' instead of 'vfs' in the addbackup method...
r23316 self._addbackupentry((location, file, backupfile, False))
Pierre-Yves David
transaction: extract backupentry registration in a dedicated function...
r23283
def _addbackupentry(self, entry):
"""register a new backup entry and write it to disk"""
self._backupentries.append(entry)
Pierre-Yves David
transaction: really fix _addbackupentry key usage (issue4684)...
r25294 self._backupmap[entry[1]] = len(self._backupentries) - 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._backupsfile.write(b"%s\0%s\0%s\0%d\n" % entry)
Pierre-Yves David
transaction: mark backup-related attributes private...
r23249 self._backupsfile.flush()
Durham Goode
transaction: add support for non-append files...
r20882
@active
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def registertmp(self, tmpfile, location=b''):
Pierre-Yves David
transaction: allow registering a temporary transaction file...
r23291 """register a temporary transaction file
Matt Mackall
transaction: fix some docstring grammar
r23355 Such files will be deleted when the transaction exits (on both
failure and success).
Pierre-Yves David
transaction: allow registering a temporary transaction file...
r23291 """
Arseniy Alekseyev
revlog: fix a bug in revlog splitting...
r51535 self._tmp_files.add(tmpfile)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._addbackupentry((location, b'', tmpfile, False))
Pierre-Yves David
transaction: allow registering a temporary transaction file...
r23291
@active
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def addfilegenerator(
transaction: do not rely on a global variable to post_finalize file...
r49534 self,
genid,
filenames,
genfunc,
order=0,
location=b'',
post_finalize=False,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ):
Pierre-Yves David
transaction: add a file generation mechanism...
r22078 """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.
Pierre-Yves David
transaction: use 'location' instead of 'vfs' objects for file generation...
r23317
The `location` arguments may be used to indicate the files are located
outside of the the standard directory for transaction. It should match
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23543 one of the key of the `transaction.vfsmap` dictionary.
transaction: do not rely on a global variable to post_finalize file...
r49534
The `post_finalize` argument can be set to `True` for file generation
that must be run after the transaction has been finalized.
Pierre-Yves David
transaction: add a file generation mechanism...
r22078 """
Pierre-Yves David
transaction: allow generating file outside of store...
r22663 # For now, we are unable to do proper backup and restore of custom vfs
# but for bookmarks that are handled outside this mechanism.
transaction: do not rely on a global variable to post_finalize file...
r49534 entry = (order, filenames, genfunc, location, post_finalize)
self._filegenerators[genid] = entry
Pierre-Yves David
transaction: add a file generation mechanism...
r22078
Jun Wu
rebase: clean up rebasestate from active transaction...
r33056 @active
def removefilegenerator(self, genid):
"""reverse of addfilegenerator, remove a file generator function"""
if genid in self._filegenerators:
del self._filegenerators[genid]
transaction: move constant to upper case...
r44886 def _generatefiles(self, suffix=b'', group=GEN_GROUP_ALL):
Pierre-Yves David
transaction: extract file generation into its own function...
r23102 # write files registered for generation
Pierre-Yves David
transaction: have _generatefile return a boolean...
r23357 any = False
transaction: clarify the logic around pre-finalize/post-finalize...
r44887
if group == GEN_GROUP_ALL:
skip_post = skip_pre = False
else:
skip_pre = group == GEN_GROUP_POST_FINALIZE
skip_post = group == GEN_GROUP_PRE_FINALIZE
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 for id, entry in sorted(self._filegenerators.items()):
Pierre-Yves David
transaction: have _generatefile return a boolean...
r23357 any = True
transaction: do not rely on a global variable to post_finalize file...
r49534 order, filenames, genfunc, location, post_finalize = entry
Durham Goode
transaction: allow running file generators after finalizers...
r28830
# for generation at closing, check if it's before or after finalize
transaction: do not rely on a global variable to post_finalize file...
r49534 if skip_post and post_finalize:
transaction: clarify the logic around pre-finalize/post-finalize...
r44887 continue
transaction: do not rely on a global variable to post_finalize file...
r49534 elif skip_pre and not post_finalize:
Durham Goode
transaction: allow running file generators after finalizers...
r28830 continue
Pierre-Yves David
transaction: use 'location' instead of 'vfs' objects for file generation...
r23317 vfs = self._vfsmap[location]
Pierre-Yves David
transaction: extract file generation into its own function...
r23102 files = []
try:
for name in filenames:
Pierre-Yves David
transaction: allow generating files with a suffix...
r23356 name += suffix
if suffix:
self.registertmp(name, location=location)
FUJIWARA Katsunori
transaction: apply checkambig=True only on limited files for similarity...
r33279 checkambig = False
Pierre-Yves David
transaction: allow generating files with a suffix...
r23356 else:
self.addbackup(name, location=location)
Gregory Szorc
transaction: make checkambigfiles a private attribute...
r39716 checkambig = (name, location) in self._checkambigfiles
Augie Fackler
formatting: blacken the codebase...
r43346 files.append(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vfs(name, b'w', atomictemp=True, checkambig=checkambig)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
transaction: extract file generation into its own function...
r23102 genfunc(*files)
Yuya Nishihara
transaction: do not overwrite atomic-temp files on error...
r41140 for f in files:
f.close()
# skip discard() loop since we're sure no open file remains
del files[:]
Pierre-Yves David
transaction: extract file generation into its own function...
r23102 finally:
for f in files:
Yuya Nishihara
transaction: do not overwrite atomic-temp files on error...
r41140 f.discard()
Pierre-Yves David
transaction: have _generatefile return a boolean...
r23357 return any
Pierre-Yves David
transaction: extract file generation into its own function...
r23102
Pierre-Yves David
transaction: add a file generation mechanism...
r22078 @active
Joerg Sonnenberger
transaction: rename find to findoffset and drop backup file support...
r46482 def findoffset(self, file):
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 if file in self._newfiles:
return 0
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 return self._offsetmap.get(file)
@active
def readjournal(self):
self._file.seek(0)
entries = []
Joerg Sonnenberger
transaction: windows workaround for missing line iteration support...
r46680 for l in self._file.readlines():
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 file, troffset = l.split(b'\0')
entries.append((file, int(troffset)))
return entries
Chris Mason
...
r2084
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 def replace(self, file, offset):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Henrik Stuart
transaction: add atomic groups to transaction logic...
r8363 replace can only replace already committed entries
that are not pending in the queue
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 if file in self._newfiles:
if not offset:
return
self._newfiles.remove(file)
self._offsetmap[file] = offset
elif file in self._offsetmap:
if not offset:
del self._offsetmap[file]
self._newfiles.add(file)
else:
self._offsetmap[file] = offset
else:
Chris Mason
...
r2084 raise KeyError(file)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._file.write(b"%s\0%d\n" % (file, offset))
Gregory Szorc
transaction: make file a private attribute...
r39713 self._file.flush()
Chris Mason
...
r2084
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 def nest(self, name='<unnamed>'):
Gregory Szorc
transaction: make count and usages private attributes...
r39710 self._count += 1
self._usages += 1
Gregory Szorc
transaction: make names a private attribute...
r39721 self._names.append(name)
mason@suse.com
Automatic nesting into running transactions in the same repository....
r1806 return self
Ronny Pfannschmidt
make transactions work on non-refcounted python implementations
r11230 def release(self):
Gregory Szorc
transaction: make count and usages private attributes...
r39710 if self._count > 0:
self._usages -= 1
Gregory Szorc
transaction: make names a private attribute...
r39721 if self._names:
self._names.pop()
Patrick Mezard
cleanup: typos
r11685 # if the transaction scopes are left without being closed, fail
Gregory Szorc
transaction: make count and usages private attributes...
r39710 if self._count > 0 and self._usages == 0:
Ronny Pfannschmidt
make transactions work on non-refcounted python implementations
r11230 self._abort()
mason@suse.com
Automatic nesting into running transactions in the same repository....
r1806 def running(self):
Gregory Szorc
transaction: make count and usages private attributes...
r39710 return self._count > 0
mason@suse.com
Automatic nesting into running transactions in the same repository....
r1806
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 def addpending(self, category, callback):
"""add a callback to be called when the transaction is pending
Pierre-Yves David
transaction: pass the transaction to 'pending' callback...
r23280 The transaction will be given as callback's first argument.
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 Category is a unique identifier to allow overwriting an old callback
with a newer callback.
"""
self._pendingcallback[category] = callback
@active
def writepending(self):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """write pending file to temporary version
Pierre-Yves David
transaction: add 'writepending' logic...
r23202
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 This is used to allow hooks to view a transaction before commit"""
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 categories = sorted(self._pendingcallback)
for cat in categories:
# remove callback since the data will have been flushed
Pierre-Yves David
transaction: pass the transaction to 'pending' callback...
r23280 any = self._pendingcallback.pop(cat)(self)
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 self._anypending = self._anypending or any
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._anypending |= self._generatefiles(suffix=b'.pending')
Pierre-Yves David
transaction: add 'writepending' logic...
r23202 return self._anypending
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
transaction: add a `hasfinalize` method...
r44508 def hasfinalize(self, category):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """check is a callback already exist for a category"""
transaction: add a `hasfinalize` method...
r44508 return category in self._finalizecallback
@active
Pierre-Yves David
transaction: allow registering a finalization callback...
r23204 def addfinalize(self, category, callback):
"""add a callback to be called when the transaction is closed
Pierre-Yves David
transaction: pass the transaction to 'finalize' callback...
r23281 The transaction will be given as callback's first argument.
Pierre-Yves David
transaction: allow registering a finalization callback...
r23204 Category is a unique identifier to allow overwriting old callbacks with
newer callbacks.
"""
self._finalizecallback[category] = callback
@active
Pierre-Yves David
transaction: allow registering a post-close callback...
r23220 def addpostclose(self, category, callback):
Jun Wu
strip: add a delayedstrip method that works in a transaction...
r33087 """add or replace a callback to be called after the transaction closed
Pierre-Yves David
transaction: allow registering a post-close callback...
r23220
Pierre-Yves David
transaction: pass the transaction to 'postclose' callback...
r23282 The transaction will be given as callback's first argument.
Pierre-Yves David
transaction: allow registering a post-close callback...
r23220 Category is a unique identifier to allow overwriting an old callback
with a newer callback.
"""
self._postclosecallback[category] = callback
@active
Jun Wu
strip: add a delayedstrip method that works in a transaction...
r33087 def getpostclose(self, category):
"""return a postclose callback added before, or None"""
return self._postclosecallback.get(category, None)
@active
Gregory Szorc
transaction: support for callbacks during abort...
r23764 def addabort(self, category, callback):
"""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.
"""
self._abortcallback[category] = callback
@active
Pulkit Goyal
transaction: add functionality to have multiple validators...
r45031 def addvalidator(self, category, callback):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """adds a callback to be called when validating the transaction.
Pulkit Goyal
transaction: add functionality to have multiple validators...
r45031
The transaction will be given as the first argument to the callback.
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 callback should raise exception if to abort transaction"""
Pulkit Goyal
transaction: add functionality to have multiple validators...
r45031 self._validatecallback[category] = callback
@active
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 def close(self):
Greg Ward
transaction: document close(), abort() methods
r9220 '''commit the transaction'''
Gregory Szorc
transaction: make count and usages private attributes...
r39710 if self._count == 1:
Pulkit Goyal
transaction: add functionality to have multiple validators...
r45031 for category in sorted(self._validatecallback):
self._validatecallback[category](self)
self._validatecallback = None # Help prevent cycles.
transaction: move constant to upper case...
r44886 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
transaction: allow finalizer to add finalizer...
r44556 while self._finalizecallback:
callbacks = self._finalizecallback
self._finalizecallback = {}
categories = sorted(callbacks)
for cat in categories:
callbacks[cat](self)
Gregory Szorc
transaction: clear callback instances after usage...
r28960 # Prevent double usage and help clear cycles.
self._finalizecallback = None
transaction: move constant to upper case...
r44886 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
Durham Goode
transaction: add onclose/onabort hook for pre-close logic...
r20881
Gregory Szorc
transaction: make count and usages private attributes...
r39710 self._count -= 1
if self._count != 0:
mason@suse.com
Automatic nesting into running transactions in the same repository....
r1806 return
Gregory Szorc
transaction: make file a private attribute...
r39713 self._file.close()
Pierre-Yves David
transaction: mark backup-related attributes private...
r23249 self._backupsfile.close()
Pierre-Yves David
transaction: allow registering a temporary transaction file...
r23291 # cleanup temporary files
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 for l, f, b, c in self._backupentries:
if l not in self._vfsmap and c:
Augie Fackler
formatting: blacken the codebase...
r43346 self._report(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 continue
Pierre-Yves David
transaction: use the location value when doing backup...
r23311 vfs = self._vfsmap[l]
if not f and b and vfs.exists(b):
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 try:
vfs.unlink(b)
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 except (IOError, OSError, error.Abort) as inst:
Pierre-Yves David
transaction: support cache file in backupentries...
r23312 if not c:
raise
# Abort may be raise by read only opener
Augie Fackler
formatting: blacken the codebase...
r43346 self._report(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 self._offsetmap = {}
Joerg Sonnenberger
transaction: split new files into a separate set...
r46484 self._newfiles = set()
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 self._writeundo()
Gregory Szorc
transaction: make after a private attribute...
r39717 if self._after:
self._after()
Augie Fackler
formatting: blacken the codebase...
r43346 self._after = None # Help prevent cycles.
Gregory Szorc
transaction: make opener a private attribute...
r39718 if self._opener.isfile(self._backupjournal):
self._opener.unlink(self._backupjournal)
if self._opener.isfile(self._journal):
self._opener.unlink(self._journal)
Martin von Zweigbergk
transaction: remove 'if True:'...
r27662 for l, _f, b, c in self._backupentries:
if l not in self._vfsmap and c:
Augie Fackler
formatting: blacken the codebase...
r43346 self._report(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"couldn't remove %s: unknown cache location"
b"%s\n" % (b, l)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Martin von Zweigbergk
transaction: remove 'if True:'...
r27662 continue
vfs = self._vfsmap[l]
if b and vfs.exists(b):
try:
vfs.unlink(b)
except (IOError, OSError, error.Abort) as inst:
if not c:
raise
# Abort may be raise by read only opener
Augie Fackler
formatting: blacken the codebase...
r43346 self._report(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
transaction: mark backup-related attributes private...
r23249 self._backupentries = []
Gregory Szorc
transaction: make journal a private attribute...
r39712 self._journal = None
FUJIWARA Katsunori
transaction: add releasefn to notify the end of a transaction scope...
r26576
Augie Fackler
formatting: blacken the codebase...
r43346 self._releasefn(self, True) # notify success of closing transaction
self._releasefn = None # Help prevent cycles.
FUJIWARA Katsunori
transaction: add releasefn to notify the end of a transaction scope...
r26576
Pierre-Yves David
transaction: allow registering a post-close callback...
r23220 # run post close action
categories = sorted(self._postclosecallback)
for cat in categories:
Pierre-Yves David
transaction: pass the transaction to 'postclose' callback...
r23282 self._postclosecallback[cat](self)
Gregory Szorc
transaction: clear callback instances after usage...
r28960 # Prevent double usage and help clear cycles.
self._postclosecallback = None
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 @active
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0 def abort(self):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """abort the transaction (generally called on error, or when the
Greg Ward
transaction: document close(), abort() methods
r9220 transaction is not explicitly committed before going out of
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 scope)"""
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 self._abort()
undo-files: have the transaction directly tracks and manages journal rename...
r51192 @active
def add_journal(self, vfs_id, path):
self._journal_files.append((vfs_id, path))
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 def _writeundo(self):
"""write transaction data for possible future undo call"""
Gregory Szorc
transaction: make undoname a private attribute...
r39711 if self._undoname is None:
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 return
undo-files: clean existing files up before writing new one...
r51198 cleanup_undo_files(
self._report,
self._vfsmap,
undo_prefix=self._undoname,
)
transaction: simplify `undo.backupfiles` file creation with a variable...
r48215
undo-files: add a undoname closure to the _write_undo method...
r51191 def undoname(fn: bytes) -> bytes:
base, name = os.path.split(fn)
assert name.startswith(self._journal)
new_name = name.replace(self._journal, self._undoname, 1)
return os.path.join(base, new_name)
transaction: simplify `undo.backupfiles` file creation with a variable...
r48215 undo_backup_path = b"%s.backupfiles" % self._undoname
undobackupfile = self._opener.open(undo_backup_path, b'w')
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 undobackupfile.write(b'%d\n' % version)
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 for l, f, b, c in self._backupentries:
if not f: # temporary file
continue
if not b:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 u = b''
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 else:
if l not in self._vfsmap and c:
Augie Fackler
formatting: blacken the codebase...
r43346 self._report(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"couldn't remove %s: unknown cache location"
b"%s\n" % (b, l)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 continue
vfs = self._vfsmap[l]
undo-files: add a undoname closure to the _write_undo method...
r51191 u = undoname(b)
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904 undobackupfile.close()
undo-files: have the transaction directly tracks and manages journal rename...
r51192 for vfs, src in self._journal_files:
dest = undoname(src)
# if src and dest refer to a same file, vfs.rename is a no-op,
# leaving both src and dest on disk. delete dest to make sure
# the rename couldn't be such a no-op.
vfs.tryunlink(dest)
try:
vfs.rename(src, dest)
except FileNotFoundError: # journal file does not yet exist
pass
Pierre-Yves David
transaction: include backup file in the "undo" transaction...
r23904
Henrik Stuart
transaction: ensure finished transactions are not reused...
r8289 def _abort(self):
Joerg Sonnenberger
transaction: change list of journal entries into a dictionary...
r46483 entries = self.readjournal()
Gregory Szorc
transaction: make count and usages private attributes...
r39710 self._count = 0
self._usages = 0
Gregory Szorc
transaction: make file a private attribute...
r39713 self._file.close()
Pierre-Yves David
transaction: mark backup-related attributes private...
r23249 self._backupsfile.close()
Henrik Stuart
transaction: reset transaction on abort...
r8290
transaction: clarify the "quick abort" scenario...
r50888 quick = self._can_quick_abort(entries)
Benoit Boissinot
transaction: initialize self.journal to None after deletion...
r10228 try:
transaction: run abort callback in all cases...
r50889 if not quick:
self._report(_(b"transaction abort!\n"))
for cat in sorted(self._abortcallback):
self._abortcallback[cat](self)
# Prevent double usage and help clear cycles.
self._abortcallback = None
transaction: clarify the "quick abort" scenario...
r50888 if quick:
self._do_quick_abort(entries)
else:
self._do_full_abort(entries)
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 finally:
Gregory Szorc
transaction: make journal a private attribute...
r39712 self._journal = None
Augie Fackler
formatting: blacken the codebase...
r43346 self._releasefn(self, False) # notify failure of transaction
self._releasefn = None # Help prevent cycles.
transaction: clarify the "quick abort" scenario...
r50888 def _can_quick_abort(self, entries):
"""False if any semantic content have been written on disk
True if nothing, except temporary files has been writen on disk."""
if entries:
return False
transaction: quietly rollback if no other changes than temporary files...
r50890 for e in self._backupentries:
if e[1]:
return False
transaction: clarify the "quick abort" scenario...
r50888 return True
def _do_quick_abort(self, entries):
"""(Silently) do a quick cleanup (see _can_quick_abort)"""
assert self._can_quick_abort(entries)
transaction: quietly rollback if no other changes than temporary files...
r50890 tmp_files = [e for e in self._backupentries if not e[1]]
for vfs_id, old_path, tmp_path, xxx in tmp_files:
vfs = self._vfsmap[vfs_id]
try:
vfs.unlink(tmp_path)
except FileNotFoundError:
pass
transaction: clarify the "quick abort" scenario...
r50888 if self._backupjournal:
self._opener.unlink(self._backupjournal)
if self._journal:
self._opener.unlink(self._journal)
def _do_full_abort(self, entries):
"""(Noisily) rollback all the change introduced by the transaction"""
try:
_playback(
self._journal,
self._report,
self._opener,
self._vfsmap,
entries,
self._backupentries,
transaction: actually delete file created during the transaction on rollback...
r51703 unlink=True,
transaction: clarify the "quick abort" scenario...
r50888 checkambigfiles=self._checkambigfiles,
)
self._report(_(b"rollback completed\n"))
except BaseException as exc:
self._report(_(b"rollback failed - please run hg recover\n"))
self._report(
_(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
)
Henrik Stuart
transaction: reset transaction on abort...
r8290
transaction: extract message about different version in a constants...
r48212 BAD_VERSION_MSG = _(
b"journal was created by a different version of Mercurial\n"
)
undo-files: add a utility function to read the backup-files definition...
r51188 def read_backup_files(report, fp):
"""parse an (already open) backup file an return contained backup entries
entries are in the form: (location, file, backupfile, xxx)
:location: the vfs identifier (vfsmap's key)
:file: original file path (in the vfs)
:backupfile: path of the backup (in the vfs)
:cache: a boolean currently always set to False
"""
lines = fp.readlines()
backupentries = []
if lines:
ver = lines[0][:-1]
if ver != (b'%d' % version):
report(BAD_VERSION_MSG)
else:
for line in lines[1:]:
if line:
# Shave off the trailing newline
line = line[:-1]
l, f, b, c = line.split(b'\0')
backupentries.append((l, f, b, bool(c)))
return backupentries
rollback: explicitly skip dirstate rollback when applicable...
r50965 def rollback(
opener,
vfsmap,
file,
report,
checkambigfiles=None,
skip_journal_pattern=None,
):
Durham Goode
transaction: add support for non-append files...
r20882 """Rolls back the transaction contained in the given file
Reads the entries in the specified file, and the corresponding
'*.backupfiles' file, to recover from an incomplete transaction.
* `file`: a file containing a list of entries, specifying where
to truncate each file. The file should contain a list of
file\0offset pairs, delimited by newlines. The corresponding
'*.backupfiles' file should contain a list of file\0backupfile
pairs, delimited by \0.
FUJIWARA Katsunori
transaction: avoid file stat ambiguity only for files in blacklist...
r33278
`checkambigfiles` is a set of (path, vfs-location) tuples,
which determine whether file stat ambiguity should be avoided at
restoring corresponded files.
Durham Goode
transaction: add support for non-append files...
r20882 """
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294 entries = []
Durham Goode
transaction: add support for non-append files...
r20882 backupentries = []
Henrik Stuart
transaction: refactor transaction.abort and rollback to use the same code...
r8294
Valentin Gatien-Baron
transaction: trivial refactoring...
r48088 with opener.open(file) as fp:
lines = fp.readlines()
Dan Villiom Podlaski Christiansen
explicitly close files...
r13400 for l in lines:
Matt Mackall
journal: report parsing errors on recover/rollback (issue4172)
r20524 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 f, o = l.split(b'\0')
Joerg Sonnenberger
transaction: drop per-file extra data support...
r46481 entries.append((f, int(o)))
Matt Mackall
journal: report parsing errors on recover/rollback (issue4172)
r20524 except ValueError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 report(
_(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
)
mpm@selenic.com
Add back links from file revisions to changeset revisions...
r0
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 backupjournal = b"%s.backupfiles" % file
Durham Goode
transaction: add support for non-append files...
r20882 if opener.exists(backupjournal):
undo-files: add a utility function to read the backup-files definition...
r51188 with opener.open(backupjournal) as fp:
backupentries = read_backup_files(report, fp)
rollback: explicitly skip dirstate rollback when applicable...
r50965 if skip_journal_pattern is not None:
keep = lambda x: not skip_journal_pattern.match(x[1])
backupentries = [x for x in backupentries if keep(x)]
Durham Goode
transaction: add support for non-append files...
r20882
Augie Fackler
formatting: blacken the codebase...
r43346 _playback(
file,
report,
opener,
vfsmap,
entries,
backupentries,
checkambigfiles=checkambigfiles,
)