[PATCH 4 of 7 V2] transaction: add support for non-append files
Durham Goode
durham at fb.com
Mon Mar 31 18:19:46 CDT 2014
# HG changeset patch
# User Durham Goode <durham at fb.com>
# Date 1395699711 25200
# Mon Mar 24 15:21:51 2014 -0700
# Node ID 498a1087dd60ec7234c4352e566abe977eb63332
# Parent f85f9ea96d16e180de3e38cfc468e53441f41743
transaction: add support for non-append files
This adds support for normal, non-append-only files in transactions. For
example, .hg/store/fncache and .hg/store/phaseroots should be written as part of
the transaction, but are not append only files.
This adds a journal.backupfiles along side the normal journal. This tracks which
files have been backed up as part of the transaction. transaction.addbackup()
creates a backup of the file (using a hardlink), which is later used to recover
in the event of the transaction failing.
Using a seperate journal allows the repository to still be used by older
versions of Mercurial. A future patch will use this functionality and add tests
for it.
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -12,8 +12,8 @@
# GNU General Public License version 2 or any later version.
from i18n import _
-import errno
-import error
+import errno, os
+import error, util
def active(func):
def _active(self, *args, **kwds):
@@ -24,22 +24,35 @@
return _active
def _playback(journal, report, opener, entries, unlink=True):
+ backupfiles = []
for f, o, ignore in entries:
if o or not unlink:
- try:
- fp = opener(f, 'a')
- fp.truncate(o)
- fp.close()
- except IOError:
- report(_("failed to truncate %s\n") % f)
- raise
+ if isinstance(o, int):
+ try:
+ fp = opener(f, 'a')
+ fp.truncate(o)
+ fp.close()
+ except IOError:
+ report(_("failed to truncate %s\n") % f)
+ raise
+ else:
+ fpath = opener.join(f)
+ opath = opener.join(o)
+ util.copyfile(opath, fpath)
+ backupfiles.append(o)
else:
try:
opener.unlink(f)
except (IOError, OSError), inst:
if inst.errno != errno.ENOENT:
raise
+
opener.unlink(journal)
+ backuppath = "%s.backupfiles" % journal
+ if opener.exists(backuppath):
+ opener.unlink(backuppath)
+ for f in backupfiles:
+ opener.unlink(f)
class transaction(object):
def __init__(self, report, opener, journal, after=None, createmode=None,
@@ -56,9 +69,12 @@
self.journal = journal
self._queue = []
+ self.backupfilesjournal = "%s.backupfiles" % journal
self.file = opener.open(self.journal, "w")
+ self.backupsfile = opener.open(self.backupfilesjournal, 'w')
if createmode is not None:
opener.chmod(self.journal, createmode & 0666)
+ opener.chmod(self.backupfilesjournal, createmode & 0666)
def __del__(self):
if self.journal:
@@ -71,11 +87,23 @@
@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)
+
+ offsets = []
+ backups = []
+ for f, o, _ in q:
+ if isinstance(o, int):
+ offsets.append((f, o))
+ else:
+ backups.append((f, o))
+ d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
self.file.write(d)
self.file.flush()
+ d = ''.join(['%s\0%s\0' % (f, o) for f, o in backups])
+ self.backupsfile.write(d)
+ self.backupsfile.flush()
+
@active
def add(self, file, offset, data=None):
if file in self.map:
@@ -91,6 +119,27 @@
self.file.flush()
@active
+ def addbackup(self, file):
+ if file in self.map:
+ return
+ backupfile = "journal.%s" % file
+ if self.opener.exists(file):
+ filepath = self.opener.join(file)
+ backuppath = self.opener.join(backupfile)
+ util.copyfiles(filepath, backuppath, hardlink=True)
+ else:
+ self.add(file, 0)
+ return
+
+ if self._queue:
+ self._queue[-1].append((file, backupfile))
+ return
+ self.entries.append((file, backupfile, None))
+ self.map[file] = len(self.entries) - 1
+ self.backupsfile.write("%s\0%s\0" % (file, backupfile))
+ self.backupsfile.flush()
+
+ @active
def find(self, file):
if file in self.map:
return self.entries[self.map[file]]
@@ -136,11 +185,16 @@
if self.count != 0:
return
self.file.close()
- self.entries = []
if self.after:
self.after()
if self.opener.isfile(self.journal):
self.opener.unlink(self.journal)
+ if self.opener.isfile(self.backupfilesjournal):
+ self.opener.unlink(self.backupfilesjournal)
+ for f, o, _ in self.entries:
+ if not isinstance(o, int):
+ self.opener.unlink(o)
+ self.entries = []
self.journal = None
@active
@@ -162,6 +216,7 @@
if not self.entries:
if self.journal:
self.opener.unlink(self.journal)
+ self.opener.unlink(self.backupfilesjournal)
return
self.report(_("transaction abort!\n"))
@@ -189,4 +244,14 @@
except ValueError:
report(_("couldn't read journal entry %r!\n") % l)
+ backupjournal = "%s.backupfiles" % file
+ if opener.exists(backupjournal):
+ fp = opener.open(backupjournal)
+ data = fp.read()
+ if len(data) > 0:
+ parts = data.split('\0')
+ for i in xrange(0, len(parts), 2):
+ f, o = parts[i:i + 1]
+ entries.append((f, o, None))
+
_playback(file, report, opener, entries)
More information about the Mercurial-devel
mailing list