1
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for branch.push behaviour."""
21
from bzrlib import bzrdir, errors
22
from bzrlib.branch import Branch
23
from bzrlib.bzrdir import BzrDir
24
from bzrlib.memorytree import MemoryTree
25
from bzrlib.remote import RemoteBranch
26
from bzrlib.revision import NULL_REVISION
27
from bzrlib.tests import TestSkipped
28
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
29
from bzrlib.transport.local import LocalURLServer
32
class TestPush(TestCaseWithBranch):
34
def test_push_convergence_simple(self):
35
# when revisions are pushed, the left-most accessible parents must
36
# become the revision-history.
37
mine = self.make_branch_and_tree('mine')
38
mine.commit('1st post', rev_id='P1', allow_pointless=True)
39
other = mine.bzrdir.sprout('other').open_workingtree()
40
other.commit('my change', rev_id='M1', allow_pointless=True)
41
mine.merge_from_branch(other.branch)
42
mine.commit('merge my change', rev_id='P2')
43
result = mine.branch.push(other.branch)
44
self.assertEqual(['P1', 'P2'], other.branch.revision_history())
45
# result object contains some structured data
46
self.assertEqual(result.old_revid, 'M1')
47
self.assertEqual(result.new_revid, 'P2')
48
# and it can be treated as an integer for compatibility
49
self.assertEqual(int(result), 0)
51
def test_push_merged_indirect(self):
52
# it should be possible to do a push from one branch into another
53
# when the tip of the target was merged into the source branch
54
# via a third branch - so its buried in the ancestry and is not
55
# directly accessible.
56
mine = self.make_branch_and_tree('mine')
57
mine.commit('1st post', rev_id='P1', allow_pointless=True)
58
target = mine.bzrdir.sprout('target').open_workingtree()
59
target.commit('my change', rev_id='M1', allow_pointless=True)
60
other = mine.bzrdir.sprout('other').open_workingtree()
61
other.merge_from_branch(target.branch)
62
other.commit('merge my change', rev_id='O2')
63
mine.merge_from_branch(other.branch)
64
mine.commit('merge other', rev_id='P2')
65
mine.branch.push(target.branch)
66
self.assertEqual(['P1', 'P2'], target.branch.revision_history())
68
def test_push_to_checkout_updates_master(self):
69
"""Pushing into a checkout updates the checkout and the master branch"""
70
master_tree = self.make_branch_and_tree('master')
71
checkout = self.make_branch_and_tree('checkout')
73
checkout.branch.bind(master_tree.branch)
74
except errors.UpgradeRequired:
75
# cant bind this format, the test is irrelevant.
77
rev1 = checkout.commit('master')
79
other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
80
rev2 = other.commit('other commit')
81
# now push, which should update both checkout and master.
82
other.branch.push(checkout.branch)
83
self.assertEqual([rev1, rev2], checkout.branch.revision_history())
84
self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
86
def test_push_raises_specific_error_on_master_connection_error(self):
87
master_tree = self.make_branch_and_tree('master')
88
checkout = self.make_branch_and_tree('checkout')
90
checkout.branch.bind(master_tree.branch)
91
except errors.UpgradeRequired:
92
# cant bind this format, the test is irrelevant.
94
other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
95
# move the branch out of the way on disk to cause a connection
97
os.rename('master', 'master_gone')
98
# try to push, which should raise a BoundBranchConnectionFailure.
99
self.assertRaises(errors.BoundBranchConnectionFailure,
100
other.branch.push, checkout.branch)
102
def test_push_uses_read_lock(self):
103
"""Push should only need a read lock on the source side."""
104
source = self.make_branch_and_tree('source')
105
target = self.make_branch('target')
107
self.build_tree(['source/a'])
111
source.branch.lock_read()
115
source.branch.push(target, stop_revision=source.last_revision())
119
source.branch.unlock()
121
def test_push_within_repository(self):
122
"""Push from one branch to another inside the same repository."""
124
repo = self.make_repository('repo', shared=True)
125
except (errors.IncompatibleFormat, errors.UninitializableFormat):
126
# This Branch format cannot create shared repositories
128
# This is a little bit trickier because make_branch_and_tree will not
129
# re-use a shared repository.
130
a_bzrdir = self.make_bzrdir('repo/tree')
132
a_branch = self.branch_format.initialize(a_bzrdir)
133
except (errors.UninitializableFormat):
134
# Cannot create these branches
137
tree = a_branch.bzrdir.create_workingtree()
138
except errors.NotLocalUrl:
139
if self.vfs_transport_factory is LocalURLServer:
140
# the branch is colocated on disk, we cannot create a checkout.
141
# hopefully callers will expect this.
142
local_controldir= bzrdir.BzrDir.open(self.get_vfs_only_url('repo/tree'))
143
tree = local_controldir.create_workingtree()
145
tree = a_branch.create_checkout('repo/tree', lightweight=True)
146
self.build_tree(['repo/tree/a'])
150
to_bzrdir = self.make_bzrdir('repo/branch')
151
to_branch = self.branch_format.initialize(to_bzrdir)
152
tree.branch.push(to_branch)
154
self.assertEqual(tree.branch.last_revision(),
155
to_branch.last_revision())
158
class TestPushHook(TestCaseWithBranch):
162
TestCaseWithBranch.setUp(self)
164
def capture_post_push_hook(self, result):
165
"""Capture post push hook calls to self.hook_calls.
167
The call is logged, as is some state of the two branches.
169
if result.local_branch:
170
local_locked = result.local_branch.is_locked()
171
local_base = result.local_branch.base
175
self.hook_calls.append(
176
('post_push', result.source_branch, local_base,
177
result.master_branch.base,
178
result.old_revno, result.old_revid,
179
result.new_revno, result.new_revid,
180
result.source_branch.is_locked(), local_locked,
181
result.master_branch.is_locked()))
183
def test_post_push_empty_history(self):
184
target = self.make_branch('target')
185
source = self.make_branch('source')
186
Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
188
# with nothing there we should still get a notification, and
189
# have both branches locked at the notification time.
191
('post_push', source, None, target.base, 0, NULL_REVISION,
192
0, NULL_REVISION, True, None, True)
196
def test_post_push_bound_branch(self):
197
# pushing to a bound branch should pass in the master branch to the
198
# hook, allowing the correct number of emails to be sent, while still
199
# allowing hooks that want to modify the target to do so to both
201
target = self.make_branch('target')
202
local = self.make_branch('local')
205
except errors.UpgradeRequired:
206
# We can't bind this format to itself- typically it is the local
207
# branch that doesn't support binding. As of May 2007
208
# remotebranches can't be bound. Let's instead make a new local
209
# branch of the default type, which does allow binding.
210
# See https://bugs.launchpad.net/bzr/+bug/112020
211
local = BzrDir.create_branch_convenience('local2')
213
source = self.make_branch('source')
214
Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
216
# with nothing there we should still get a notification, and
217
# have both branches locked at the notification time.
219
('post_push', source, local.base, target.base, 0, NULL_REVISION,
220
0, NULL_REVISION, True, True, True)
224
def test_post_push_nonempty_history(self):
225
target = self.make_branch_and_memory_tree('target')
228
rev1 = target.commit('rev 1')
230
sourcedir = target.bzrdir.clone(self.get_url('source'))
231
source = MemoryTree.create_on_branch(sourcedir.open_branch())
232
rev2 = source.commit('rev 2')
233
Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
234
source.branch.push(target.branch)
235
# with nothing there we should still get a notification, and
236
# have both branches locked at the notification time.
238
('post_push', source.branch, None, target.branch.base, 1, rev1,
239
2, rev2, True, None, True)