transaction.py
599 lines
| 21.2 KiB
| text/x-python
|
PythonLexer
/ mercurial / transaction.py
Mads Kiilerich
|
r17424 | # transaction.py - simple journaling scheme for mercurial | ||
mpm@selenic.com
|
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. | ||||
# | ||||
Vadim Gelfer
|
r2859 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | ||
mpm@selenic.com
|
r0 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
mpm@selenic.com
|
r0 | |||
Gregory Szorc
|
r25986 | from __future__ import absolute_import | ||
Matt Mackall
|
r20886 | import errno | ||
Gregory Szorc
|
r25986 | |||
from .i18n import _ | ||||
from . import ( | ||||
error, | ||||
util, | ||||
) | ||||
Henrik Stuart
|
r8289 | |||
Pierre-Yves David
|
r23313 | version = 2 | ||
Durham Goode
|
r23064 | |||
Durham Goode
|
r28830 | # These are the file generators that should only be executed after the | ||
# finalizers are done, since they rely on the output of the finalizers (like | ||||
# the changelog having been written). | ||||
postfinalizegenerators = set([ | ||||
'bookmarks', | ||||
'dirstate' | ||||
]) | ||||
Pierre-Yves David
|
r29297 | gengroupall='all' | ||
gengroupprefinalize='prefinalize' | ||||
gengrouppostfinalize='postfinalize' | ||||
Durham Goode
|
r28830 | |||
Henrik Stuart
|
r8289 | def active(func): | ||
def _active(self, *args, **kwds): | ||||
if self.count == 0: | ||||
raise error.Abort(_( | ||||
'cannot use transaction when it is already committed/aborted')) | ||||
return func(self, *args, **kwds) | ||||
return _active | ||||
mpm@selenic.com
|
r0 | |||
Pierre-Yves David
|
r23311 | def _playback(journal, report, opener, vfsmap, entries, backupentries, | ||
unlink=True): | ||||
Mads Kiilerich
|
r22204 | for f, o, _ignore in entries: | ||
Henrik Stuart
|
r8294 | if o or not unlink: | ||
try: | ||||
FUJIWARA Katsunori
|
r30002 | fp = opener(f, 'a', checkambig=True) | ||
Dan Villiom Podlaski Christiansen
|
r13400 | fp.truncate(o) | ||
fp.close() | ||||
Benoit Boissinot
|
r9686 | except IOError: | ||
Henrik Stuart
|
r8294 | report(_("failed to truncate %s\n") % f) | ||
raise | ||||
else: | ||||
try: | ||||
FUJIWARA Katsunori
|
r20084 | opener.unlink(f) | ||
Gregory Szorc
|
r25660 | except (IOError, OSError) as inst: | ||
Henrik Stuart
|
r8294 | if inst.errno != errno.ENOENT: | ||
raise | ||||
Durham Goode
|
r20882 | |||
backupfiles = [] | ||||
Pierre-Yves David
|
r23309 | for l, f, b, c in backupentries: | ||
Pierre-Yves David
|
r23312 | if l not in vfsmap and c: | ||
report("couldn't handle %s: unknown cache location %s\n" | ||||
% (b, l)) | ||||
Pierre-Yves David
|
r23311 | vfs = vfsmap[l] | ||
Pierre-Yves David
|
r23312 | try: | ||
if f and b: | ||||
filepath = vfs.join(f) | ||||
backuppath = vfs.join(b) | ||||
try: | ||||
FUJIWARA Katsunori
|
r29353 | util.copyfile(backuppath, filepath, checkambig=True) | ||
Pierre-Yves David
|
r23312 | backupfiles.append(b) | ||
except IOError: | ||||
report(_("failed to recover %s\n") % f) | ||||
else: | ||||
target = f or b | ||||
try: | ||||
vfs.unlink(target) | ||||
Gregory Szorc
|
r25660 | except (IOError, OSError) as inst: | ||
Pierre-Yves David
|
r23312 | if inst.errno != errno.ENOENT: | ||
raise | ||||
Pierre-Yves David
|
r26587 | except (IOError, OSError, error.Abort) as inst: | ||
Pierre-Yves David
|
r23312 | if not c: | ||
Pierre-Yves David
|
r23278 | raise | ||
Durham Goode
|
r20882 | |||
backuppath = "%s.backupfiles" % journal | ||||
if opener.exists(backuppath): | ||||
opener.unlink(backuppath) | ||||
FUJIWARA Katsunori
|
r26753 | opener.unlink(journal) | ||
Pierre-Yves David
|
r23312 | try: | ||
for f in backupfiles: | ||||
if opener.exists(f): | ||||
opener.unlink(f) | ||||
Pierre-Yves David
|
r26587 | except (IOError, OSError, error.Abort) as inst: | ||
Pierre-Yves David
|
r23312 | # only pure backup file remains, it is sage to ignore any error | ||
pass | ||||
Henrik Stuart
|
r8294 | |||
Eric Hopper
|
r1559 | class transaction(object): | ||
Pierre-Yves David
|
r23903 | def __init__(self, report, opener, vfsmap, journalname, undoname=None, | ||
FUJIWARA Katsunori
|
r26576 | after=None, createmode=None, validator=None, releasefn=None): | ||
Durham Goode
|
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
|
r26576 | * `releasefn`: called after releasing (with transaction and result) | ||
Durham Goode
|
r20881 | """ | ||
mason@suse.com
|
r1806 | self.count = 1 | ||
Ronny Pfannschmidt
|
r11230 | self.usages = 1 | ||
mpm@selenic.com
|
r582 | self.report = report | ||
Pierre-Yves David
|
r23310 | # a vfs to the store content | ||
mpm@selenic.com
|
r0 | self.opener = opener | ||
Pierre-Yves David
|
r23310 | # a map to access file in various {location -> vfs} | ||
vfsmap = vfsmap.copy() | ||||
vfsmap[''] = opener # set default value | ||||
self._vfsmap = vfsmap | ||||
mpm@selenic.com
|
r95 | self.after = after | ||
mpm@selenic.com
|
r0 | self.entries = [] | ||
Pierre-Yves David
|
r23249 | self.map = {} | ||
Pierre-Yves David
|
r23901 | self.journal = journalname | ||
Pierre-Yves David
|
r23903 | self.undoname = undoname | ||
Pierre-Yves David
|
r23279 | self._queue = [] | ||
Pierre-Yves David
|
r24283 | # A callback to validate transaction content before closing it. | ||
# should raise exception is anything is wrong. | ||||
# target user is repository hooks. | ||||
if validator is None: | ||||
validator = lambda tr: None | ||||
self.validator = validator | ||||
FUJIWARA Katsunori
|
r26576 | # A callback to do something just after releasing transaction. | ||
if releasefn is None: | ||||
releasefn = lambda tr, success: None | ||||
self.releasefn = releasefn | ||||
Pierre-Yves David
|
r23279 | # a dict of arguments to be passed to hooks | ||
self.hookargs = {} | ||||
self.file = opener.open(self.journal, "w") | ||||
Pierre-Yves David
|
r23309 | # a list of ('location', 'path', 'backuppath', cache) entries. | ||
Pierre-Yves David
|
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
|
r23249 | self._backupentries = [] | ||
self._backupmap = {} | ||||
Pierre-Yves David
|
r23901 | self._backupjournal = "%s.backupfiles" % self.journal | ||
Pierre-Yves David
|
r23249 | self._backupsfile = opener.open(self._backupjournal, 'w') | ||
self._backupsfile.write('%d\n' % version) | ||||
Pierre-Yves David
|
r23279 | |||
Alexis S. L. Carvalho
|
r6065 | if createmode is not None: | ||
Gregory Szorc
|
r25658 | opener.chmod(self.journal, createmode & 0o666) | ||
opener.chmod(self._backupjournal, createmode & 0o666) | ||||
mpm@selenic.com
|
r0 | |||
Pierre-Yves David
|
r22078 | # hold file generations to be performed on commit | ||
self._filegenerators = {} | ||||
Mads Kiilerich
|
r23543 | # hold callback to write pending data for hooks | ||
Pierre-Yves David
|
r23202 | self._pendingcallback = {} | ||
# True is any pending data have been written ever | ||||
self._anypending = False | ||||
Pierre-Yves David
|
r23204 | # holds callback to call when writing the transaction | ||
self._finalizecallback = {} | ||||
Mads Kiilerich
|
r23543 | # hold callback for post transaction close | ||
Pierre-Yves David
|
r23220 | self._postclosecallback = {} | ||
Gregory Szorc
|
r23764 | # holds callbacks to call during abort | ||
self._abortcallback = {} | ||||
Pierre-Yves David
|
r22078 | |||
mpm@selenic.com
|
r0 | def __del__(self): | ||
mpm@selenic.com
|
r558 | if self.journal: | ||
Sune Foldager
|
r9693 | self._abort() | ||
mpm@selenic.com
|
r0 | |||
Henrik Stuart
|
r8289 | @active | ||
Henrik Stuart
|
r8363 | def startgroup(self): | ||
Pierre-Yves David
|
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
|
r23251 | self._queue.append([]) | ||
Henrik Stuart
|
r8363 | |||
@active | ||||
def endgroup(self): | ||||
Pierre-Yves David
|
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
|
r8363 | q = self._queue.pop() | ||
Pierre-Yves David
|
r23253 | for f, o, data in q: | ||
self._addentry(f, o, data) | ||||
Henrik Stuart
|
r8363 | |||
@active | ||||
Chris Mason
|
r2084 | def add(self, file, offset, data=None): | ||
Pierre-Yves David
|
r23252 | """record the state of an append-only file before update""" | ||
Pierre-Yves David
|
r23249 | if file in self.map or file in self._backupmap: | ||
Matt Mackall
|
r10282 | return | ||
Henrik Stuart
|
r8363 | if self._queue: | ||
Pierre-Yves David
|
r23251 | self._queue[-1].append((file, offset, data)) | ||
Henrik Stuart
|
r8363 | return | ||
Pierre-Yves David
|
r23253 | self._addentry(file, offset, data) | ||
def _addentry(self, file, offset, data): | ||||
"""add a append-only entry to memory and on-disk state""" | ||||
if file in self.map or file in self._backupmap: | ||||
return | ||||
Chris Mason
|
r2084 | self.entries.append((file, offset, data)) | ||
self.map[file] = len(self.entries) - 1 | ||||
mpm@selenic.com
|
r0 | # add enough data to the journal to do the truncate | ||
self.file.write("%s\0%d\n" % (file, offset)) | ||||
self.file.flush() | ||||
Henrik Stuart
|
r8289 | @active | ||
Pierre-Yves David
|
r23316 | def addbackup(self, file, hardlink=True, location=''): | ||
Durham Goode
|
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 | ||||
""" | ||||
Pierre-Yves David
|
r23251 | if self._queue: | ||
msg = 'cannot use transaction.addbackup inside "group"' | ||||
Jun Wu
|
r31648 | raise error.ProgrammingError(msg) | ||
Durham Goode
|
r20882 | |||
Pierre-Yves David
|
r23249 | if file in self.map or file in self._backupmap: | ||
Durham Goode
|
r20882 | return | ||
Pierre-Yves David
|
r23582 | vfs = self._vfsmap[location] | ||
dirname, filename = vfs.split(file) | ||||
Pierre-Yves David
|
r23315 | backupfilename = "%s.backup.%s" % (self.journal, filename) | ||
Pierre-Yves David
|
r23581 | backupfile = vfs.reljoin(dirname, backupfilename) | ||
Pierre-Yves David
|
r22663 | if vfs.exists(file): | ||
filepath = vfs.join(file) | ||||
Pierre-Yves David
|
r23314 | backuppath = vfs.join(backupfile) | ||
Pierre-Yves David
|
r23900 | util.copyfile(filepath, backuppath, hardlink=hardlink) | ||
Durham Goode
|
r20882 | else: | ||
Pierre-Yves David
|
r23278 | backupfile = '' | ||
Durham Goode
|
r20882 | |||
Pierre-Yves David
|
r23316 | self._addbackupentry((location, file, backupfile, False)) | ||
Pierre-Yves David
|
r23283 | |||
def _addbackupentry(self, entry): | ||||
"""register a new backup entry and write it to disk""" | ||||
self._backupentries.append(entry) | ||||
Pierre-Yves David
|
r25294 | self._backupmap[entry[1]] = len(self._backupentries) - 1 | ||
Pierre-Yves David
|
r23309 | self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry) | ||
Pierre-Yves David
|
r23249 | self._backupsfile.flush() | ||
Durham Goode
|
r20882 | |||
@active | ||||
Pierre-Yves David
|
r23354 | def registertmp(self, tmpfile, location=''): | ||
Pierre-Yves David
|
r23291 | """register a temporary transaction file | ||
Matt Mackall
|
r23355 | Such files will be deleted when the transaction exits (on both | ||
failure and success). | ||||
Pierre-Yves David
|
r23291 | """ | ||
Pierre-Yves David
|
r23354 | self._addbackupentry((location, '', tmpfile, False)) | ||
Pierre-Yves David
|
r23291 | |||
@active | ||||
Pierre-Yves David
|
r23317 | def addfilegenerator(self, genid, filenames, genfunc, order=0, | ||
location=''): | ||||
Pierre-Yves David
|
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
|
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
|
r23543 | one of the key of the `transaction.vfsmap` dictionary. | ||
Pierre-Yves David
|
r22078 | """ | ||
Pierre-Yves David
|
r22663 | # For now, we are unable to do proper backup and restore of custom vfs | ||
# but for bookmarks that are handled outside this mechanism. | ||||
Pierre-Yves David
|
r23317 | self._filegenerators[genid] = (order, filenames, genfunc, location) | ||
Pierre-Yves David
|
r22078 | |||
Pierre-Yves David
|
r29297 | def _generatefiles(self, suffix='', group=gengroupall): | ||
Pierre-Yves David
|
r23102 | # write files registered for generation | ||
Pierre-Yves David
|
r23357 | any = False | ||
Durham Goode
|
r28830 | for id, entry in sorted(self._filegenerators.iteritems()): | ||
Pierre-Yves David
|
r23357 | any = True | ||
Pierre-Yves David
|
r23317 | order, filenames, genfunc, location = entry | ||
Durham Goode
|
r28830 | |||
# for generation at closing, check if it's before or after finalize | ||||
Pierre-Yves David
|
r29297 | postfinalize = group == gengrouppostfinalize | ||
if (group != gengroupall and | ||||
Durham Goode
|
r28830 | (id in postfinalizegenerators) != (postfinalize)): | ||
continue | ||||
Pierre-Yves David
|
r23317 | vfs = self._vfsmap[location] | ||
Pierre-Yves David
|
r23102 | files = [] | ||
try: | ||||
for name in filenames: | ||||
Pierre-Yves David
|
r23356 | name += suffix | ||
if suffix: | ||||
self.registertmp(name, location=location) | ||||
else: | ||||
self.addbackup(name, location=location) | ||||
FUJIWARA Katsunori
|
r29299 | files.append(vfs(name, 'w', atomictemp=True, | ||
checkambig=not suffix)) | ||||
Pierre-Yves David
|
r23102 | genfunc(*files) | ||
finally: | ||||
for f in files: | ||||
f.close() | ||||
Pierre-Yves David
|
r23357 | return any | ||
Pierre-Yves David
|
r23102 | |||
Pierre-Yves David
|
r22078 | @active | ||
Chris Mason
|
r2084 | def find(self, file): | ||
if file in self.map: | ||||
return self.entries[self.map[file]] | ||||
Pierre-Yves David
|
r23249 | if file in self._backupmap: | ||
return self._backupentries[self._backupmap[file]] | ||||
Chris Mason
|
r2084 | return None | ||
Henrik Stuart
|
r8289 | @active | ||
Chris Mason
|
r2084 | def replace(self, file, offset, data=None): | ||
Henrik Stuart
|
r8363 | ''' | ||
replace can only replace already committed entries | ||||
that are not pending in the queue | ||||
''' | ||||
Chris Mason
|
r2084 | if file not in self.map: | ||
raise KeyError(file) | ||||
index = self.map[file] | ||||
self.entries[index] = (file, offset, data) | ||||
self.file.write("%s\0%d\n" % (file, offset)) | ||||
self.file.flush() | ||||
Henrik Stuart
|
r8289 | @active | ||
mason@suse.com
|
r1806 | def nest(self): | ||
self.count += 1 | ||||
Ronny Pfannschmidt
|
r11230 | self.usages += 1 | ||
mason@suse.com
|
r1806 | return self | ||
Ronny Pfannschmidt
|
r11230 | def release(self): | ||
if self.count > 0: | ||||
self.usages -= 1 | ||||
Patrick Mezard
|
r11685 | # if the transaction scopes are left without being closed, fail | ||
Ronny Pfannschmidt
|
r11230 | if self.count > 0 and self.usages == 0: | ||
self._abort() | ||||
Bryan O'Sullivan
|
r27862 | def __enter__(self): | ||
return self | ||||
def __exit__(self, exc_type, exc_val, exc_tb): | ||||
Durham Goode
|
r27924 | try: | ||
if exc_type is None: | ||||
self.close() | ||||
finally: | ||||
self.release() | ||||
Bryan O'Sullivan
|
r27862 | |||
mason@suse.com
|
r1806 | def running(self): | ||
return self.count > 0 | ||||
Pierre-Yves David
|
r23202 | def addpending(self, category, callback): | ||
"""add a callback to be called when the transaction is pending | ||||
Pierre-Yves David
|
r23280 | The transaction will be given as callback's first argument. | ||
Pierre-Yves David
|
r23202 | Category is a unique identifier to allow overwriting an old callback | ||
with a newer callback. | ||||
""" | ||||
self._pendingcallback[category] = callback | ||||
@active | ||||
def writepending(self): | ||||
'''write pending file to temporary version | ||||
This is used to allow hooks to view a transaction before commit''' | ||||
categories = sorted(self._pendingcallback) | ||||
for cat in categories: | ||||
# remove callback since the data will have been flushed | ||||
Pierre-Yves David
|
r23280 | any = self._pendingcallback.pop(cat)(self) | ||
Pierre-Yves David
|
r23202 | self._anypending = self._anypending or any | ||
Pierre-Yves David
|
r23358 | self._anypending |= self._generatefiles(suffix='.pending') | ||
Pierre-Yves David
|
r23202 | return self._anypending | ||
Henrik Stuart
|
r8289 | @active | ||
Pierre-Yves David
|
r23204 | def addfinalize(self, category, callback): | ||
"""add a callback to be called when the transaction is closed | ||||
Pierre-Yves David
|
r23281 | The transaction will be given as callback's first argument. | ||
Pierre-Yves David
|
r23204 | Category is a unique identifier to allow overwriting old callbacks with | ||
newer callbacks. | ||||
""" | ||||
self._finalizecallback[category] = callback | ||||
@active | ||||
Pierre-Yves David
|
r23220 | def addpostclose(self, category, callback): | ||
"""add a callback to be called after the transaction is closed | ||||
Pierre-Yves David
|
r23282 | The transaction will be given as callback's first argument. | ||
Pierre-Yves David
|
r23220 | Category is a unique identifier to allow overwriting an old callback | ||
with a newer callback. | ||||
""" | ||||
self._postclosecallback[category] = callback | ||||
@active | ||||
Gregory Szorc
|
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 | ||||
mpm@selenic.com
|
r0 | def close(self): | ||
Greg Ward
|
r9220 | '''commit the transaction''' | ||
Pierre-Yves David
|
r23290 | if self.count == 1: | ||
Pierre-Yves David
|
r24283 | self.validator(self) # will raise exception if needed | ||
Pierre-Yves David
|
r29297 | self._generatefiles(group=gengroupprefinalize) | ||
Pierre-Yves David
|
r23204 | categories = sorted(self._finalizecallback) | ||
for cat in categories: | ||||
Pierre-Yves David
|
r23281 | self._finalizecallback[cat](self) | ||
Gregory Szorc
|
r28960 | # Prevent double usage and help clear cycles. | ||
self._finalizecallback = None | ||||
Pierre-Yves David
|
r29297 | self._generatefiles(group=gengrouppostfinalize) | ||
Durham Goode
|
r20881 | |||
mason@suse.com
|
r1806 | self.count -= 1 | ||
if self.count != 0: | ||||
return | ||||
mpm@selenic.com
|
r0 | self.file.close() | ||
Pierre-Yves David
|
r23249 | self._backupsfile.close() | ||
Pierre-Yves David
|
r23291 | # cleanup temporary files | ||
Pierre-Yves David
|
r23312 | for l, f, b, c in self._backupentries: | ||
if l not in self._vfsmap and c: | ||||
Matt Mackall
|
r26754 | self.report("couldn't remove %s: unknown cache location %s\n" | ||
Pierre-Yves David
|
r23312 | % (b, l)) | ||
continue | ||||
Pierre-Yves David
|
r23311 | vfs = self._vfsmap[l] | ||
if not f and b and vfs.exists(b): | ||||
Pierre-Yves David
|
r23312 | try: | ||
vfs.unlink(b) | ||||
Pierre-Yves David
|
r26587 | except (IOError, OSError, error.Abort) as inst: | ||
Pierre-Yves David
|
r23312 | if not c: | ||
raise | ||||
# Abort may be raise by read only opener | ||||
Matt Mackall
|
r26754 | self.report("couldn't remove %s: %s\n" | ||
Pierre-Yves David
|
r23312 | % (vfs.join(b), inst)) | ||
mpm@selenic.com
|
r0 | self.entries = [] | ||
Pierre-Yves David
|
r23904 | self._writeundo() | ||
mpm@selenic.com
|
r95 | if self.after: | ||
mpm@selenic.com
|
r785 | self.after() | ||
FUJIWARA Katsunori
|
r26753 | if self.opener.isfile(self._backupjournal): | ||
self.opener.unlink(self._backupjournal) | ||||
FUJIWARA Katsunori
|
r20087 | if self.opener.isfile(self.journal): | ||
self.opener.unlink(self.journal) | ||||
Martin von Zweigbergk
|
r27662 | for l, _f, b, c in self._backupentries: | ||
if l not in self._vfsmap and c: | ||||
self.report("couldn't remove %s: unknown cache location" | ||||
"%s\n" % (b, l)) | ||||
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 | ||||
self.report("couldn't remove %s: %s\n" | ||||
% (vfs.join(b), inst)) | ||||
Pierre-Yves David
|
r23249 | self._backupentries = [] | ||
mpm@selenic.com
|
r573 | self.journal = None | ||
FUJIWARA Katsunori
|
r26576 | |||
self.releasefn(self, True) # notify success of closing transaction | ||||
Pierre-Yves David
|
r23220 | # run post close action | ||
categories = sorted(self._postclosecallback) | ||||
for cat in categories: | ||||
Pierre-Yves David
|
r23282 | self._postclosecallback[cat](self) | ||
Gregory Szorc
|
r28960 | # Prevent double usage and help clear cycles. | ||
self._postclosecallback = None | ||||
mpm@selenic.com
|
r0 | |||
Henrik Stuart
|
r8289 | @active | ||
mpm@selenic.com
|
r0 | def abort(self): | ||
Greg Ward
|
r9220 | '''abort the transaction (generally called on error, or when the | ||
transaction is not explicitly committed before going out of | ||||
scope)''' | ||||
Henrik Stuart
|
r8289 | self._abort() | ||
Pierre-Yves David
|
r23904 | def _writeundo(self): | ||
"""write transaction data for possible future undo call""" | ||||
if self.undoname is None: | ||||
return | ||||
undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w') | ||||
undobackupfile.write('%d\n' % version) | ||||
for l, f, b, c in self._backupentries: | ||||
if not f: # temporary file | ||||
continue | ||||
if not b: | ||||
u = '' | ||||
else: | ||||
if l not in self._vfsmap and c: | ||||
Matt Mackall
|
r26754 | self.report("couldn't remove %s: unknown cache location" | ||
Pierre-Yves David
|
r23904 | "%s\n" % (b, l)) | ||
continue | ||||
vfs = self._vfsmap[l] | ||||
base, name = vfs.split(b) | ||||
assert name.startswith(self.journal), name | ||||
uname = name.replace(self.journal, self.undoname, 1) | ||||
u = vfs.reljoin(base, uname) | ||||
util.copyfile(vfs.join(b), vfs.join(u), hardlink=True) | ||||
undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c)) | ||||
undobackupfile.close() | ||||
Henrik Stuart
|
r8289 | def _abort(self): | ||
Henrik Stuart
|
r8290 | self.count = 0 | ||
Ronny Pfannschmidt
|
r11230 | self.usages = 0 | ||
Henrik Stuart
|
r8290 | self.file.close() | ||
Pierre-Yves David
|
r23249 | self._backupsfile.close() | ||
Henrik Stuart
|
r8290 | |||
Benoit Boissinot
|
r10228 | try: | ||
Pierre-Yves David
|
r23249 | if not self.entries and not self._backupentries: | ||
FUJIWARA Katsunori
|
r26753 | if self._backupjournal: | ||
self.opener.unlink(self._backupjournal) | ||||
Benoit Boissinot
|
r10228 | if self.journal: | ||
FUJIWARA Katsunori
|
r20087 | self.opener.unlink(self.journal) | ||
Benoit Boissinot
|
r10228 | return | ||
mpm@selenic.com
|
r0 | |||
Benoit Boissinot
|
r10228 | self.report(_("transaction abort!\n")) | ||
mpm@selenic.com
|
r0 | |||
mpm@selenic.com
|
r108 | try: | ||
Gregory Szorc
|
r23764 | for cat in sorted(self._abortcallback): | ||
self._abortcallback[cat](self) | ||||
Gregory Szorc
|
r28960 | # Prevent double usage and help clear cycles. | ||
self._abortcallback = None | ||||
Pierre-Yves David
|
r23311 | _playback(self.journal, self.report, self.opener, self._vfsmap, | ||
Pierre-Yves David
|
r23249 | self.entries, self._backupentries, False) | ||
Henrik Stuart
|
r8294 | self.report(_("rollback completed\n")) | ||
Pierre-Yves David
|
r25183 | except BaseException: | ||
Henrik Stuart
|
r8294 | self.report(_("rollback failed - please run hg recover\n")) | ||
finally: | ||||
self.journal = None | ||||
FUJIWARA Katsunori
|
r26576 | self.releasefn(self, False) # notify failure of transaction | ||
Henrik Stuart
|
r8290 | |||
Pierre-Yves David
|
r23311 | def rollback(opener, vfsmap, file, report): | ||
Durham Goode
|
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. | ||||
""" | ||||
Henrik Stuart
|
r8294 | entries = [] | ||
Durham Goode
|
r20882 | backupentries = [] | ||
Henrik Stuart
|
r8294 | |||
FUJIWARA Katsunori
|
r20087 | fp = opener.open(file) | ||
Dan Villiom Podlaski Christiansen
|
r13400 | lines = fp.readlines() | ||
fp.close() | ||||
for l in lines: | ||||
Matt Mackall
|
r20524 | try: | ||
f, o = l.split('\0') | ||||
entries.append((f, int(o), None)) | ||||
except ValueError: | ||||
report(_("couldn't read journal entry %r!\n") % l) | ||||
mpm@selenic.com
|
r0 | |||
Durham Goode
|
r20882 | backupjournal = "%s.backupfiles" % file | ||
if opener.exists(backupjournal): | ||||
fp = opener.open(backupjournal) | ||||
Durham Goode
|
r23065 | lines = fp.readlines() | ||
if lines: | ||||
ver = lines[0][:-1] | ||||
Durham Goode
|
r23064 | if ver == str(version): | ||
Durham Goode
|
r23065 | for line in lines[1:]: | ||
if line: | ||||
# Shave off the trailing newline | ||||
line = line[:-1] | ||||
Pierre-Yves David
|
r23309 | l, f, b, c = line.split('\0') | ||
backupentries.append((l, f, b, bool(c))) | ||||
Durham Goode
|
r23064 | else: | ||
Pierre-Yves David
|
r23309 | report(_("journal was created by a different version of " | ||
Michael O'Connor
|
r24721 | "Mercurial\n")) | ||
Durham Goode
|
r20882 | |||
Pierre-Yves David
|
r23311 | _playback(file, report, opener, vfsmap, entries, backupentries) | ||