[PATCH 6 of 6] transaction: add atomic groups to transaction logic

Henrik Stuart hg at hstuart.dk
Wed Apr 22 02:58:48 CDT 2009


# HG changeset patch
# User Henrik Stuart <hg at hstuart.dk>
# Date 1240384802 -7200
# Node ID d5ab1e8c1d4dcb0007b692ff3957851500fc53ea
# Parent  6e2e7f523c56f0caf1a8d7400ba1e1d33919f0f1
transaction: add atomic groups to transaction logic

When performing a strip operation on a repository it is vital that all the
truncations are performed, or that none of them are. This is done by adding
atomic blocks to the journal. If a partial atomic block is written, and they
can be nested, then it will not be executed on a hg repair, and a warning
will be written that the journal contains a partial atomic block.

Co-contributor: Sune Foldager <cryo at cyanite.org>

diff -r 6e2e7f523c56 -r d5ab1e8c1d4d mercurial/repair.py
--- a/mercurial/repair.py	Wed Apr 22 09:20:01 2009 +0200
+++ b/mercurial/repair.py	Wed Apr 22 09:20:02 2009 +0200
@@ -9,6 +9,7 @@
 import changegroup, os
 from node import nullrev, short
 from i18n import _
+from transaction import transaction
 
 def _bundle(repo, bases, heads, node, suffix, extranodes=None):
     """create a bundle with the specified revisions as a backup"""
@@ -124,13 +125,16 @@
     tr = repo.transaction()
     offset = len(tr.entries)
 
+    tr.start_atomic()
     cl.strip(striprev, tr)
     mfst.strip(striprev, tr)
     for f in fs:
         f.strip(striprev, tr)
+    tr.end_atomic()
+    tr.flush()
 
     try:
-        for i in xrange(offset, len(tr.entries)):
+        for i in xrange(offset + 1, len(tr.entries) - 1):
             file, troffset, ignore = tr.entries[i]
             repo.sopener(file, 'a').truncate(troffset)
         tr.close()
diff -r 6e2e7f523c56 -r d5ab1e8c1d4d mercurial/transaction.py
--- a/mercurial/transaction.py	Wed Apr 22 09:20:01 2009 +0200
+++ b/mercurial/transaction.py	Wed Apr 22 09:20:02 2009 +0200
@@ -25,16 +25,40 @@
     return _active
 
 def _journal_playback(journal, opener, entries, unlink=True):
-    for f, o, ignore in entries:
-        if o or not unlink:
-            opener(f, 'a').truncate(o)
+    i, end = 0, len(entries)
+
+    def find_atomic_end(offset):
+        count = 0
+        while offset < end:
+            if entries[offset] == transaction.atomic_start:
+                count += 1
+            elif entries[offset] == transaction.atomic_end:
+                count -= 1
+                if count == 0:
+                    return offset
+            offset += 1
+        return -1
+
+    while i < end:
+        if entries[i] == transaction.atomic_start:
+            atomic_end = find_atomic_end(i)
+            if atomic_end == -1:
+                raise error.Abort(_('incomplete atomic section in journal - will not replay\n'))
+        elif entries[i] == transaction.atomic_end:
+            pass
         else:
-            try:
-                fn = opener(f).name
-                os.unlink(fn)
-            except OSError, inst:
-                if inst.errno != errno.ENOENT:
-                    raise
+            f, o, ignore = entries[i]
+            if o or not unlink:
+                opener(f, 'a').truncate(o)
+            else:
+                try:
+                    fn = opener(f).name
+                    os.unlink(fn)
+                except OSError, inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
+
+        i += 1
 
     os.unlink(journal)
 
@@ -59,6 +83,19 @@
             if self.entries: self._abort()
             self.file.close()
 
+    atomic_start = 'start atomic\n'
+    atomic_end = 'end atomic\n'
+
+    @active
+    def start_atomic(self):
+        self.entries.append(transaction.atomic_start)
+        self._write(transaction.atomic_start)
+
+    @active
+    def end_atomic(self):
+        self.entries.append(transaction.atomic_end)
+        self._write(transaction.atomic_end)
+
     @active
     def add(self, file, offset, data=None):
         if file in self.map: return
@@ -135,7 +172,10 @@
     entries = []
 
     for l in open(file).readlines():
-        f, o = l.split('\0')
-        entries.append((f, int(o), None))
+        if l in [transaction.atomic_start, transaction.atomic_end]:
+            entries.append(l)
+        else:
+            f, o = l.split('\0')
+            entries.append((f, int(o), None))
 
     _journal_playback(file, opener, entries)
diff -r 6e2e7f523c56 -r d5ab1e8c1d4d tests/test-repair-strip.out
--- a/tests/test-repair-strip.out	Wed Apr 22 09:20:01 2009 +0200
+++ b/tests/test-repair-strip.out	Wed Apr 22 09:20:02 2009 +0200
@@ -21,10 +21,12 @@
 2 warnings encountered!
 2 integrity errors encountered!
 % journal contents
+start atomic
 00changelog.i
 00manifest.i
 data/b.i
 data/c.i
+end atomic
 rolling back interrupted transaction
 checking changesets
 checking manifests
@@ -73,10 +75,12 @@
 5 integrity errors encountered!
 (first damaged changeset appears to be 3)
 % journal contents
+start atomic
 00changelog.i
 00manifest.i
 data/b.i
 data/c.i
+end atomic
 rolling back interrupted transaction
 checking changesets
 checking manifests


More information about the Mercurial-devel mailing list