[PATCH 3 of 5] transaction: use "location" when doing backup

Pierre-Yves David pierre-yves.david at ens-lyon.org
Sat Oct 18 06:44:55 CDT 2014


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at fb.com>
# Date 1413605075 25200
#      Fri Oct 17 21:04:35 2014 -0700
# Node ID f533386d7f84a8e5ad1885d654d5983d81302b79
# Parent  580364627689487b0c13185a28d925f676fa0526
transaction: use "location" when doing backup

Same logic here, we stop passing vfs object as arguments and use the location
identifier to backup the right file. We persist this in the transaction state
file.  As this is a format change (3 values per entry instead of 2) we change
the file name to "backupfiles2".

Note that backup of empty file is handled by the "entries" list, not
"backupentries" and will improve that in the next changeset.

Some more compatibility layer between "backupfiles" and "backupfiles2" may come
in the futur.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -915,11 +915,12 @@ class localrepository(object):
     def recover(self):
         lock = self.lock()
         try:
             if self.svfs.exists("journal"):
                 self.ui.status(_("rolling back interrupted transaction\n"))
-                transaction.rollback(self.sopener, "journal",
+                vfsmap = {'store': self.sopener, 'plain': self.opener}
+                transaction.rollback(self.sopener, vfsmap, "journal",
                                      self.ui.warn)
                 self.invalidate()
                 return True
             else:
                 self.ui.warn(_("no interrupted transaction available\n"))
@@ -971,11 +972,12 @@ class localrepository(object):
         if dryrun:
             return 0
 
         parents = self.dirstate.parents()
         self.destroying()
-        transaction.rollback(self.sopener, 'undo', ui.warn)
+        vfsmap = {'store': self.sopener, 'plain': self.opener}
+        transaction.rollback(self.sopener, vfsmap, 'undo', ui.warn)
         if self.vfs.exists('undo.bookmarks'):
             self.vfs.rename('undo.bookmarks', 'bookmarks')
         if self.svfs.exists('undo.phaseroots'):
             self.svfs.rename('undo.phaseroots', 'phaseroots')
         self.invalidate()
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -21,11 +21,12 @@ def active(func):
             raise error.Abort(_(
                 'cannot use transaction when it is already committed/aborted'))
         return func(self, *args, **kwds)
     return _active
 
-def _playback(journal, report, opener, entries, backupentries, unlink=True):
+def _playback(journal, report, opener, vfsmap, entries, backupentries,\
+              unlink=True):
     for f, o, _ignore in entries:
         if o or not unlink:
             try:
                 fp = opener(f, 'a')
                 fp.truncate(o)
@@ -39,22 +40,23 @@ def _playback(journal, report, opener, e
             except (IOError, OSError), inst:
                 if inst.errno != errno.ENOENT:
                     raise
 
     backupfiles = []
-    for f, b, _ignore in backupentries:
-        filepath = opener.join(f)
-        backuppath = opener.join(b)
+    for l, f, b, _ignore in backupentries:
+        vfs = vfsmap[l]
+        filepath = vfs.join(f)
+        backuppath = vfs.join(b)
         try:
             util.copyfile(backuppath, filepath)
             backupfiles.append(b)
         except IOError:
             report(_("failed to recover %s\n") % f)
             raise
 
     opener.unlink(journal)
-    backuppath = "%s.backupfiles" % journal
+    backuppath = "%s.backupfiles2" % journal
     if opener.exists(backuppath):
         opener.unlink(backuppath)
     for f in backupfiles:
         opener.unlink(f)
 
@@ -90,11 +92,11 @@ class transaction(object):
         self.journal = journal
         self._queue = []
         # a dict of arguments to be passed to hooks
         self.hookargs = {}
 
-        self.backupjournal = "%s.backupfiles" % journal
+        self.backupjournal = "%s.backupfiles2" % journal
         self.file = opener.open(self.journal, "w")
         self.backupsfile = opener.open(self.backupjournal, 'w')
         if createmode is not None:
             opener.chmod(self.journal, createmode & 0666)
             opener.chmod(self.backupjournal, createmode & 0666)
@@ -119,18 +121,18 @@ class transaction(object):
         offsets = []
         backups = []
         for f, o, _data in q[0]:
             offsets.append((f, o))
 
-        for f, b, _data in q[1]:
-            backups.append((f, b))
+        for l, f, b, _data in q[1]:
+            backups.append((l, f, b))
 
         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, b) for f, b in backups])
+        d = ''.join(['%s\0%s\0%s\0' % entry for entry in backups])
         self.backupsfile.write(d)
         self.backupsfile.flush()
 
     @active
     def add(self, file, offset, data=None):
@@ -145,11 +147,11 @@ class transaction(object):
         # add enough data to the journal to do the truncate
         self.file.write("%s\0%d\n" % (file, offset))
         self.file.flush()
 
     @active
-    def addbackup(self, file, hardlink=True, vfs=None):
+    def addbackup(self, file, hardlink=True, location='store'):
         """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.
@@ -159,12 +161,11 @@ class transaction(object):
         """
 
         if file in self.map or file in self.backupmap:
             return
         backupfile = "%s.backup.%s" % (self.journal, file)
-        if vfs is None:
-            vfs = self.opener
+        vfs = self._vfsmap[location]
         if vfs.exists(file):
             filepath = vfs.join(file)
             backuppath = self.opener.join(backupfile)
             util.copyfiles(filepath, backuppath, hardlink=hardlink)
         else:
@@ -173,13 +174,13 @@ class transaction(object):
 
         if self._queue:
             self._queue[-1][1].append((file, backupfile))
             return
 
-        self.backupentries.append((file, backupfile, None))
+        self.backupentries.append((location, file, backupfile, None))
         self.backupmap[file] = len(self.backupentries) - 1
-        self.backupsfile.write("%s\0%s\0" % (file, backupfile))
+        self.backupsfile.write("%s\0%s\0%s\0" % (location, file, backupfile))
         self.backupsfile.flush()
 
     @active
     def addfilegenerator(self, genid, filenames, genfunc, order=0,
                          location='store'):
@@ -278,12 +279,13 @@ class transaction(object):
             self.after()
         if self.opener.isfile(self.journal):
             self.opener.unlink(self.journal)
         if self.opener.isfile(self.backupjournal):
             self.opener.unlink(self.backupjournal)
-            for _f, b, _ignore in self.backupentries:
-                self.opener.unlink(b)
+            for l, _f, b, _ignore in self.backupentries:
+                vfs = self._vfsmap[l]
+                vfs.unlink(b)
         self.backupentries = []
         self.journal = None
 
     @active
     def abort(self):
@@ -310,20 +312,20 @@ class transaction(object):
                 return
 
             self.report(_("transaction abort!\n"))
 
             try:
-                _playback(self.journal, self.report, self.opener,
+                _playback(self.journal, self.report, self.opener, self._vfsmap,
                           self.entries, self.backupentries, False)
                 self.report(_("rollback completed\n"))
             except Exception:
                 self.report(_("rollback failed - please run hg recover\n"))
         finally:
             self.journal = None
 
 
-def rollback(opener, file, report):
+def rollback(opener, vfsmap, file, report):
     """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.
 
@@ -350,10 +352,10 @@ def rollback(opener, file, report):
     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, b = parts[i:i + 1]
-                backupentries.append((f, b, None))
+            for i in xrange(0, len(parts), 3):
+                f, b = parts[i:i + 2]
+                backupentries.append((l, f, b, None))
 
-    _playback(file, report, opener, entries, backupentries)
+    _playback(file, report, opener, vfsmap, entries, backupentries)


More information about the Mercurial-devel mailing list