Patchwork [4,of,5,V2] hook: add a generic hook right before we commit a transaction

login
register
mail settings
Submitter Pierre-Yves David
Date March 12, 2015, 1:33 a.m.
Message ID <f77c70299f01765fb391.1426123998@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/8017/
State Accepted
Commit ff14b26fe5f4269c41c1b70372ca2485cefaec25
Headers show

Comments

Pierre-Yves David - March 12, 2015, 1:33 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1425966649 25200
#      Mon Mar 09 22:50:49 2015 -0700
# Node ID f77c70299f01765fb3913755ea642aaeb3af963f
# Parent  b72c668ed29e918777d54387f8fefb988cf2e944
hook: add a generic hook right before we commit a transaction

We are adding a 'txnclose' hook that will be run right before a transaction is
close. Hooks running at that time will have access to the full transaction
content through both 'hookargs' content and on disk reading. They will be able
to abort the transaction.

Patch

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -811,19 +811,27 @@  variables it is passed are listed with n
 ``pretxnopen``
   Run before any new repository transaction is open. Reason for the
   transaction opening will be in ``$HG_TXNNAME``. Non-zero status will
   prevent the transaction to be opened.
 
+``pretxnclose``
+  Run right before that transaction is actually commited for good. Any
+  repository change will be visible to the hook program. This lets you
+  validate the transaction content or changes it. Exit status 0 allows
+  the commit to proceed. Non-zero status will cause the transaction to
+  be rolled back. Reason for the transaction opening will be in
+  ``$HG_TXNNAME``. The rest of the available data will vary according
+  the event who append during the transaction. New changesets will add
+  ``$HG_NODE`` (id of the first added changesets, ``$HG_URL`` and
+  ``$HG_SOURCE`` variables, bookmarks and phases changes will set
+  ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc
+
 ``txnclosed``
   Run after any repository transaction have been commited. At this
   point, the transaction can no longer be rollbacked. The hook will run
-  after the lock is released.  Reason for the transaction opening will
-  be in ``$HG_TXNNAME``. The rest of the available data will vary
-  according the event who append during the transaction. New changesets
-  will add ``$HG_NODE`` (id of the first added changesets, ``$HG_URL``
-  and ``$HG_SOURCE`` variables, bookmarks and phases changes will set
-  ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc
+  after the lock is released. see ``pretxnclose`` docs for details about
+  available variables.
 
 ``pretxnchangegroup``
   Run after a changegroup has been added via push, pull or unbundle,
   but before the transaction has been committed. Changegroup is
   visible to hook program. This lets you validate incoming changes
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -913,21 +913,28 @@  class localrepository(object):
 
         self._writejournal(desc)
         renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
         rp = report and report or self.ui.warn
         vfsmap = {'plain': self.vfs} # root of .hg/
-        tr = transaction.transaction(rp, self.svfs, vfsmap,
+        # we must avoid cyclic reference between repo and transaction.
+        reporef = weakref.ref(self)
+        def validate(tr):
+            """will run pre-closing hooks"""
+            pending = lambda: tr.writepending() and self.root or ""
+            reporef().hook('pretxnclose', throw=True, pending=pending,
+                           xnname=desc)
+
+        tr = transaction.transaction(rp, self.sopener, vfsmap,
                                      "journal",
                                      "undo",
                                      aftertrans(renames),
-                                     self.store.createmode)
+                                     self.store.createmode,
+                                     validator=validate)
         # note: writing the fncache only during finalize mean that the file is
         # outdated when running hooks. As fncache is used for streaming clone,
         # this is not expected to break anything that happen during the hooks.
         tr.addfinalize('flush-fncache', self.store.write)
-        # we must avoid cyclic reference between repo and transaction.
-        reporef = weakref.ref(self)
         def txnclosehook(tr2):
             """To be run if transaction is successful, will schedule a hook run
             """
             def hook():
                 reporef().hook('txnclose', throw=False, txnname=desc,
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -11,19 +11,21 @@  commit hooks can see env vars
   > pretxncommit.tip = hg -q tip
   > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
   > pre-cat = python "$TESTDIR/printenv.py" pre-cat
   > post-cat = python "$TESTDIR/printenv.py" post-cat
   > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
+  > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
   > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
   > EOF
   $ echo a > a
   $ hg add a
   $ hg commit -m a
   precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
   pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
   0:cb9a9f314b8b
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=commit
   commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
   commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
 
   $ hg clone . ../b
@@ -47,10 +49,11 @@  pretxncommit and commit hooks can see bo
   $ hg commit -m a1 -d "1 0"
   precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
   1:ab228980c14d
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   $ hg update -C 0
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -59,10 +62,11 @@  pretxncommit and commit hooks can see bo
   $ hg commit -m b -d '1 0'
   precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
   2:ee9deb46ab31
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   created new head
   $ hg merge 1
@@ -71,10 +75,11 @@  pretxncommit and commit hooks can see bo
   $ hg commit -m merge -d '2 0'
   precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
   pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
   3:07f3376c1e65
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
   commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
 
 test generic hooks
@@ -114,10 +119,11 @@  tag hooks can see env vars
   pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
   precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
   pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
   4:539e4b31b6dc
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
   txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
   commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
   $ hg tag -l la
@@ -210,10 +216,11 @@  pushkey hook
   $ hg push -B foo ../a
   pushing to ../a
   searching for changes
   no changes found
   pretxnopen hook: HG_TXNNAME=bookmarks
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=bookmarks
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNNAME=bookmarks
   pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
   exporting bookmark foo
   [1]
   $ cd ../a