transaction.py
176 lines
| 4.9 KiB
| text/x-python
|
PythonLexer
/ mercurial / transaction.py
mpm@selenic.com
|
r0 | # transaction.py - simple journalling 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. | ||||
# | ||||
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 | |||
Matt Mackall
|
r3891 | from i18n import _ | ||
Benoit Boissinot
|
r7335 | import os, errno | ||
Henrik Stuart
|
r8289 | import error | ||
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 | |||
Henrik Stuart
|
r8294 | def _playback(journal, report, opener, entries, unlink=True): | ||
for f, o, ignore in entries: | ||||
if o or not unlink: | ||||
try: | ||||
opener(f, 'a').truncate(o) | ||||
Benoit Boissinot
|
r9686 | except IOError: | ||
Henrik Stuart
|
r8294 | report(_("failed to truncate %s\n") % f) | ||
raise | ||||
else: | ||||
try: | ||||
fn = opener(f).name | ||||
os.unlink(fn) | ||||
Benoit Boissinot
|
r9686 | except (IOError, OSError), inst: | ||
Henrik Stuart
|
r8294 | if inst.errno != errno.ENOENT: | ||
raise | ||||
os.unlink(journal) | ||||
Eric Hopper
|
r1559 | class transaction(object): | ||
Alexis S. L. Carvalho
|
r6065 | def __init__(self, report, opener, journal, after=None, createmode=None): | ||
mason@suse.com
|
r1806 | self.count = 1 | ||
Ronny Pfannschmidt
|
r11230 | self.usages = 1 | ||
mpm@selenic.com
|
r582 | self.report = report | ||
mpm@selenic.com
|
r0 | self.opener = opener | ||
mpm@selenic.com
|
r95 | self.after = after | ||
mpm@selenic.com
|
r0 | self.entries = [] | ||
mpm@selenic.com
|
r42 | self.map = {} | ||
mpm@selenic.com
|
r0 | self.journal = journal | ||
Henrik Stuart
|
r8363 | self._queue = [] | ||
mpm@selenic.com
|
r0 | |||
self.file = open(self.journal, "w") | ||||
Alexis S. L. Carvalho
|
r6065 | if createmode is not None: | ||
os.chmod(self.journal, createmode & 0666) | ||||
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): | ||
self._queue.append([]) | ||||
@active | ||||
def endgroup(self): | ||||
q = self._queue.pop() | ||||
d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q]) | ||||
self.entries.extend(q) | ||||
self.file.write(d) | ||||
self.file.flush() | ||||
@active | ||||
Chris Mason
|
r2084 | def add(self, file, offset, data=None): | ||
Matt Mackall
|
r10282 | if file in self.map: | ||
return | ||||
Henrik Stuart
|
r8363 | if self._queue: | ||
self._queue[-1].append((file, offset, data)) | ||||
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 | ||
Chris Mason
|
r2084 | def find(self, file): | ||
if file in self.map: | ||||
return self.entries[self.map[file]] | ||||
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 | ||||
# of the transaction scopes are left without being closed, fail | ||||
if self.count > 0 and self.usages == 0: | ||||
self._abort() | ||||
mason@suse.com
|
r1806 | def running(self): | ||
return self.count > 0 | ||||
Henrik Stuart
|
r8289 | @active | ||
mpm@selenic.com
|
r0 | def close(self): | ||
Greg Ward
|
r9220 | '''commit the transaction''' | ||
mason@suse.com
|
r1806 | self.count -= 1 | ||
if self.count != 0: | ||||
return | ||||
mpm@selenic.com
|
r0 | self.file.close() | ||
self.entries = [] | ||||
mpm@selenic.com
|
r95 | if self.after: | ||
mpm@selenic.com
|
r785 | self.after() | ||
Sune Foldager
|
r9693 | if os.path.isfile(self.journal): | ||
mpm@selenic.com
|
r95 | os.unlink(self.journal) | ||
mpm@selenic.com
|
r573 | self.journal = 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() | ||
def _abort(self): | ||||
Henrik Stuart
|
r8290 | self.count = 0 | ||
Ronny Pfannschmidt
|
r11230 | self.usages = 0 | ||
Henrik Stuart
|
r8290 | self.file.close() | ||
Benoit Boissinot
|
r10228 | try: | ||
if not self.entries: | ||||
if self.journal: | ||||
os.unlink(self.journal) | ||||
return | ||||
mpm@selenic.com
|
r0 | |||
Benoit Boissinot
|
r10228 | self.report(_("transaction abort!\n")) | ||
mpm@selenic.com
|
r0 | |||
mpm@selenic.com
|
r108 | try: | ||
Matt Mackall
|
r10282 | _playback(self.journal, self.report, self.opener, | ||
self.entries, False) | ||||
Henrik Stuart
|
r8294 | self.report(_("rollback completed\n")) | ||
mpm@selenic.com
|
r108 | except: | ||
Henrik Stuart
|
r8294 | self.report(_("rollback failed - please run hg recover\n")) | ||
finally: | ||||
self.journal = None | ||||
Henrik Stuart
|
r8290 | |||
Henrik Stuart
|
r8294 | def rollback(opener, file, report): | ||
entries = [] | ||||
mpm@selenic.com
|
r162 | for l in open(file).readlines(): | ||
f, o = l.split('\0') | ||||
Henrik Stuart
|
r8294 | entries.append((f, int(o), None)) | ||
mpm@selenic.com
|
r0 | |||
Henrik Stuart
|
r8294 | _playback(file, report, opener, entries) | ||