~bzr-pqm/bzr/bzr.dev

2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
1
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
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
16
17
"""Tests for branch.pull behaviour."""
18
19
import os
20
2477.1.2 by Martin Pool
Rename push/pull back to 'run_hooks' (jameinel)
21
from bzrlib.branch import Branch, BzrBranchFormat5
22
from bzrlib.bzrdir import BzrDir
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
23
from bzrlib import errors
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
24
from bzrlib.memorytree import MemoryTree
25
from bzrlib.revision import NULL_REVISION
26
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
27
28
29
class TestPull(TestCaseWithBranch):
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
30
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)
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
38
        parent.merge_from_branch(mine.branch)
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
39
        parent.commit('merge my change', rev_id='P2')
40
        mine.pull(parent.branch)
41
        self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
42
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()
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
53
        other.merge_from_branch(mine.branch)
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
54
        other.commit('merge my change', rev_id='O2')
1979.2.1 by Robert Collins
(robertc) adds a convenience method "merge_from_branch" to WorkingTree.
55
        parent.merge_from_branch(other.branch)
1649.1.1 by Robert Collins
* 'pull' and 'push' now normalise the revision history, so that any two
56
        parent.commit('merge other', rev_id='P2')
57
        mine.pull(parent.branch)
58
        self.assertEqual(['P1', 'P2'], mine.branch.revision_history())
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
59
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')
65
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())
72
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
78
        # error.
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)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
83
3482.1.1 by John Arbash Meinel
Fix bug #238149, RemoteBranch.pull needs to return the _real_branch's pull result.
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)
100
1551.10.23 by Aaron Bentley
Update test case per review
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)
3052.5.2 by John Arbash Meinel
Use a Graph.heads() check to determine if the ancestries are compatible.
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
112
        # the revision
113
        self.assertEqual('rev2a', tree_a.branch.last_revision())
114
        self.assertTrue(tree_a.branch.repository.has_revision('rev2b'))
2975.1.1 by Robert Collins
Minor fixes for foreign format friendliness.
115
        tree_a.branch.pull(tree_b.branch, overwrite=True,
1551.10.23 by Aaron Bentley
Update test case per review
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())
120
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
121
122
class TestPullHook(TestCaseWithBranch):
123
124
    def setUp(self):
125
        self.hook_calls = []
126
        TestCaseWithBranch.setUp(self)
127
2297.1.1 by Martin Pool
Pull now returns a PullResult rather than just an integer.
128
    def capture_post_pull_hook(self, result):
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
129
        """Capture post pull hook calls to self.hook_calls.
130
        
131
        The call is logged, as is some state of the two branches.
132
        """
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
133
        if result.local_branch:
134
            local_locked = result.local_branch.is_locked()
135
            local_base = result.local_branch.base
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
136
        else:
137
            local_locked = None
138
            local_base = None
139
        self.hook_calls.append(
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
140
            ('post_pull', result.source_branch, local_base,
141
             result.master_branch.base, result.old_revno,
2297.1.1 by Martin Pool
Pull now returns a PullResult rather than just an integer.
142
             result.old_revid,
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
143
             result.new_revno, result.new_revid,
144
             result.source_branch.is_locked(), local_locked,
145
             result.master_branch.is_locked()))
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
146
147
    def test_post_pull_empty_history(self):
148
        target = self.make_branch('target')
149
        source = self.make_branch('source')
3256.2.17 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.branch_implementation.test_pull.
150
        Branch.hooks.install_named_hook('post_pull',
151
            self.capture_post_pull_hook, None)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
152
        target.pull(source)
153
        # with nothing there we should still get a notification, and
154
        # have both branches locked at the notification time.
155
        self.assertEqual([
156
            ('post_pull', source, None, target.base, 0, NULL_REVISION,
157
             0, NULL_REVISION, True, None, True)
158
            ],
159
            self.hook_calls)
160
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 
165
        # instances.
166
        target = self.make_branch('target')
167
        local = self.make_branch('local')
168
        try:
169
            local.bind(target)
170
        except errors.UpgradeRequired:
2477.1.2 by Martin Pool
Rename push/pull back to 'run_hooks' (jameinel)
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')
177
            local.bind(target)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
178
        source = self.make_branch('source')
3256.2.17 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.branch_implementation.test_pull.
179
        Branch.hooks.install_named_hook('post_pull',
180
            self.capture_post_pull_hook, None)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
181
        local.pull(source)
182
        # with nothing there we should still get a notification, and
183
        # have both branches locked at the notification time.
184
        self.assertEqual([
185
            ('post_pull', source, local.base, target.base, 0, NULL_REVISION,
186
             0, NULL_REVISION, True, True, True)
187
            ],
188
            self.hook_calls)
189
190
    def test_post_pull_nonempty_history(self):
191
        target = self.make_branch_and_memory_tree('target')
192
        target.lock_write()
193
        target.add('')
194
        rev1 = target.commit('rev 1')
195
        target.unlock()
196
        sourcedir = target.bzrdir.clone(self.get_url('source'))
197
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
198
        rev2 = source.commit('rev 2')
3256.2.17 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.branch_implementation.test_pull.
199
        Branch.hooks.install_named_hook('post_pull',
200
            self.capture_post_pull_hook, None)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
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.
204
        self.assertEqual([
205
            ('post_pull', source.branch, None, target.branch.base, 1, rev1,
206
             2, rev2, True, None, True)
207
            ],
208
            self.hook_calls)