[PATCH] strip: make repair.strip transactional to avoid repository corruption [revised]

Henrik Stuart hg at hstuart.dk
Thu Apr 16 08:40:21 CDT 2009


# HG changeset patch
# User Henrik Stuart <henrik.stuart at edlund.dk>
# Date 1239888843 -7200
# Node ID c58b824da951118a5ce3eb6bc8cac14cd06b93ad
# Parent  d76d5b797392122cb3040a06177b262719d817cd
strip: make repair.strip transactional to avoid repository corruption

Uses a transaction instance from the local repository to journal the
truncation of revlog files, such that if a strip only partially completes,
hg recover will be able to finish the truncate of all the files.

The potential unbundling of changes that have been backed up to be restored
later will, in case of an error, have to be unbundled manually. The
difference is that it will be possible to recover the repository state so
the unbundle can actually succeed.

diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -118,11 +118,25 @@
         chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
                             extranodes)
 
-    cl.strip(striprev)
-    repo.manifest.strip(striprev)
-    for name in files:
-        f = repo.file(name)
-        f.strip(striprev)
+    fs = [repo.file(name) for name in files]
+    mfst = repo.manifest
+
+    tr = repo.transaction()
+    offset = len(tr.entries)
+
+    cl.strip(striprev, tr)
+    mfst.strip(striprev, tr)
+    for f in fs:
+        f.strip(striprev, tr)
+
+    try:
+        for i in xrange(offset, len(tr.entries)):
+            file, troffset, ignore = tr.entries[i]
+            repo.sopener(file, 'a').truncate(troffset)
+        tr.close()
+    except:
+        tr.abort()
+        raise
 
     if saveheads or extranodes:
         ui.status(_("adding branch\n"))
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1285,7 +1285,7 @@
 
         return node
 
-    def strip(self, minlink):
+    def strip(self, minlink, transaction):
         """truncate the revlog on the first revision with a linkrev >= minlink
 
         This function is called when we're stripping revision minlink and
@@ -1314,14 +1314,12 @@
         # first truncate the files on disk
         end = self.start(rev)
         if not self._inline:
-            df = self.opener(self.datafile, "a")
-            df.truncate(end)
+            transaction.add(self.datafile, end)
             end = rev * self._io.size
         else:
             end += rev * self._io.size
 
-        indexf = self.opener(self.indexfile, "a")
-        indexf.truncate(end)
+        transaction.add(self.indexfile, end)
 
         # then reset internal state in memory to forget those revisions
         self._cache = None
diff --git a/tests/test-repair-strip b/tests/test-repair-strip
new file mode 100755
--- /dev/null
+++ b/tests/test-repair-strip
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=">> $HGRCPATH
+
+teststrip() {
+    hg -q up -C $1
+    echo % before update $1, strip $2
+    hg parents
+    chmod -$3 $4
+    hg strip $2 2>&1 | sed 's/\(saving bundle to \).*/\1/' | sed 's/Permission denied.*\.hg\/store\/\(.*\)/Permission denied \.hg\/store\/\1/'
+    echo % after update $1, strip $2
+    chmod +$3 $4
+    hg verify
+    echo % journal contents
+    cat .hg/store/journal | sed 's/\.i[^\n]*/\.i/'
+    ls .hg/store/journal >/dev/null 2>&1 && hg recover
+    ls .hg/strip-backup/* >/dev/null 2>&1 && hg unbundle -q .hg/strip-backup/*
+    rm -rf .hg/strip-backup
+}
+
+hg init test
+cd test
+
+echo a > a
+hg -q ci -m "a" -A
+
+echo b > b
+hg -q ci -m "b" -A
+
+echo c > c
+hg -q ci -m "c" -A
+
+teststrip 0 1 w .hg/store/data/b.i
+teststrip 0 1 r .hg/store/data/b.i
+teststrip 0 1 w .hg/store/00changelog.i
diff --git a/tests/test-repair-strip.out b/tests/test-repair-strip.out
new file mode 100644
--- /dev/null
+++ b/tests/test-repair-strip.out
@@ -0,0 +1,83 @@
+% before update 0, strip 1
+changeset:   0:cb9a9f314b8b
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     a
+
+saving bundle to 
+transaction abort!
+failed to truncate data/b.i
+rollback failed - please run hg recover
+abort: Permission denied .hg/store/data/b.i
+% after update 0, strip 1
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+warning: orphan revlog 'data/b.i'
+1 files, 1 changesets, 1 total revisions
+1 warnings encountered!
+% journal contents
+00changelog.i
+00manifest.i
+data/b.i
+data/c.i
+rolling back interrupted transaction
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+% before update 0, strip 1
+changeset:   0:cb9a9f314b8b
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     a
+
+abort: Permission denied .hg/store/data/b.i
+% after update 0, strip 1
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+3 files, 3 changesets, 3 total revisions
+% journal contents
+cat: .hg/store/journal: No such file or directory
+% before update 0, strip 1
+changeset:   0:cb9a9f314b8b
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     a
+
+saving bundle to 
+transaction abort!
+failed to truncate 00changelog.i
+rollback failed - please run hg recover
+abort: Permission denied .hg/store/00changelog.i
+% after update 0, strip 1
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+ 1: changeset refers to unknown manifest a539ce0c1a22
+ 2: changeset refers to unknown manifest e3738bf54399
+ b at 1: in changeset but not in manifest
+ c at 2: in changeset but not in manifest
+checking files
+ data/b.i at 1: missing revlog!
+ 0: empty or missing b
+ data/c.i at 2: missing revlog!
+ 0: empty or missing c
+3 files, 3 changesets, 1 total revisions
+8 integrity errors encountered!
+(first damaged changeset appears to be 0)
+% journal contents
+00changelog.i
+00manifest.i
+data/b.i
+data/c.i
+rolling back interrupted transaction
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions


More information about the Mercurial-devel mailing list