1
# Copyright (C) 2004, 2005, 2007, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for InterBranch.pull behaviour."""
21
from bzrlib.branch import Branch
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.per_interbranch import TestCaseWithInterBranch
29
# The tests here are based on the tests in
30
# bzrlib.tests.branch_implementations.test_pull
33
class TestPull(TestCaseWithInterBranch):
35
def test_pull_convergence_simple(self):
36
# when revisions are pulled, the left-most accessible parents must
37
# become the revision-history.
38
parent = self.make_from_branch_and_tree('parent')
39
parent.commit('1st post', rev_id='P1', allow_pointless=True)
40
mine = self.sprout_to(parent.bzrdir, 'mine').open_workingtree()
41
mine.commit('my change', rev_id='M1', allow_pointless=True)
42
parent.merge_from_branch(mine.branch)
43
parent.commit('merge my change', rev_id='P2')
44
mine.pull(parent.branch)
45
self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
47
def test_pull_merged_indirect(self):
48
# it should be possible to do a pull from one branch into another
49
# when the tip of the target was merged into the source branch
50
# via a third branch - so its buried in the ancestry and is not
51
# directly accessible.
52
parent = self.make_from_branch_and_tree('parent')
53
parent.commit('1st post', rev_id='P1', allow_pointless=True)
54
mine = self.sprout_to(parent.bzrdir, 'mine').open_workingtree()
55
mine.commit('my change', rev_id='M1', allow_pointless=True)
56
other = self.sprout_to(parent.bzrdir, 'other').open_workingtree()
57
other.merge_from_branch(mine.branch)
58
other.commit('merge my change', rev_id='O2')
59
parent.merge_from_branch(other.branch)
60
parent.commit('merge other', rev_id='P2')
61
mine.pull(parent.branch)
62
self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
64
def test_pull_updates_checkout_and_master(self):
65
"""Pulling into a checkout updates the checkout and the master branch"""
66
master_tree = self.make_from_branch_and_tree('master')
67
rev1 = master_tree.commit('master')
68
checkout = master_tree.branch.create_checkout('checkout')
69
other = self.sprout_to(master_tree.branch.bzrdir, 'other').open_workingtree()
70
rev2 = other.commit('other commit')
71
# now pull, which should update both checkout and master.
72
checkout.branch.pull(other.branch)
73
self.assertEqual([rev1, rev2], checkout.branch.revision_history())
74
self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
76
def test_pull_raises_specific_error_on_master_connection_error(self):
77
master_tree = self.make_from_branch_and_tree('master')
78
checkout = master_tree.branch.create_checkout('checkout')
79
other = self.sprout_to(master_tree.branch.bzrdir, 'other').open_workingtree()
80
# move the branch out of the way on disk to cause a connection
82
os.rename('master', 'master_gone')
83
# try to pull, which should raise a BoundBranchConnectionFailure.
84
self.assertRaises(errors.BoundBranchConnectionFailure,
85
checkout.branch.pull, other.branch)
87
def test_pull_returns_result(self):
88
parent = self.make_from_branch_and_tree('parent')
89
parent.commit('1st post', rev_id='P1')
90
mine = self.sprout_to(parent.bzrdir, 'mine').open_workingtree()
91
mine.commit('my change', rev_id='M1')
92
result = parent.branch.pull(mine.branch)
93
self.assertIsNot(None, result)
94
self.assertIs(mine.branch, result.source_branch)
95
self.assertIs(parent.branch, result.target_branch)
96
self.assertIs(parent.branch, result.master_branch)
97
self.assertIs(None, result.local_branch)
98
self.assertEqual(1, result.old_revno)
99
self.assertEqual('P1', result.old_revid)
100
self.assertEqual(2, result.new_revno)
101
self.assertEqual('M1', result.new_revid)
102
self.assertEqual(None, result.tag_conflicts)
104
def test_pull_overwrite(self):
105
tree_a = self.make_from_branch_and_tree('tree_a')
106
tree_a.commit('message 1')
107
tree_b = self.sprout_to(tree_a.bzrdir, 'tree_b').open_workingtree()
108
tree_a.commit('message 2', rev_id='rev2a')
109
tree_b.commit('message 2', rev_id='rev2b')
110
self.assertRaises(errors.DivergedBranches, tree_a.pull, tree_b.branch)
111
self.assertRaises(errors.DivergedBranches,
112
tree_a.branch.pull, tree_b.branch,
113
overwrite=False, stop_revision='rev2b')
114
# It should not have updated the branch tip, but it should have fetched
116
self.assertEqual('rev2a', tree_a.branch.last_revision())
117
self.assertTrue(tree_a.branch.repository.has_revision('rev2b'))
118
tree_a.branch.pull(tree_b.branch, overwrite=True,
119
stop_revision='rev2b')
120
self.assertEqual('rev2b', tree_a.branch.last_revision())
121
self.assertEqual(tree_b.branch.revision_history(),
122
tree_a.branch.revision_history())
125
class TestPullHook(TestCaseWithInterBranch):
129
TestCaseWithInterBranch.setUp(self)
131
def capture_post_pull_hook(self, result):
132
"""Capture post pull hook calls to self.hook_calls.
134
The call is logged, as is some state of the two branches.
136
if result.local_branch:
137
local_locked = result.local_branch.is_locked()
138
local_base = result.local_branch.base
142
self.hook_calls.append(
143
('post_pull', result.source_branch, local_base,
144
result.master_branch.base, result.old_revno,
146
result.new_revno, result.new_revid,
147
result.source_branch.is_locked(), local_locked,
148
result.master_branch.is_locked()))
150
def test_post_pull_empty_history(self):
151
target = self.make_to_branch('target')
152
source = self.make_from_branch('source')
153
Branch.hooks.install_named_hook('post_pull',
154
self.capture_post_pull_hook, None)
156
# with nothing there we should still get a notification, and
157
# have both branches locked at the notification time.
159
('post_pull', source, None, target.base, 0, NULL_REVISION,
160
0, NULL_REVISION, True, None, True)
164
def test_post_pull_bound_branch(self):
165
# pulling to a bound branch should pass in the master branch to the
166
# hook, allowing the correct number of emails to be sent, while still
167
# allowing hooks that want to modify the target to do so to both
169
target = self.make_to_branch('target')
170
local = self.make_from_branch('local')
173
except errors.UpgradeRequired:
174
# We can't bind this format to itself- typically it is the local
175
# branch that doesn't support binding. As of May 2007
176
# remotebranches can't be bound. Let's instead make a new local
177
# branch of the default type, which does allow binding.
178
# See https://bugs.launchpad.net/bzr/+bug/112020
179
local = BzrDir.create_branch_convenience('local2')
181
source = self.make_from_branch('source')
182
Branch.hooks.install_named_hook('post_pull',
183
self.capture_post_pull_hook, None)
185
# with nothing there we should still get a notification, and
186
# have both branches locked at the notification time.
188
('post_pull', source, local.base, target.base, 0, NULL_REVISION,
189
0, NULL_REVISION, True, True, True)
193
def test_post_pull_nonempty_history(self):
194
target = self.make_to_branch_and_memory_tree('target')
197
rev1 = target.commit('rev 1')
199
sourcedir = target.bzrdir.clone(self.get_url('source'))
200
source = MemoryTree.create_on_branch(sourcedir.open_branch())
201
rev2 = source.commit('rev 2')
202
Branch.hooks.install_named_hook('post_pull',
203
self.capture_post_pull_hook, None)
204
target.branch.pull(source.branch)
205
# with nothing there we should still get a notification, and
206
# have both branches locked at the notification time.
208
('post_pull', source.branch, None, target.branch.base, 1, rev1,
209
2, rev2, True, None, True)