Rev 2248: [merge] bzr.dev 2252 in http://bzr.arbash-meinel.com/branches/bzr/0.15-dev/annotate_revnos

John Arbash Meinel john at arbash-meinel.com
Thu Feb 1 21:29:25 GMT 2007


At http://bzr.arbash-meinel.com/branches/bzr/0.15-dev/annotate_revnos

------------------------------------------------------------
revno: 2248
revision-id: john at arbash-meinel.com-20070201212919-s0s8iwvi8lowk0w0
parent: john at arbash-meinel.com-20070201163300-jp0cs02xz90leqpf
parent: pqm at pqm.ubuntu.com-20070201203641-a125a85aee82c725
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: annotate_revnos
timestamp: Thu 2007-02-01 15:29:19 -0600
message:
  [merge] bzr.dev 2252
added:
  bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
  bzrlib/tests/branch_implementations/test_push.py test_push.py-20070130153159-fhfap8uoifevg30j-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/help_topics.py          help_topics.py-20060920210027-rnim90q9e0bwxvy4-1
  bzrlib/lockdir.py              lockdir.py-20060220222025-98258adf27fbdda3
  bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
  bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/branch_implementations/__init__.py __init__.py-20060123013057-b12a52c3f361daf4
  bzrlib/tests/branch_implementations/test_bound_sftp.py test_bound_sftp.py-20051231055311-2f96048c4f0940ef
  bzrlib/tests/branch_implementations/test_pull.py test_pull.py-20060410103942-83c35b26657414fc
  bzrlib/tests/test_branch.py    test_branch.py-20060116013032-97819aa07b8ab3b5
  bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
  bzrlib/tests/test_lockdir.py   test_lockdir.py-20060220222025-33d4221569a3d600
  bzrlib/tests/test_merge.py     testmerge.py-20050905070950-c1b5aa49ff911024
  bzrlib/tests/test_osutils.py   test_osutils.py-20051201224856-e48ee24c12182989
  bzrlib/tests/test_selftest.py  test_selftest.py-20051202044319-c110a115d8c0456a
  bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
    ------------------------------------------------------------
    revno: 2245.1.7
    merged: pqm at pqm.ubuntu.com-20070201203641-a125a85aee82c725
    parent: pqm at pqm.ubuntu.com-20070201202403-7e92ef4d6842ba85
    parent: john at arbash-meinel.com-20070201144953-d877vy9zbvxr5q98
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Thu 2007-02-01 20:36:41 +0000
    message:
      (John Arbash Meinel) hard-code the whitespace chars to avoid problems in some locales.
        ------------------------------------------------------------
        revno: 2245.1.4.2.1
        merged: john at arbash-meinel.com-20070201144953-d877vy9zbvxr5q98
        parent: pqm at pqm.ubuntu.com-20070131184047-424584b0fabcee96
        committer: John Arbash Meinel <john at arbash-meinel.com>
        branch nick: jam-integration
        timestamp: Thu 2007-02-01 08:49:53 -0600
        message:
          (John Arbash Meinel) hard-code the whitespace chars to avoid problems in some locales.
    ------------------------------------------------------------
    revno: 2245.1.6
    merged: pqm at pqm.ubuntu.com-20070201202403-7e92ef4d6842ba85
    parent: pqm at pqm.ubuntu.com-20070201175351-9ba49ec1a0744c97
    parent: abentley at panoramicfeedback.com-20070201145904-nrzbl3vkwjssrkhb
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Thu 2007-02-01 20:24:03 +0000
    message:
      Treat Permission Denied as a lock failure, not lock contention
        ------------------------------------------------------------
        revno: 1551.2.49.1.40.1.22.1.42.1.31.1.4
        merged: abentley at panoramicfeedback.com-20070201145904-nrzbl3vkwjssrkhb
        parent: abentley at panoramicfeedback.com-20070131184912-1n4p0avc2qw5qyax
        committer: Aaron Bentley <abentley at panoramicfeedback.com>
        branch nick: Aaron's mergeable stuff
        timestamp: Thu 2007-02-01 09:59:04 -0500
        message:
          Update to skip on win32
        ------------------------------------------------------------
        revno: 1551.2.49.1.40.1.22.1.42.1.31.1.3
        merged: abentley at panoramicfeedback.com-20070131184912-1n4p0avc2qw5qyax
        parent: aaron.bentley at utoronto.ca-20070131035136-exu2pa898yr2gzen
        committer: Aaron Bentley <abentley at panoramicfeedback.com>
        branch nick: Aaron's mergeable stuff
        timestamp: Wed 2007-01-31 13:49:12 -0500
        message:
          Lock attempts don't treat permission problems as lock contention
    ------------------------------------------------------------
    revno: 2245.1.5
    merged: pqm at pqm.ubuntu.com-20070201175351-9ba49ec1a0744c97
    parent: pqm at pqm.ubuntu.com-20070131184047-424584b0fabcee96
    parent: robertc at robertcollins.net-20070201171842-aw4059y8znmigwgn
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Thu 2007-02-01 17:53:51 +0000
    message:
      (robertc) Split branch pushing out of branch pulling.
        ------------------------------------------------------------
        revno: 2245.1.4.1.1
        merged: robertc at robertcollins.net-20070201171842-aw4059y8znmigwgn
        parent: pqm at pqm.ubuntu.com-20070131184047-424584b0fabcee96
        parent: robertc at robertcollins.net-20070130211105-u8p44qh31vsn4xh3
        committer: Robert Collins <robertc at robertcollins.net>
        branch nick: integration
        timestamp: Fri 2007-02-02 04:18:42 +1100
        message:
          (robertc) Split branch pushing out of branch pulling.
        ------------------------------------------------------------
        revno: 2245.3.3
        merged: robertc at robertcollins.net-20070130211105-u8p44qh31vsn4xh3
        parent: robertc at robertcollins.net-20070130210611-6ogb9vztebubsqyx
        committer: Robert Collins <robertc at robertcollins.net>
        branch nick: split-branch-pull
        timestamp: Wed 2007-01-31 08:11:05 +1100
        message:
          Add a NEWS entry.
        ------------------------------------------------------------
        revno: 2245.3.2
        merged: robertc at robertcollins.net-20070130210611-6ogb9vztebubsqyx
        parent: robertc at robertcollins.net-20070130205825-cagx4gvf17ioe2i5
        committer: Robert Collins <robertc at robertcollins.net>
        branch nick: split-branch-pull
        timestamp: Wed 2007-01-31 08:06:11 +1100
        message:
          Update cmd_push to invoke the correct 'push' method on branch, rather than pull. This does not alter tests because the behaviour for push and pull is still symmetrical.
        ------------------------------------------------------------
        revno: 2245.3.1
        merged: robertc at robertcollins.net-20070130205825-cagx4gvf17ioe2i5
        parent: pqm at pqm.ubuntu.com-20070125194626-4ded330415b7276d
        committer: Robert Collins <robertc at robertcollins.net>
        branch nick: split-branch-pull
        timestamp: Wed 2007-01-31 07:58:25 +1100
        message:
          Split branch pushing out of branch pulling.
    ------------------------------------------------------------
    revno: 2245.1.4
    merged: pqm at pqm.ubuntu.com-20070131184047-424584b0fabcee96
    parent: pqm at pqm.ubuntu.com-20070131165725-e3091cb8d282ef90
    parent: john at arbash-meinel.com-20070131174855-gxkjd9rvy17uc6ko
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Wed 2007-01-31 18:40:47 +0000
    message:
      (John Arbash Meinel) switch from looping over f.write() to f.writelines()
        ------------------------------------------------------------
        revno: 2245.1.2.1.2
        merged: john at arbash-meinel.com-20070131174855-gxkjd9rvy17uc6ko
        parent: john at arbash-meinel.com-20070131163348-kkwfh7hvvm39tx8y
        committer: John Arbash Meinel <john at arbash-meinel.com>
        branch nick: jam-integration
        timestamp: Wed 2007-01-31 11:48:55 -0600
        message:
          Switch from for line in foo: f.write(line) to f.writelines(foo)
    ------------------------------------------------------------
    revno: 2245.1.3
    merged: pqm at pqm.ubuntu.com-20070131165725-e3091cb8d282ef90
    parent: pqm at pqm.ubuntu.com-20070131140456-56881c31a01089a3
    parent: john at arbash-meinel.com-20070131163348-kkwfh7hvvm39tx8y
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Wed 2007-01-31 16:57:25 +0000
    message:
      fix --Derror => -Derror (trivial)
        ------------------------------------------------------------
        revno: 2245.1.2.1.1
        merged: john at arbash-meinel.com-20070131163348-kkwfh7hvvm39tx8y
        parent: pqm at pqm.ubuntu.com-20070131140456-56881c31a01089a3
        committer: John Arbash Meinel <john at arbash-meinel.com>
        branch nick: jam-integration
        timestamp: Wed 2007-01-31 10:33:48 -0600
        message:
          fix --Derror => -Derror (trivial)
    ------------------------------------------------------------
    revno: 2245.1.2
    merged: pqm at pqm.ubuntu.com-20070131140456-56881c31a01089a3
    parent: pqm at pqm.ubuntu.com-20070130211230-64c2d9b4bce9304f
    parent: aaron.bentley at utoronto.ca-20070131035136-exu2pa898yr2gzen
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Wed 2007-01-31 14:04:56 +0000
    message:
      Handle merge with dangling inventory entries
        ------------------------------------------------------------
        revno: 1551.2.49.1.40.1.22.1.42.1.31.1.2
        merged: aaron.bentley at utoronto.ca-20070131035136-exu2pa898yr2gzen
        parent: aaron.bentley at utoronto.ca-20070131020421-a9fhra61hiwr0kcb
        committer: Aaron Bentley <aaron.bentley at utoronto.ca>
        branch nick: Aaron's mergeable stuff
        timestamp: Tue 2007-01-30 22:51:36 -0500
        message:
          Handle merge with dangling inventory entries
        ------------------------------------------------------------
        revno: 1551.2.49.1.40.1.22.1.42.1.31.1.1
        merged: aaron.bentley at utoronto.ca-20070131020421-a9fhra61hiwr0kcb
        parent: abentley at panoramicfeedback.com-20070111155749-v70rw01ug96bs1ll
        parent: pqm at pqm.ubuntu.com-20070125194626-4ded330415b7276d
        committer: Aaron Bentley <aaron.bentley at utoronto.ca>
        branch nick: Aaron's mergeable stuff
        timestamp: Tue 2007-01-30 21:04:21 -0500
        message:
          Merge bzr.dev
    ------------------------------------------------------------
    revno: 2245.1.1
    merged: pqm at pqm.ubuntu.com-20070130211230-64c2d9b4bce9304f
    parent: pqm at pqm.ubuntu.com-20070125194626-4ded330415b7276d
    parent: robertc at robertcollins.net-20070130115230-2052dzn5xo9h6o07
    committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
    branch nick: +trunk
    timestamp: Tue 2007-01-30 21:12:30 +0000
    message:
      (robertc) Add Branch hooks faciity, allowing clean creation and management of hooks around branch API calls.
    ------------------------------------------------------------
    revno: 2245.2.3
    merged: robertc at robertcollins.net-20070130115230-2052dzn5xo9h6o07
    parent: robertc at robertcollins.net-20070130104104-wsikhrgretspg86m
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: branch-hook
    timestamp: Tue 2007-01-30 22:52:30 +1100
    message:
      Add install_hook to the BranchHooks class as the official means for installing a hook.
    ------------------------------------------------------------
    revno: 2245.2.2
    merged: robertc at robertcollins.net-20070130104104-wsikhrgretspg86m
    parent: robertc at robertcollins.net-20070129165849-409f5714fa7ebe48
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: branch-hook
    timestamp: Tue 2007-01-30 21:41:04 +1100
    message:
      Remove the static DefaultHooks method from Branch, replacing it with a derived dict BranchHooks object, which is easier to use and provides a place to put the policy-checking add method discussed on list.
    ------------------------------------------------------------
    revno: 2245.2.1
    merged: robertc at robertcollins.net-20070129165849-409f5714fa7ebe48
    parent: pqm at pqm.ubuntu.com-20070125194626-4ded330415b7276d
    committer: Robert Collins <robertc at robertcollins.net>
    branch nick: branch-hook
    timestamp: Tue 2007-01-30 03:58:49 +1100
    message:
      New Branch hooks facility, with one initial hook 'set_rh' which triggers
      whenever the revision history is set. This allows triggering on e.g.
      push, pull, commit, and so on. Developed for use with the branchrss
      plugin. See bzrlib/tests/branch_implementations/test_hooks for more
      details. (Robert Collins)
-------------- next part --------------
=== added file 'bzrlib/tests/branch_implementations/test_hooks.py'
--- a/bzrlib/tests/branch_implementations/test_hooks.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/branch_implementations/test_hooks.py	2007-01-30 10:41:04 +0000
@@ -0,0 +1,67 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests that branch classes implement hook callouts correctly."""
+
+from bzrlib.branch import Branch
+from bzrlib.tests import TestCaseWithMemoryTransport
+
+
+class TestPushHook(TestCaseWithMemoryTransport):
+
+    def setUp(self):
+        self.hook_calls = []
+        TestCaseWithMemoryTransport.setUp(self)
+
+    def capture_set_rh_hook(self, branch, rev_history):
+        """Capture post push hook calls to self.hook_calls.
+        
+        The call is logged, as is some state of the two branches.
+        """
+        self.hook_calls.append(
+            ('set_rh', branch, rev_history, branch.is_locked()))
+
+    def test_set_rh_empty_history(self):
+        branch = self.make_branch('source')
+        Branch.hooks['set_rh'].append(self.capture_set_rh_hook)
+        branch.set_revision_history([])
+        self.assertEqual(self.hook_calls,
+            [('set_rh', branch, [], True)])
+
+    def test_set_rh_nonempty_history(self):
+        branch = self.make_branch('source')
+        Branch.hooks['set_rh'].append(self.capture_set_rh_hook)
+        branch.set_revision_history([u'foo'])
+        self.assertEqual(self.hook_calls,
+            [('set_rh', branch, [u'foo'], True)])
+
+    def test_set_rh_branch_is_locked(self):
+        branch = self.make_branch('source')
+        Branch.hooks['set_rh'].append(self.capture_set_rh_hook)
+        branch.set_revision_history([])
+        self.assertEqual(self.hook_calls,
+            [('set_rh', branch, [], True)])
+
+    def test_set_rh_calls_all_hooks_no_errors(self):
+        branch = self.make_branch('source')
+        Branch.hooks['set_rh'].append(self.capture_set_rh_hook)
+        Branch.hooks['set_rh'].append(self.capture_set_rh_hook)
+        branch.set_revision_history([])
+        self.assertEqual(self.hook_calls,
+            [('set_rh', branch, [], True),
+             ('set_rh', branch, [], True),
+            ])
+

=== added file 'bzrlib/tests/branch_implementations/test_push.py'
--- a/bzrlib/tests/branch_implementations/test_push.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/branch_implementations/test_push.py	2007-01-30 20:58:25 +0000
@@ -0,0 +1,79 @@
+# Copyright (C) 2004, 2005, 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests for branch.push behaviour."""
+
+import os
+
+from bzrlib.branch import Branch
+from bzrlib import errors
+from bzrlib.tests import TestCaseWithTransport
+
+
+class TestPush(TestCaseWithTransport):
+
+    def test_push_convergence_simple(self):
+        # when revisions are pushed, the left-most accessible parents must 
+        # become the revision-history.
+        mine = self.make_branch_and_tree('mine')
+        mine.commit('1st post', rev_id='P1', allow_pointless=True)
+        other = mine.bzrdir.sprout('other').open_workingtree()
+        other.commit('my change', rev_id='M1', allow_pointless=True)
+        mine.merge_from_branch(other.branch)
+        mine.commit('merge my change', rev_id='P2')
+        mine.branch.push(other.branch)
+        self.assertEqual(['P1', 'P2'], other.branch.revision_history())
+
+    def test_push_merged_indirect(self):
+        # it should be possible to do a push from one branch into another
+        # when the tip of the target was merged into the source branch
+        # via a third branch - so its buried in the ancestry and is not
+        # directly accessible.
+        mine = self.make_branch_and_tree('mine')
+        mine.commit('1st post', rev_id='P1', allow_pointless=True)
+        target = mine.bzrdir.sprout('target').open_workingtree()
+        target.commit('my change', rev_id='M1', allow_pointless=True)
+        other = mine.bzrdir.sprout('other').open_workingtree()
+        other.merge_from_branch(target.branch)
+        other.commit('merge my change', rev_id='O2')
+        mine.merge_from_branch(other.branch)
+        mine.commit('merge other', rev_id='P2')
+        mine.branch.push(target.branch)
+        self.assertEqual(['P1', 'P2'], target.branch.revision_history())
+
+    def test_push_to_checkout_updates_master(self):
+        """Pushing into a checkout updates the checkout and the master branch"""
+        master_tree = self.make_branch_and_tree('master')
+        rev1 = master_tree.commit('master')
+        checkout = master_tree.branch.create_checkout('checkout')
+
+        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
+        rev2 = other.commit('other commit')
+        # now push, which should update both checkout and master.
+        other.branch.push(checkout.branch)
+        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
+        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
+
+    def test_push_raises_specific_error_on_master_connection_error(self):
+        master_tree = self.make_branch_and_tree('master')
+        checkout = master_tree.branch.create_checkout('checkout')
+        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
+        # move the branch out of the way on disk to cause a connection
+        # error.
+        os.rename('master', 'master_gone')
+        # try to push, which should raise a BoundBranchConnectionFailure.
+        self.assertRaises(errors.BoundBranchConnectionFailure,
+                other.branch.push, checkout.branch)

=== modified file 'NEWS'
--- a/NEWS	2007-01-29 21:11:28 +0000
+++ b/NEWS	2007-02-01 21:29:19 +0000
@@ -25,12 +25,24 @@
       versionedfiles, repositories, branches, and working trees
       (Aaron Bentley)
 
+    * New Branch hooks facility, with one initial hook 'set_rh' which triggers
+      whenever the revision history is set. This allows triggering on e.g.
+      push, pull, commit, and so on. Developed for use with the branchrss
+      plugin. See bzrlib.branch.BranchHooks for more details. (Robert Collins)
+
+    * New method ``Branch.push()`` which should be used when pushing from a
+      branch as it makes performance and policy decisions to match the UI
+      level command ``push``. (Robert Collins).
+
   BUGFIXES:
 
     * ``bzr annotate`` now uses dotted revnos from the viewpoint of the
       branch, rather than the last changed revision of the file.
       (John Arbash Meinel, #82158)
 
+    * Lock operations no longer hang if they encounter a permission problem.
+      (Aaron Bentley)
+
   TESTING:
 
     * New ``--first`` option to ``bzr selftest`` to run specified tests

=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2007-01-17 15:37:08 +0000
+++ b/bzrlib/branch.py	2007-02-01 17:18:42 +0000
@@ -81,6 +81,8 @@
 
     base
         Base directory/url of the branch.
+
+    hooks: An instance of BranchHooks.
     """
     # this is really an instance variable - FIXME move it there
     # - RBC 20060112
@@ -375,8 +377,19 @@
         return history[revno - 1]
 
     def pull(self, source, overwrite=False, stop_revision=None):
+        """Mirror source into this branch.
+
+        This branch is considered to be 'local', having low latency.
+        """
         raise NotImplementedError(self.pull)
 
+    def push(self, target, overwrite=False, stop_revision=None):
+        """Mirror this branch into target.
+
+        This branch is considered to be 'local', having low latency.
+        """
+        raise NotImplementedError(self.push)
+
     def basis_tree(self):
         """Return `Tree` object for last revision."""
         return self.repository.revision_tree(self.last_revision())
@@ -599,7 +612,7 @@
             format = self.repository.bzrdir.cloning_metadir()
         return format
 
-    def create_checkout(self, to_location, revision_id=None, 
+    def create_checkout(self, to_location, revision_id=None,
                         lightweight=False):
         """Create a checkout of a branch.
         
@@ -716,6 +729,45 @@
         return self.get_format_string().rstrip()
 
 
+class BranchHooks(dict):
+    """A dictionary mapping hook name to a list of callables for branch hooks.
+    
+    e.g. ['set_rh'] Is the list of items to be called when the
+    set_revision_history function is invoked.
+    """
+
+    def __init__(self):
+        """Create the default hooks.
+
+        These are all empty initially, because by default nothing should get
+        notified.
+        """
+        dict.__init__(self)
+        # invoked whenever the revision history has been set
+        # with set_revision_history. The api signature is
+        # (branch, revision_history), and the branch will
+        # be write-locked. Introduced in 0.15.
+        self['set_rh'] = []
+
+    def install_hook(self, hook_name, a_callable):
+        """Install a_callable in to the hook hook_name.
+
+        :param hook_name: A hook name. See the __init__ method of BranchHooks
+            for the complete list of hooks.
+        :param a_callable: The callable to be invoked when the hook triggers.
+            The exact signature will depend on the hook - see the __init__ 
+            method of BranchHooks for details on each hook.
+        """
+        try:
+            self[hook_name].append(a_callable)
+        except KeyError:
+            raise errors.UnknownHook('branch', hook_name)
+
+
+# install the default hooks into the Branch class.
+Branch.hooks = BranchHooks()
+
+
 class BzrBranchFormat4(BranchFormat):
     """Bzr branch format 4.
 
@@ -1101,6 +1153,8 @@
             # this call is disabled because revision_history is 
             # not really an object yet, and the transaction is for objects.
             # transaction.register_clean(history)
+        for hook in Branch.hooks['set_rh']:
+            hook(self, rev_history)
 
     @needs_read_lock
     def revision_history(self):
@@ -1205,6 +1259,24 @@
         finally:
             source.unlock()
 
+    @needs_read_lock
+    def push(self, target, overwrite=False, stop_revision=None):
+        """See Branch.push."""
+        target.lock_write()
+        try:
+            old_count = len(target.revision_history())
+            try:
+                target.update_revisions(self, stop_revision)
+            except DivergedBranches:
+                if not overwrite:
+                    raise
+            if overwrite:
+                target.set_revision_history(self.revision_history())
+            new_count = len(target.revision_history())
+            return new_count - old_count
+        finally:
+            target.unlock()
+
     def get_parent(self):
         """See Branch.get_parent."""
 
@@ -1283,7 +1355,7 @@
         
     @needs_write_lock
     def pull(self, source, overwrite=False, stop_revision=None):
-        """Updates branch.pull to be bound branch aware."""
+        """Extends branch.pull to be bound branch aware."""
         bound_location = self.get_bound_location()
         if source.base != bound_location:
             # not pulling from master, so we need to update master.
@@ -1293,6 +1365,22 @@
                 source = master_branch
         return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
+    @needs_write_lock
+    def push(self, target, overwrite=False, stop_revision=None):
+        """Updates branch.push to be bound branch aware."""
+        bound_location = target.get_bound_location()
+        if target.base != bound_location:
+            # not pushing to master, so we need to update master.
+            master_branch = target.get_master_branch()
+            if master_branch:
+                # push into the master from this branch.
+                super(BzrBranch5, self).push(master_branch, overwrite,
+                    stop_revision)
+        # and push into the target branch from this. Note that we push from
+        # this branch again, because its considered the highest bandwidth
+        # repository.
+        return super(BzrBranch5, self).push(target, overwrite, stop_revision)
+
     def get_bound_location(self):
         try:
             return self.control_files.get_utf8('bound').read()[:-1]

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-01-24 17:06:02 +0000
+++ b/bzrlib/builtins.py	2007-01-30 21:06:11 +0000
@@ -676,11 +676,16 @@
                 except errors.NotLocalUrl:
                     warning('This transport does not update the working '
                             'tree of: %s' % (br_to.base,))
-                    count = br_to.pull(br_from, overwrite)
+                    count = br_from.push(br_to, overwrite)
                 except errors.NoWorkingTree:
-                    count = br_to.pull(br_from, overwrite)
+                    count = br_from.push(br_to, overwrite)
                 else:
-                    count = tree_to.pull(br_from, overwrite)
+                    tree_to.lock_write()
+                    try:
+                        count = br_from.push(tree_to.branch, overwrite)
+                        tree_to.update()
+                    finally:
+                        tree_to.unlock()
             except errors.DivergedBranches:
                 raise errors.BzrCommandError('These branches have diverged.'
                                         '  Try using "merge" and then "push".')

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2007-01-19 19:23:53 +0000
+++ b/bzrlib/errors.py	2007-01-30 11:52:30 +0000
@@ -405,6 +405,16 @@
         self.args = [base] + list(args)
 
 
+class UnknownHook(BzrError):
+
+    _fmt = "The %(type)s hook '%(hook)s' is unknown in this version of bzrlib."
+
+    def __init__(self, hook_type, hook_name):
+        BzrError.__init__(self)
+        self.type = hook_type
+        self.hook = hook_name
+
+
 class UnsupportedProtocol(PathError):
 
     _fmt = 'Unsupported protocol for url "%(path)s"%(extra)s'

=== modified file 'bzrlib/help_topics.py'
--- a/bzrlib/help_topics.py	2007-01-17 14:46:47 +0000
+++ b/bzrlib/help_topics.py	2007-01-31 16:33:48 +0000
@@ -141,7 +141,7 @@
                This does not suppress other plugin effects
 --no-plugins   Do not process any plugins
 
---Derror       Instead of normal error handling, always print a traceback on
+-Derror        Instead of normal error handling, always print a traceback on
                error.
 --profile      Profile execution using the hotshot profiler
 --lsprof       Profile execution using the lsprof profiler

=== modified file 'bzrlib/lockdir.py'
--- a/bzrlib/lockdir.py	2006-11-10 21:06:11 +0000
+++ b/bzrlib/lockdir.py	2007-01-31 18:49:12 +0000
@@ -220,6 +220,8 @@
             self.transport.rename(tmpname, self._held_dir)
             self._lock_held = True
             self.confirm()
+        except errors.PermissionDenied:
+            raise
         except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
             mutter("contention on %r: %s", self, e)
             raise LockContention(self)

=== modified file 'bzrlib/merge.py'
--- a/bzrlib/merge.py	2006-11-10 21:06:11 +0000
+++ b/bzrlib/merge.py	2007-01-31 03:51:36 +0000
@@ -572,7 +572,8 @@
             parent_id = self.tt.final_parent(trans_id)
             if file_id in self.this_tree.inventory:
                 self.tt.unversion_file(trans_id)
-                self.tt.delete_contents(trans_id)
+                if file_id in self.this_tree:
+                    self.tt.delete_contents(trans_id)
             file_group = self._dump_conflicts(name, parent_id, file_id, 
                                               set_version=True)
             self._raw_conflicts.append(('contents conflict', file_group))

=== modified file 'bzrlib/osutils.py'
--- a/bzrlib/osutils.py	2007-01-04 23:36:44 +0000
+++ b/bzrlib/osutils.py	2007-02-01 20:36:41 +0000
@@ -40,7 +40,6 @@
 from shutil import (
     rmtree,
     )
-import string
 import tempfile
 from tempfile import (
     mkdtemp,
@@ -777,7 +776,15 @@
 
 def contains_whitespace(s):
     """True if there are any whitespace characters in s."""
-    for ch in string.whitespace:
+    # string.whitespace can include '\xa0' in certain locales, because it is
+    # considered "non-breaking-space" as part of ISO-8859-1. But it
+    # 1) Isn't a breaking whitespace
+    # 2) Isn't one of ' \t\r\n' which are characters we sometimes use as
+    #    separators
+    # 3) '\xa0' isn't unicode safe since it is >128.
+    # So we are following textwrap's example and hard-coding our own.
+    # We probably could ignore \v and \f, too.
+    for ch in u' \t\n\r\v\f':
         if ch in s:
             return True
     else:
@@ -915,6 +922,19 @@
     return sys.platform != "win32"
 
 
+def supports_posix_readonly():
+    """Return True if 'readonly' has POSIX semantics, False otherwise.
+
+    Notably, a win32 readonly file cannot be deleted, unlike POSIX where the
+    directory controls creation/deletion, etc.
+
+    And under win32, readonly means that the directory itself cannot be
+    deleted.  The contents of a readonly directory can be changed, unlike POSIX
+    where files in readonly directories cannot be added, deleted or renamed.
+    """
+    return sys.platform != "win32"
+
+
 def set_or_unset_env(env_variable, value):
     """Modify the environment, setting or removing the env_variable.
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2007-01-19 19:42:19 +0000
+++ b/bzrlib/tests/__init__.py	2007-01-30 10:41:04 +0000
@@ -569,6 +569,12 @@
         self._startLogFile()
         self._benchcalls = []
         self._benchtime = None
+        # prevent hooks affecting tests
+        self._preserved_hooks = bzrlib.branch.Branch.hooks
+        self.addCleanup(self._restoreHooks)
+        # this list of hooks must be kept in sync with the defaults
+        # in branch.py
+        bzrlib.branch.Branch.hooks = bzrlib.branch.BranchHooks()
 
     def _silenceUI(self):
         """Turn off UI for duration of test"""
@@ -835,6 +841,9 @@
         for name, value in self.__old_env.iteritems():
             osutils.set_or_unset_env(name, value)
 
+    def _restoreHooks(self):
+        bzrlib.branch.Branch.hooks = self._preserved_hooks
+
     def tearDown(self):
         self._runCleanups()
         unittest.TestCase.tearDown(self)

=== modified file 'bzrlib/tests/branch_implementations/__init__.py'
--- a/bzrlib/tests/branch_implementations/__init__.py	2006-10-11 23:08:27 +0000
+++ b/bzrlib/tests/branch_implementations/__init__.py	2007-02-01 17:18:42 +0000
@@ -42,11 +42,13 @@
         'bzrlib.tests.branch_implementations.test_bound_sftp',
         'bzrlib.tests.branch_implementations.test_branch',
         'bzrlib.tests.branch_implementations.test_break_lock',
+        'bzrlib.tests.branch_implementations.test_hooks',
         'bzrlib.tests.branch_implementations.test_http',
         'bzrlib.tests.branch_implementations.test_locking',
         'bzrlib.tests.branch_implementations.test_parent',
         'bzrlib.tests.branch_implementations.test_permissions',
         'bzrlib.tests.branch_implementations.test_pull',
+        'bzrlib.tests.branch_implementations.test_push',
         'bzrlib.tests.branch_implementations.test_update',
         ]
     adapter = BranchTestProviderAdapter(

=== modified file 'bzrlib/tests/branch_implementations/test_bound_sftp.py'
--- a/bzrlib/tests/branch_implementations/test_bound_sftp.py	2007-01-05 17:12:03 +0000
+++ b/bzrlib/tests/branch_implementations/test_bound_sftp.py	2007-01-30 20:58:25 +0000
@@ -184,18 +184,6 @@
         self.assertEqual(['r at b-1'], wt_child.branch.revision_history())
         self.assertEqual(['r at b-1'], sftp_b_newbase.revision_history())
 
-    def test_pull_updates_both(self):
-        b_base, wt_child = self.create_branches()
-
-        wt_newchild = b_base.bzrdir.sprout('newchild').open_workingtree()
-        open('newchild/b', 'wb').write('newchild b contents\n')
-        wt_newchild.commit('newchild', rev_id='r at d-2')
-        self.assertEqual(['r at b-1', 'r at d-2'], wt_newchild.branch.revision_history())
-
-        wt_child.pull(wt_newchild.branch)
-        self.assertEqual(['r at b-1', 'r at d-2'], wt_child.branch.revision_history())
-        self.assertEqual(['r at b-1', 'r at d-2'], b_base.revision_history())
-
     def test_bind_diverged(self):
         from bzrlib.builtins import merge
 
@@ -333,25 +321,6 @@
         self.assertRaises(errors.BoundBranchConnectionFailure,
                 wt_child.commit, 'added text', rev_id='r at c-2')
 
-    def test_pull_fails(self):
-        b_base, wt_child = self.create_branches()
-
-        wt_other = wt_child.bzrdir.sprout('other').open_workingtree()
-        open('other/a', 'wb').write('new contents\n')
-        wt_other.commit('changed a', rev_id='r at d-2')
-
-        self.assertEqual(['r at b-1'], b_base.revision_history())
-        self.assertEqual(['r at b-1'], wt_child.branch.revision_history())
-        self.assertEqual(['r at b-1', 'r at d-2'], wt_other.branch.revision_history())
-
-        # this deletes the branch from memory
-        del b_base
-        # and this moves it out of the way on disk
-        os.rename('base', 'hidden_base')
-
-        self.assertRaises(errors.BoundBranchConnectionFailure,
-                wt_child.pull, wt_other.branch)
-
     # TODO: jam 20051231 We need invasive failure tests, so that we can show
     #       performance even when something fails.
 

=== modified file 'bzrlib/tests/branch_implementations/test_pull.py'
--- a/bzrlib/tests/branch_implementations/test_pull.py	2006-10-11 23:08:27 +0000
+++ b/bzrlib/tests/branch_implementations/test_pull.py	2007-01-30 20:58:25 +0000
@@ -19,7 +19,7 @@
 import os
 
 from bzrlib.branch import Branch
-from bzrlib.osutils import abspath, realpath
+from bzrlib import errors
 from bzrlib.tests import TestCaseWithTransport
 
 
@@ -53,3 +53,27 @@
         parent.commit('merge other', rev_id='P2')
         mine.pull(parent.branch)
         self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
+
+    def test_pull_updates_checkout_and_master(self):
+        """Pulling into a checkout updates the checkout and the master branch"""
+        master_tree = self.make_branch_and_tree('master')
+        rev1 = master_tree.commit('master')
+        checkout = master_tree.branch.create_checkout('checkout')
+
+        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
+        rev2 = other.commit('other commit')
+        # now pull, which should update both checkout and master.
+        checkout.branch.pull(other.branch)
+        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
+        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
+
+    def test_pull_raises_specific_error_on_master_connection_error(self):
+        master_tree = self.make_branch_and_tree('master')
+        checkout = master_tree.branch.create_checkout('checkout')
+        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
+        # move the branch out of the way on disk to cause a connection
+        # error.
+        os.rename('master', 'master_gone')
+        # try to pull, which should raise a BoundBranchConnectionFailure.
+        self.assertRaises(errors.BoundBranchConnectionFailure,
+                checkout.branch.pull, other.branch)

=== modified file 'bzrlib/tests/test_branch.py'
--- a/bzrlib/tests/test_branch.py	2006-09-07 23:31:28 +0000
+++ b/bzrlib/tests/test_branch.py	2007-01-30 11:52:30 +0000
@@ -32,6 +32,7 @@
                            BzrDir, BzrDirFormat)
 from bzrlib.errors import (NotBranchError,
                            UnknownFormatError,
+                           UnknownHook,
                            UnsupportedFormatError,
                            )
 
@@ -162,3 +163,27 @@
         self.assertEqual(made_branch.base, target_branch.base)
         opened_branch = branch_dir.open_branch()
         self.assertEqual(opened_branch.base, target_branch.base)
+
+
+class TestHooks(TestCase):
+
+    def test_constructor(self):
+        """Check that creating a BranchHooks instance has the right defaults."""
+        hooks = bzrlib.branch.BranchHooks()
+        self.assertTrue("set_rh" in hooks, "set_rh not in %s" % hooks)
+
+    def test_installed_hooks_are_BranchHooks(self):
+        """The installed hooks object should be a BranchHooks."""
+        # the installed hooks are saved in self._preserved_hooks.
+        self.assertIsInstance(self._preserved_hooks, bzrlib.branch.BranchHooks)
+
+    def test_install_hook_raises_unknown_hook(self):
+        """install_hook should raise UnknownHook if a hook is unknown."""
+        hooks = bzrlib.branch.BranchHooks()
+        self.assertRaises(UnknownHook, hooks.install_hook, 'silly', None)
+
+    def test_install_hook_appends_known_hook(self):
+        """install_hook should append the callable for known hooks."""
+        hooks = bzrlib.branch.BranchHooks()
+        hooks.install_hook('set_rh', None)
+        self.assertEqual(hooks['set_rh'], [None])

=== modified file 'bzrlib/tests/test_errors.py'
--- a/bzrlib/tests/test_errors.py	2006-12-21 20:07:34 +0000
+++ b/bzrlib/tests/test_errors.py	2007-01-30 11:52:30 +0000
@@ -94,6 +94,16 @@
             "the current request that is open.",
             str(error))
 
+    def test_unknown_hook(self):
+        error = errors.UnknownHook("branch", "foo")
+        self.assertEqualDiff("The branch hook 'foo' is unknown in this version"
+            " of bzrlib.",
+            str(error))
+        error = errors.UnknownHook("tree", "bar")
+        self.assertEqualDiff("The tree hook 'bar' is unknown in this version"
+            " of bzrlib.",
+            str(error))
+
     def test_up_to_date(self):
         error = errors.UpToDateFormat(bzrdir.BzrDirFormat4())
         self.assertEqualDiff("The branch format Bazaar-NG branch, "

=== modified file 'bzrlib/tests/test_lockdir.py'
--- a/bzrlib/tests/test_lockdir.py	2006-09-29 20:12:58 +0000
+++ b/bzrlib/tests/test_lockdir.py	2007-02-01 14:59:04 +0000
@@ -17,13 +17,16 @@
 """Tests for LockDir"""
 
 from cStringIO import StringIO
+import os
 from threading import Thread, Lock
 import time
 
 import bzrlib
 from bzrlib import (
     config,
+    errors,
     osutils,
+    tests,
     )
 from bzrlib.errors import (
         LockBreakMismatch,
@@ -600,3 +603,12 @@
         ld1.create()
         ld1.lock_write()
         ld1.unlock()
+
+    def test_lock_permission(self):
+        if not osutils.supports_posix_readonly():
+            raise tests.TestSkipped('Cannot induce a permission failure')
+        ld1 = self.get_lock()
+        lock_path = ld1.transport.local_abspath('test_lock')
+        os.mkdir(lock_path)
+        osutils.make_readonly(lock_path)
+        self.assertRaises(errors.PermissionDenied, ld1.attempt_lock)

=== modified file 'bzrlib/tests/test_merge.py'
--- a/bzrlib/tests/test_merge.py	2006-10-11 23:08:27 +0000
+++ b/bzrlib/tests/test_merge.py	2007-01-31 03:51:36 +0000
@@ -173,3 +173,16 @@
             conflicts.DeletingParent('Not deleting', 'b', 'b-id'),
             conflicts.UnversionedParent('Versioned directory', 'b', 'b-id')],
             tree_a.conflicts())
+
+    def test_merge_with_missing(self):
+        tree_a = self.make_branch_and_tree('tree_a')
+        self.build_tree_contents([('tree_a/file', 'content_1')])
+        tree_a.add('file')
+        tree_a.commit('commit base')
+        base_tree = tree_a.basis_tree()
+        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
+        self.build_tree_contents([('tree_a/file', 'content_2')])
+        tree_a.commit('commit other')
+        other_tree = tree_a.basis_tree()
+        os.unlink('tree_b/file')
+        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)

=== modified file 'bzrlib/tests/test_osutils.py'
--- a/bzrlib/tests/test_osutils.py	2007-01-04 23:36:44 +0000
+++ b/bzrlib/tests/test_osutils.py	2007-02-01 14:49:53 +0000
@@ -38,6 +38,20 @@
 
 class TestOSUtils(TestCaseInTempDir):
 
+    def test_contains_whitespace(self):
+        self.failUnless(osutils.contains_whitespace(u' '))
+        self.failUnless(osutils.contains_whitespace(u'hello there'))
+        self.failUnless(osutils.contains_whitespace(u'hellothere\n'))
+        self.failUnless(osutils.contains_whitespace(u'hello\nthere'))
+        self.failUnless(osutils.contains_whitespace(u'hello\rthere'))
+        self.failUnless(osutils.contains_whitespace(u'hello\tthere'))
+
+        # \xa0 is "Non-breaking-space" which on some python locales thinks it
+        # is whitespace, but we do not.
+        self.failIf(osutils.contains_whitespace(u''))
+        self.failIf(osutils.contains_whitespace(u'hellothere'))
+        self.failIf(osutils.contains_whitespace(u'hello\xa0there'))
+
     def test_fancy_rename(self):
         # This should work everywhere
         def rename(a, b):

=== modified file 'bzrlib/tests/test_selftest.py'
--- a/bzrlib/tests/test_selftest.py	2007-01-11 08:45:51 +0000
+++ b/bzrlib/tests/test_selftest.py	2007-01-30 10:41:04 +0000
@@ -852,7 +852,12 @@
         self.assertContainsRe(
             output_stream.getvalue(),
             r"\d+ms/ +\d+ms\n$")
-        
+
+    def test_hooks_sanitised(self):
+        """The bzrlib hooks should be sanitised by setUp."""
+        self.assertEqual(bzrlib.branch.BranchHooks(),
+            bzrlib.branch.Branch.hooks)
+
     def test__gather_lsprof_in_benchmarks(self):
         """When _gather_lsprof_in_benchmarks is on, accumulate profile data.
         

=== modified file 'bzrlib/transform.py'
--- a/bzrlib/transform.py	2006-12-07 04:06:34 +0000
+++ b/bzrlib/transform.py	2007-01-31 17:48:55 +0000
@@ -273,8 +273,7 @@
                 os.unlink(name)
                 raise
 
-            for segment in contents:
-                f.write(segment)
+            f.writelines(contents)
         finally:
             f.close()
         self._set_mode(trans_id, mode_id, S_ISREG)



More information about the bazaar-commits mailing list