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.pull behaviour."""
21
from bzrlib.branch import Branch, BzrBranchFormat5
22
from bzrlib.bzrdir import BzrDir
23
from bzrlib import errors
24
from bzrlib.memorytree import MemoryTree
25
from bzrlib.revision import NULL_REVISION
26
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
29
class TestPull(TestCaseWithBranch):
31
def test_pull_convergence_simple(self):
32
# when revisions are pulled, the left-most accessible parents must
33
# become the revision-history.
34
parent = self.make_branch_and_tree('parent')
35
parent.commit('1st post', rev_id='P1', allow_pointless=True)
36
mine = parent.bzrdir.sprout('mine').open_workingtree()
37
mine.commit('my change', rev_id='M1', allow_pointless=True)
38
parent.merge_from_branch(mine.branch)
39
parent.commit('merge my change', rev_id='P2')
40
mine.pull(parent.branch)
41
self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
43
def test_pull_merged_indirect(self):
44
# it should be possible to do a pull from one branch into another
45
# when the tip of the target was merged into the source branch
46
# via a third branch - so its buried in the ancestry and is not
47
# directly accessible.
48
parent = self.make_branch_and_tree('parent')
49
parent.commit('1st post', rev_id='P1', allow_pointless=True)
50
mine = parent.bzrdir.sprout('mine').open_workingtree()
51
mine.commit('my change', rev_id='M1', allow_pointless=True)
52
other = parent.bzrdir.sprout('other').open_workingtree()
53
other.merge_from_branch(mine.branch)
54
other.commit('merge my change', rev_id='O2')
55
parent.merge_from_branch(other.branch)
56
parent.commit('merge other', rev_id='P2')
57
mine.pull(parent.branch)
58
self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
60
def test_pull_updates_checkout_and_master(self):
61
"""Pulling into a checkout updates the checkout and the master branch"""
62
master_tree = self.make_branch_and_tree('master')
63
rev1 = master_tree.commit('master')
64
checkout = master_tree.branch.create_checkout('checkout')
66
other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
67
rev2 = other.commit('other commit')
68
# now pull, which should update both checkout and master.
69
checkout.branch.pull(other.branch)
70
self.assertEqual([rev1, rev2], checkout.branch.revision_history())
71
self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
73
def test_pull_raises_specific_error_on_master_connection_error(self):
74
master_tree = self.make_branch_and_tree('master')
75
checkout = master_tree.branch.create_checkout('checkout')
76
other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
77
# move the branch out of the way on disk to cause a connection
79
os.rename('master', 'master_gone')
80
# try to pull, which should raise a BoundBranchConnectionFailure.
81
self.assertRaises(errors.BoundBranchConnectionFailure,
82
checkout.branch.pull, other.branch)
84
def test_pull_returns_result(self):
85
parent = self.make_branch_and_tree('parent')
86
parent.commit('1st post', rev_id='P1')
87
mine = parent.bzrdir.sprout('mine').open_workingtree()
88
mine.commit('my change', rev_id='M1')
89
result = parent.branch.pull(mine.branch)
90
self.assertIsNot(None, result)
91
self.assertIs(mine.branch, result.source_branch)
92
self.assertIs(parent.branch, result.target_branch)
93
self.assertIs(parent.branch, result.master_branch)
94
self.assertIs(None, result.local_branch)
95
self.assertEqual(1, result.old_revno)
96
self.assertEqual('P1', result.old_revid)
97
self.assertEqual(2, result.new_revno)
98
self.assertEqual('M1', result.new_revid)
99
self.assertEqual(None, result.tag_conflicts)
101
def test_pull_overwrite(self):
102
tree_a = self.make_branch_and_tree('tree_a')
103
tree_a.commit('message 1')
104
tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
105
tree_a.commit('message 2', rev_id='rev2a')
106
tree_b.commit('message 2', rev_id='rev2b')
107
self.assertRaises(errors.DivergedBranches, tree_a.pull, tree_b.branch)
108
self.assertRaises(errors.DivergedBranches,
109
tree_a.branch.pull, tree_b.branch,
110
overwrite=False, stop_revision='rev2b')
111
# It should not have updated the branch tip, but it should have fetched
113
self.assertEqual('rev2a', tree_a.branch.last_revision())
114
self.assertTrue(tree_a.branch.repository.has_revision('rev2b'))
115
tree_a.branch.pull(tree_b.branch, overwrite=True,
116
stop_revision='rev2b')
117
self.assertEqual('rev2b', tree_a.branch.last_revision())
118
self.assertEqual(tree_b.branch.revision_history(),
119
tree_a.branch.revision_history())
122
class TestPullHook(TestCaseWithBranch):
126
TestCaseWithBranch.setUp(self)
128
def capture_post_pull_hook(self, result):
129
"""Capture post pull hook calls to self.hook_calls.
131
The call is logged, as is some state of the two branches.
133
if result.local_branch:
134
local_locked = result.local_branch.is_locked()
135
local_base = result.local_branch.base
139
self.hook_calls.append(
140
('post_pull', result.source_branch, local_base,
141
result.master_branch.base, result.old_revno,
143
result.new_revno, result.new_revid,
144
result.source_branch.is_locked(), local_locked,
145
result.master_branch.is_locked()))
147
def test_post_pull_empty_history(self):
148
target = self.make_branch('target')
149
source = self.make_branch('source')
150
Branch.hooks.install_named_hook('post_pull',
151
self.capture_post_pull_hook, None)
153
# with nothing there we should still get a notification, and
154
# have both branches locked at the notification time.
156
('post_pull', source, None, target.base, 0, NULL_REVISION,
157
0, NULL_REVISION, True, None, True)
161
def test_post_pull_bound_branch(self):
162
# pulling to a bound branch should pass in the master branch to the
163
# hook, allowing the correct number of emails to be sent, while still
164
# allowing hooks that want to modify the target to do so to both
166
target = self.make_branch('target')
167
local = self.make_branch('local')
170
except errors.UpgradeRequired:
171
# We can't bind this format to itself- typically it is the local
172
# branch that doesn't support binding. As of May 2007
173
# remotebranches can't be bound. Let's instead make a new local
174
# branch of the default type, which does allow binding.
175
# See https://bugs.launchpad.net/bzr/+bug/112020
176
local = BzrDir.create_branch_convenience('local2')
178
source = self.make_branch('source')
179
Branch.hooks.install_named_hook('post_pull',
180
self.capture_post_pull_hook, None)
182
# with nothing there we should still get a notification, and
183
# have both branches locked at the notification time.
185
('post_pull', source, local.base, target.base, 0, NULL_REVISION,
186
0, NULL_REVISION, True, True, True)
190
def test_post_pull_nonempty_history(self):
191
target = self.make_branch_and_memory_tree('target')
194
rev1 = target.commit('rev 1')
196
sourcedir = target.bzrdir.clone(self.get_url('source'))
197
source = MemoryTree.create_on_branch(sourcedir.open_branch())
198
rev2 = source.commit('rev 2')
199
Branch.hooks.install_named_hook('post_pull',
200
self.capture_post_pull_hook, None)
201
target.branch.pull(source.branch)
202
# with nothing there we should still get a notification, and
203
# have both branches locked at the notification time.
205
('post_pull', source.branch, None, target.branch.base, 1, rev1,
206
2, rev2, True, None, True)