Rev 3520: Add pre_change_branch_tip hook. (Andrew Bennetts) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Fri Jul 4 04:41:31 BST 2008
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 3520
revision-id:pqm at pqm.ubuntu.com-20080704034121-6ysq01uf3p1h97aj
parent: pqm at pqm.ubuntu.com-20080703180607-kxmp7ftyw7sqg5gj
parent: andrew.bennetts at canonical.com-20080704031947-zkhvfgu22nb3ml53
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2008-07-04 04:41:21 +0100
message:
Add pre_change_branch_tip hook. (Andrew Bennetts)
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/branch.py branch.py-20050309040759-e4baf4e0d046576e
bzrlib/help_topics/en/hooks.txt hooks.txt-20070830033044-xxu2rced13f72dka-1
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
------------------------------------------------------------
revno: 3517.2.6
revision-id:andrew.bennetts at canonical.com-20080704031947-zkhvfgu22nb3ml53
parent: andrew.bennetts at canonical.com-20080704013444-24c2sa27q85eqkms
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Fri 2008-07-04 13:19:47 +1000
message:
Add NEWS entry, and document in the 'hooks' help topic.
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/help_topics/en/hooks.txt hooks.txt-20070830033044-xxu2rced13f72dka-1
------------------------------------------------------------
revno: 3517.2.5
revision-id:andrew.bennetts at canonical.com-20080704013444-24c2sa27q85eqkms
parent: andrew.bennetts at canonical.com-20080703075706-7zup8zm007a2dyb9
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Fri 2008-07-04 11:34:44 +1000
message:
Reduce duplication in test_hooks a little.
modified:
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
------------------------------------------------------------
revno: 3517.2.4
revision-id:andrew.bennetts at canonical.com-20080703075706-7zup8zm007a2dyb9
parent: andrew.bennetts at canonical.com-20080703075602-8n055qsfkjijcz6i
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Thu 2008-07-03 17:57:06 +1000
message:
Fix typo.
modified:
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
------------------------------------------------------------
revno: 3517.2.3
revision-id:andrew.bennetts at canonical.com-20080703075602-8n055qsfkjijcz6i
parent: andrew.bennetts at canonical.com-20080703071735-33dgjy9niohq197k
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Thu 2008-07-03 17:56:02 +1000
message:
Better tests for {pre,post}_change_branch_tip hooks.
modified:
bzrlib/branch.py branch.py-20050309040759-e4baf4e0d046576e
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
------------------------------------------------------------
revno: 3517.2.2
revision-id:andrew.bennetts at canonical.com-20080703071735-33dgjy9niohq197k
parent: andrew.bennetts at canonical.com-20080703054955-n94lvd3puo6aiwt3
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Thu 2008-07-03 17:17:35 +1000
message:
Add test for a pre_change_branch_tip hook rejecting a change.
modified:
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
------------------------------------------------------------
revno: 3517.2.1
revision-id:andrew.bennetts at canonical.com-20080703054955-n94lvd3puo6aiwt3
parent: pqm at pqm.ubuntu.com-20080702195105-5gqthymygmtjrwaf
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: pre-branch-tip-changed-hook
timestamp: Thu 2008-07-03 15:49:55 +1000
message:
Quick draft of pre_change_branch_tip hook.
modified:
bzrlib/branch.py branch.py-20050309040759-e4baf4e0d046576e
bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
=== modified file 'NEWS'
--- a/NEWS 2008-07-03 10:44:34 +0000
+++ b/NEWS 2008-07-04 03:41:21 +0000
@@ -10,6 +10,10 @@
FEATURES:
+ * New ``pre_change_branch_tip`` hook that is called before the
+ branch tip is moved, while the branch is write-locked. See the User
+ Reference for signature details. (Andrew Bennetts)
+
* Rule-based preferences can now be defined for selected files in
selected branches, allowing commands and plugins to provide
custom behaviour for files matching defined patterns.
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py 2008-06-25 22:27:57 +0000
+++ b/bzrlib/branch.py 2008-07-03 07:56:02 +0000
@@ -1109,6 +1109,12 @@
# local is the local branch or None, master is the target branch,
# and an empty branch recieves new_revno of 0, new_revid of None.
self['post_uncommit'] = []
+ # Introduced in 1.6
+ # Invoked before the tip of a branch changes.
+ # the api signature is
+ # (params) where params is a ChangeBranchTipParams with the members
+ # (branch, old_revno, new_revno, old_revid, new_revid)
+ self['pre_change_branch_tip'] = []
# Introduced in 1.4
# Invoked after the tip of a branch changes.
# the api signature is
@@ -1150,6 +1156,14 @@
self.old_revid = old_revid
self.new_revid = new_revid
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
+
+ def __repr__(self):
+ return "<%s of %s from (%s, %s) to (%s, %s)>" % (
+ self.__class__.__name__, self.branch,
+ self.old_revno, self.old_revid, self.new_revno, self.new_revid)
+
class BzrBranchFormat4(BranchFormat):
"""Bzr branch format 4.
@@ -1512,6 +1526,17 @@
for hook in Branch.hooks['set_rh']:
hook(self, rev_history)
+ def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
+ """Run the pre_change_branch_tip hooks."""
+ hooks = Branch.hooks['pre_change_branch_tip']
+ if not hooks:
+ return
+ old_revno, old_revid = self.last_revision_info()
+ params = ChangeBranchTipParams(
+ self, old_revno, new_revno, old_revid, new_revid)
+ for hook in hooks:
+ hook(params)
+
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
"""Run the post_change_branch_tip hooks."""
hooks = Branch.hooks['post_change_branch_tip']
@@ -1543,6 +1568,7 @@
history = self._lefthand_history(revision_id)
if len(history) != revno:
raise AssertionError('%d != %d' % (len(history), revno))
+ self._run_pre_change_branch_tip_hooks(revno, revision_id)
self.set_revision_history(history)
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
@@ -1958,6 +1984,7 @@
old_revno, old_revid = self.last_revision_info()
if self._get_append_revisions_only():
self._check_history_violation(revision_id)
+ self._run_pre_change_branch_tip_hooks(revno, revision_id)
self._write_last_revision_info(revno, revision_id)
self._clear_cached_state()
self._last_revision_info_cache = revno, revision_id
=== modified file 'bzrlib/help_topics/en/hooks.txt'
--- a/bzrlib/help_topics/en/hooks.txt 2008-05-08 06:47:59 +0000
+++ b/bzrlib/help_topics/en/hooks.txt 2008-07-04 03:19:47 +0000
@@ -105,6 +105,34 @@
branch, and an empty branch receives new_revno of 0, new_revid of None.
+pre_change_branch_tip (Branch)
+-------------------------------
+
+Run before a branch tip has been changed, while the branch is write-locked.
+Note that push, pull, commit and uncommit all invoke this hook.
+
+The hook signature is (params), where params is an object containing
+the members
+
+ branch
+ The branch whose tip has been changed.
+
+ old_revno
+ The revision number (eg 10) of the branch before the change.
+
+ old_revid
+ The revision id (eg joe at foo.com-1234234-aoeua34) before the change.
+
+ new_revno
+ The revision number (eg 12) of the branch after the change.
+
+ new_revid
+ The revision id (eg joe at foo.com-5676566-boa234a) after the change.
+
+The old_revno and new_revno members are integers, as the head
+revision never has a dotted revision number.
+
+
post_change_branch_tip (Branch)
-------------------------------
@@ -130,7 +158,7 @@
The revision id (eg joe at foo.com-5676566-boa234a) after the change.
The old_revno and new_revno members are integers, as the head
-revision is never has a dotted revision number.
+revision never has a dotted revision number.
set_rh (Branch)
=== modified file 'bzrlib/tests/branch_implementations/test_hooks.py'
--- a/bzrlib/tests/branch_implementations/test_hooks.py 2008-04-20 08:21:39 +0000
+++ b/bzrlib/tests/branch_implementations/test_hooks.py 2008-07-04 01:34:44 +0000
@@ -16,7 +16,7 @@
"""Tests that branch classes implement hook callouts correctly."""
-from bzrlib.branch import Branch
+from bzrlib.branch import Branch, ChangeBranchTipParams
from bzrlib.revision import NULL_REVISION
from bzrlib.tests import TestCaseWithMemoryTransport
@@ -80,79 +80,165 @@
])
-class TestPostChangeBranchTip(TestCaseWithMemoryTransport):
-
- def setUp(self):
- self.hook_calls = []
- TestCaseWithMemoryTransport.setUp(self)
-
- def capture_post_change_branch_tip_hook(self, params):
- """Capture post_change_branch_tip hook calls to self.hook_calls.
-
- The call is logged, as is some state of the branch.
+class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
+ """Base TestCase for testing pre/post_change_branch_tip hooks."""
+
+ def install_logging_hook(self, prefix):
+ """Add a hook that logs calls made to it.
+
+ :returns: the list that the calls will be appended to.
"""
- self.hook_calls.append((params, params.branch.is_locked()))
- self.assertEquals(params.branch.last_revision_info(),
- (params.new_revno, params.new_revid))
-
- def test_post_change_branch_tip_empty_history(self):
- branch = self.make_branch('source')
+ hook_calls = []
Branch.hooks.install_named_hook(
- 'post_change_branch_tip',
- self.capture_post_change_branch_tip_hook,
- None)
- branch.set_last_revision_info(0, NULL_REVISION)
- self.assertEqual(len(self.hook_calls), 1)
- self.assertEqual(self.hook_calls[0][0].branch, branch)
- self.assertEqual(self.hook_calls[0][0].old_revid, NULL_REVISION)
- self.assertEqual(self.hook_calls[0][0].old_revno, 0)
- self.assertEqual(self.hook_calls[0][0].new_revid, NULL_REVISION)
- self.assertEqual(self.hook_calls[0][0].new_revno, 0)
+ 'pre_change_branch_tip', hook_calls.append, None)
+ return hook_calls
- def test_post_change_branch_tip_nonempty_history(self):
+ def make_branch_with_revision_ids(self, *revision_ids):
+ """Makes a branch with the given commits."""
tree = self.make_branch_and_memory_tree('source')
tree.lock_write()
tree.add('')
- tree.commit('another commit', rev_id='f\xc2\xb5')
- tree.commit('empty commit', rev_id='foo')
+ for revision_id in revision_ids:
+ tree.commit('Message of ' + revision_id, rev_id=revision_id)
tree.unlock()
branch = tree.branch
- Branch.hooks.install_named_hook(
- 'post_change_branch_tip',
- self.capture_post_change_branch_tip_hook,
- None)
- # some branches require that their history be set to a revision in the
- # repository
- branch.set_last_revision_info(1, 'f\xc2\xb5')
- self.assertEqual(len(self.hook_calls), 1)
- self.assertEqual(self.hook_calls[0][0].branch, branch)
- self.assertEqual(self.hook_calls[0][0].old_revid, 'foo')
- self.assertEqual(self.hook_calls[0][0].old_revno, 2)
- self.assertEqual(self.hook_calls[0][0].new_revid, 'f\xc2\xb5')
- self.assertEqual(self.hook_calls[0][0].new_revno, 1)
-
- def test_post_change_branch_tip_branch_is_locked(self):
- branch = self.make_branch('source')
- Branch.hooks.install_named_hook(
- 'post_change_branch_tip',
- self.capture_post_change_branch_tip_hook,
- None)
- branch.set_last_revision_info(0, NULL_REVISION)
- self.assertEqual(len(self.hook_calls), 1)
- self.assertEqual(self.hook_calls[0][0].branch, branch)
- self.assertEqual(self.hook_calls[0][1], True)
-
- def test_post_change_branch_tip_calls_all_hooks_no_errors(self):
- branch = self.make_branch('source')
- Branch.hooks.install_named_hook(
- 'post_change_branch_tip',
- self.capture_post_change_branch_tip_hook,
- None)
- Branch.hooks.install_named_hook(
- 'post_change_branch_tip',
- self.capture_post_change_branch_tip_hook,
- None)
- branch.set_last_revision_info(0, NULL_REVISION)
- self.assertEqual(len(self.hook_calls), 2)
- self.assertEqual(self.hook_calls[0][0].branch, branch)
- self.assertEqual(self.hook_calls[1][0].branch, branch)
+ return branch
+
+
+class TestPreChangeBranchTip(ChangeBranchTipTestCase):
+ """Tests for pre_change_branch_tip hook.
+
+ Most of these tests are very similar to the tests in
+ TestPostChangeBranchTip.
+ """
+
+ def test_hook_runs_before_change(self):
+ """The hook runs *before* the branch's last_revision_info has changed.
+ """
+ branch = self.make_branch_with_revision_ids('revid-one')
+ def assertBranchAtRevision1(params):
+ self.assertEquals(
+ (1, 'revid-one'), params.branch.last_revision_info())
+ Branch.hooks.install_named_hook(
+ 'pre_change_branch_tip', assertBranchAtRevision1, None)
+ branch.set_last_revision_info(0, NULL_REVISION)
+
+ def test_reject_by_hook(self):
+ """If a hook raises an exception, the change does not take effect.
+
+ Also, the exception will be propogated.
+ """
+ branch = self.make_branch_with_revision_ids(
+ 'one-\xc2\xb5', 'two-\xc2\xb5')
+ class PearShapedError(Exception):
+ pass
+ def hook_that_raises(params):
+ raise PearShapedError()
+ Branch.hooks.install_named_hook(
+ 'pre_change_branch_tip', hook_that_raises, None)
+ self.assertRaises(
+ PearShapedError, branch.set_last_revision_info, 0, NULL_REVISION)
+ # The revision info is unchanged.
+ self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
+
+ def test_empty_history(self):
+ branch = self.make_branch('source')
+ hook_calls = self.install_logging_hook('pre')
+ branch.set_last_revision_info(0, NULL_REVISION)
+ expected_params = ChangeBranchTipParams(
+ branch, 0, 0, NULL_REVISION, NULL_REVISION)
+ self.assertEqual([expected_params], hook_calls)
+
+ def test_nonempty_history(self):
+ # some branches require that their history be set to a revision in the
+ # repository, so we need to make a branch with non-empty history for
+ # this test.
+ branch = self.make_branch_with_revision_ids(
+ 'one-\xc2\xb5', 'two-\xc2\xb5')
+ hook_calls = self.install_logging_hook('pre')
+ branch.set_last_revision_info(1, 'one-\xc2\xb5')
+ expected_params = ChangeBranchTipParams(
+ branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
+ self.assertEqual([expected_params], hook_calls)
+
+ def test_branch_is_locked(self):
+ branch = self.make_branch('source')
+ def assertBranchIsLocked(params):
+ self.assertTrue(params.branch.is_locked())
+ Branch.hooks.install_named_hook(
+ 'pre_change_branch_tip', assertBranchIsLocked, None)
+ branch.set_last_revision_info(0, NULL_REVISION)
+
+ def test_calls_all_hooks_no_errors(self):
+ """If multiple hooks are registered, all are called (if none raise
+ errors).
+ """
+ branch = self.make_branch('source')
+ hook_calls_1 = self.install_logging_hook('pre')
+ hook_calls_2 = self.install_logging_hook('pre')
+ self.assertIsNot(hook_calls_1, hook_calls_2)
+ branch.set_last_revision_info(0, NULL_REVISION)
+ # Both hooks are called.
+ self.assertEqual(len(hook_calls_1), 1)
+ self.assertEqual(len(hook_calls_2), 1)
+
+
+class TestPostChangeBranchTip(ChangeBranchTipTestCase):
+ """Tests for post_change_branch_tip hook.
+
+ Most of these tests are very similar to the tests in
+ TestPostChangeBranchTip.
+ """
+
+ def test_hook_runs_after_change(self):
+ """The hook runs *after* the branch's last_revision_info has changed.
+ """
+ branch = self.make_branch_with_revision_ids('revid-one')
+ def assertBranchAtRevision1(params):
+ self.assertEquals(
+ (0, NULL_REVISION), params.branch.last_revision_info())
+ Branch.hooks.install_named_hook(
+ 'post_change_branch_tip', assertBranchAtRevision1, None)
+ branch.set_last_revision_info(0, NULL_REVISION)
+
+ def test_empty_history(self):
+ branch = self.make_branch('source')
+ hook_calls = self.install_logging_hook('post')
+ branch.set_last_revision_info(0, NULL_REVISION)
+ expected_params = ChangeBranchTipParams(
+ branch, 0, 0, NULL_REVISION, NULL_REVISION)
+ self.assertEqual([expected_params], hook_calls)
+
+ def test_nonempty_history(self):
+ # some branches require that their history be set to a revision in the
+ # repository, so we need to make a branch with non-empty history for
+ # this test.
+ branch = self.make_branch_with_revision_ids(
+ 'one-\xc2\xb5', 'two-\xc2\xb5')
+ hook_calls = self.install_logging_hook('post')
+ branch.set_last_revision_info(1, 'one-\xc2\xb5')
+ expected_params = ChangeBranchTipParams(
+ branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
+ self.assertEqual([expected_params], hook_calls)
+
+ def test_branch_is_locked(self):
+ """The branch passed to the hook is locked."""
+ branch = self.make_branch('source')
+ def assertBranchIsLocked(params):
+ self.assertTrue(params.branch.is_locked())
+ Branch.hooks.install_named_hook(
+ 'post_change_branch_tip', assertBranchIsLocked, None)
+ branch.set_last_revision_info(0, NULL_REVISION)
+
+ def test_calls_all_hooks_no_errors(self):
+ """If multiple hooks are registered, all are called (if none raise
+ errors).
+ """
+ branch = self.make_branch('source')
+ hook_calls_1 = self.install_logging_hook('post')
+ hook_calls_2 = self.install_logging_hook('post')
+ self.assertIsNot(hook_calls_1, hook_calls_2)
+ branch.set_last_revision_info(0, NULL_REVISION)
+ # Both hooks are called.
+ self.assertEqual(len(hook_calls_1), 1)
+ self.assertEqual(len(hook_calls_2), 1)
More information about the bazaar-commits
mailing list