Patchwork [2,of,3,main-line-of-work] transaction: allow to register temporary transaction file

mail settings
Submitter Pierre-Yves David
Date Nov. 13, 2014, 5:05 p.m.
Message ID <>
Download mbox | patch
Permalink /patch/6712/
State Accepted
Headers show


Pierre-Yves David - Nov. 13, 2014, 5:05 p.m.
# HG changeset patch
# User Pierre-Yves David <>
# Date 1415179628 0
#      Wed Nov 05 09:27:08 2014 +0000
# Node ID 979d2d237821deea966ed077d2c2d42e44927990
# Parent  d6bae78a0cd5293b9f666909ebac799fc84e43fd
transaction: allow to register temporary transaction file

During the transaction, files may be created to store or expose data involved in
the transaction. (eg: changelog index data are written in a 'changelog.i.a' for
hooks). But we do not have an official way to record such file creation and make
sure they are cleaned up. The lack of clean-up is currently okay because there
is a single file involved and a single producer/consumer.

However, as we want to expose more data (bookmarks, phases, obsmarker) we need
something more solid. The 'backupentries' mechanism could handle that. Temporary
file can be encoded as a backup of nothing '('', <temporarypath>)'. We "need" to
attach it to the same mechanism as we to be able to use temporary transaction
file outside of .'store/' and 'backupentries' is expected to gain such feature.

This changeset make it clean that we should rename 'backupentries' to something
more generic.


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -42,32 +42,34 @@  def _playback(journal, report, opener, e
                 if inst.errno != errno.ENOENT:
     backupfiles = []
     for f, b in backupentries:
-        if b:
+        if f and b:
             filepath = opener.join(f)
             backuppath = opener.join(b)
                 util.copyfile(backuppath, filepath)
             except IOError:
                 report(_("failed to recover %s\n") % f)
+            target = f or b
-                opener.unlink(f)
+                opener.unlink(target)
             except (IOError, OSError), inst:
                 if inst.errno != errno.ENOENT:
     backuppath = "%s.backupfiles" % journal
     if opener.exists(backuppath):
     for f in backupfiles:
-        opener.unlink(f)
+        if opener.exists(f):
+            opener.unlink(f)
 class transaction(object):
     def __init__(self, report, opener, journal, after=None, createmode=None,
             onclose=None, onabort=None):
         """Begin a new transaction
@@ -97,10 +99,11 @@  class transaction(object):
         self.hookargs = {}
         self.file =, "w")
         # a list of ('path', 'backuppath') entries.
         # if 'backuppath' is empty, no file existed at backup time
+        # if 'path' is empty, this is a temporary transaction file
         self._backupentries = []
         self._backupmap = {}
         self._backupjournal = "%s.backupfiles" % journal
         self._backupsfile =, 'w')
         self._backupsfile.write('%d\n' % version)
@@ -198,10 +201,19 @@  class transaction(object):
         self._backupmap[file] = len(self._backupentries) - 1
         self._backupsfile.write("%s\0%s\n" % entry)
+    def registertmp(self, tmpfile):
+        """register a temporary transaction file
+        Such file will be delete when the transaction exit (on both failure and
+        success).
+        """
+        self._addbackupentry(('', tmpfile))
+    @active
     def addfilegenerator(self, genid, filenames, genfunc, order=0, vfs=None):
         """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.
@@ -340,19 +352,23 @@  class transaction(object):
         self.count -= 1
         if self.count != 0:
+        # cleanup temporary files
+        for f, b in self._backupentries:
+            if not f and b and self.opener.exists(b):
+                self.opener.unlink(b)
         self.entries = []
         if self.after:
         if self.opener.isfile(self.journal):
         if self.opener.isfile(self._backupjournal):
             for _f, b in self._backupentries:
-                if b:
+                if b and self.opener.exists(b):
         self._backupentries = []
         self.journal = None
         # run post close action
         categories = sorted(self._postclosecallback)