Introduce HG_PREPEND to solve pretxn races
authorMatt Mackall <mpm@selenic.com>
Mon, 16 Feb 2009 19:35:07 -0600
changeset 7787 b8d750daadde
parent 7786 92455c1d6f83
child 7788 0896c008cb52
Introduce HG_PREPEND to solve pretxn races - add writepending to flush delayed writes to separate file - add support in hooks for lazy evaluation of callable parameters - add HG_PENDING to pretxn hooks - call writepending if hook is used - pass repo root to hook environment - if HG_PENDING = repo root, we're in pretxn hook - read pending data to make pending changesets visible - filter HG_PENDING in tests/printenv.py
mercurial/changelog.py
mercurial/hook.py
mercurial/localrepo.py
tests/printenv.py
tests/test-hook.out
--- a/mercurial/changelog.py	Sun Jan 18 19:59:51 2009 +0100
+++ b/mercurial/changelog.py	Mon Feb 16 19:35:07 2009 -0600
@@ -96,7 +96,7 @@
             fp = self.opener(self.indexfile, 'a')
             fp.write("".join(self._delaybuf))
             fp.close()
-            del self._delaybuf
+            self._delaybuf = []
         # split when we're done
         self.checkinlinesize(tr)
 
@@ -115,6 +115,31 @@
         # otherwise, divert to memory
         return appender(fp, self._delaybuf)
 
+    def readpending(self, file):
+        r = revlog.revlog(self.opener, file)
+        self.index = r.index
+        self.nodemap = r.nodemap
+        self._chunkcache = r._chunkcache
+
+    def writepending(self):
+        "create a file containing the unfinalized state for pretxnchangegroup"
+        if self._delaybuf:
+            # make a temporary copy of the index
+            fp1 = self._realopener(self.indexfile)
+            fp2 = self._realopener(self.indexfile + ".a", "w")
+            fp2.write(fp1.read())
+            # add pending data
+            fp2.write("".join(self._delaybuf))
+            fp2.close()
+            # switch modes so finalize can simply rename
+            self._delaybuf = []
+            self._delayname = fp1.name
+
+        if self._delayname:
+            return True
+
+        return False
+
     def checkinlinesize(self, tr, fp=None):
         if self.opener == self._delayopener:
             return
--- a/mercurial/hook.py	Sun Jan 18 19:59:51 2009 +0100
+++ b/mercurial/hook.py	Mon Feb 16 19:35:07 2009 -0600
@@ -70,7 +70,13 @@
 
 def _exthook(ui, repo, name, cmd, args, throw):
     ui.note(_("running hook %s: %s\n") % (name, cmd))
-    env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
+
+    env = {}
+    for k, v in args.iteritems():
+        if callable(v):
+            v = v()
+        env['HG_' + k.upper()] = v
+
     if repo:
         cwd = repo.root
     else:
--- a/mercurial/localrepo.py	Sun Jan 18 19:59:51 2009 +0100
+++ b/mercurial/localrepo.py	Mon Feb 16 19:35:07 2009 -0600
@@ -88,6 +88,10 @@
     def __getattr__(self, name):
         if name == 'changelog':
             self.changelog = changelog.changelog(self.sopener)
+            if 'HG_PENDING' in os.environ:
+                p = os.environ['HG_PENDING']
+                if p.startswith(self.root):
+                    self.changelog.readpending('00changelog.i.a')
             self.sopener.defversion = self.changelog.version
             return self.changelog
         if name == 'manifest':
@@ -955,10 +959,13 @@
                 raise util.Abort(_("empty commit message"))
             text = '\n'.join(lines)
 
+            self.changelog.delayupdate()
             n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
                                    user, wctx.date(), extra)
+            p = lambda: self.changelog.writepending() and self.root or ""
             self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
-                      parent2=xp2)
+                      parent2=xp2, pending=p)
+            self.changelog.finalize(trp)
             tr.close()
 
             if self.branchcache:
@@ -2034,9 +2041,6 @@
                 revisions += len(fl) - o
                 files += 1
 
-            # make changelog see real files again
-            cl.finalize(trp)
-
             newheads = len(self.changelog.heads())
             heads = ""
             if oldheads and newheads != oldheads:
@@ -2047,9 +2051,13 @@
                              % (changesets, revisions, files, heads))
 
             if changesets > 0:
+                p = lambda: self.changelog.writepending() and self.root or ""
                 self.hook('pretxnchangegroup', throw=True,
                           node=hex(self.changelog.node(cor+1)), source=srctype,
-                          url=url)
+                          url=url, pending=p)
+
+            # make changelog see real files again
+            cl.finalize(trp)
 
             tr.close()
         finally:
--- a/tests/printenv.py	Sun Jan 18 19:59:51 2009 +0100
+++ b/tests/printenv.py	Mon Feb 16 19:35:07 2009 -0600
@@ -46,6 +46,9 @@
 elif url.startswith("remote:http"):
     os.environ["HG_URL"] = "remote:http"
 
+if "HG_PENDING" in os.environ:
+    os.environ["HG_PENDING"] = os.environ["HG_PENDING"] and "true"
+
 out.write("%s hook: " % name)
 for v in env:
     out.write("%s=%s " % (v, os.environ[v]))
--- a/tests/test-hook.out	Sun Jan 18 19:59:51 2009 +0100
+++ b/tests/test-hook.out	Mon Feb 16 19:35:07 2009 -0600
@@ -1,18 +1,18 @@
 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000 
-pretxncommit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 
+pretxncommit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=true 
 0:29b62aeb769f
 commit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 
 commit.b hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 
 updating working directory
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
-pretxncommit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
+pretxncommit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PENDING=true 
 1:b702efe96888
 commit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
 commit.b hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
-pretxncommit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
+pretxncommit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PENDING=true 
 2:1324a5531bac
 commit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
 commit.b hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b 
@@ -20,7 +20,7 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 (branch merge, don't forget to commit)
 precommit hook: HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
-pretxncommit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
+pretxncommit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PENDING=true 
 3:4c52fb2e4022
 commit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
 commit.b hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
@@ -43,7 +43,7 @@
 (run 'hg update' to get a working copy)
 pretag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a 
 precommit hook: HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 
-pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 
+pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PENDING=true 
 4:8ea2ef7ad3e8
 commit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 
 commit.b hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 
@@ -58,9 +58,9 @@
 abort: pretag.forbid hook exited with status 1
 4:8ea2ef7ad3e8
 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
-pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
+pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=true 
 5:fad284daf8c0
-pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
+pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=true 
 transaction abort!
 rollback completed
 abort: pretxncommit.forbid1 hook exited with status 1
@@ -80,7 +80,7 @@
 searching for changes
 abort: prechangegroup.forbid hook exited with status 1
 4:8ea2ef7ad3e8
-pretxnchangegroup.forbid hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull HG_URL=file: 
+pretxnchangegroup.forbid hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=true HG_SOURCE=pull HG_URL=file: 
 pulling from ../a
 searching for changes
 adding changesets