~bzr-pqm/bzr/bzr.dev

2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
1
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
2
#
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.
7
#
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.
12
#
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.push behaviour."""
18
19
import os
20
2018.5.130 by Robert Collins
Make all branch_implementations tests pass.
21
from bzrlib import bzrdir, errors
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
22
from bzrlib.branch import Branch
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
23
from bzrlib.memorytree import MemoryTree
2018.5.97 by Andrew Bennetts
Fix more tests.
24
from bzrlib.remote import RemoteBranch
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
25
from bzrlib.revision import NULL_REVISION
26
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
2018.5.130 by Robert Collins
Make all branch_implementations tests pass.
27
from bzrlib.transport.local import LocalURLServer
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
28
29
30
class TestPush(TestCaseWithBranch):
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
31
32
    def test_push_convergence_simple(self):
33
        # when revisions are pushed, the left-most accessible parents must 
34
        # become the revision-history.
35
        mine = self.make_branch_and_tree('mine')
36
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
37
        other = mine.bzrdir.sprout('other').open_workingtree()
38
        other.commit('my change', rev_id='M1', allow_pointless=True)
39
        mine.merge_from_branch(other.branch)
40
        mine.commit('merge my change', rev_id='P2')
2297.1.4 by Martin Pool
Push now returns a PushResult rather than just an integer.
41
        result = mine.branch.push(other.branch)
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
42
        self.assertEqual(['P1', 'P2'], other.branch.revision_history())
2297.1.4 by Martin Pool
Push now returns a PushResult rather than just an integer.
43
        # result object contains some structured data
44
        self.assertEqual(result.old_revid, 'M1')
45
        self.assertEqual(result.new_revid, 'P2')
46
        # and it can be treated as an integer for compatibility
47
        self.assertEqual(int(result), 0)
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
48
49
    def test_push_merged_indirect(self):
50
        # it should be possible to do a push from one branch into another
51
        # when the tip of the target was merged into the source branch
52
        # via a third branch - so its buried in the ancestry and is not
53
        # directly accessible.
54
        mine = self.make_branch_and_tree('mine')
55
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
56
        target = mine.bzrdir.sprout('target').open_workingtree()
57
        target.commit('my change', rev_id='M1', allow_pointless=True)
58
        other = mine.bzrdir.sprout('other').open_workingtree()
59
        other.merge_from_branch(target.branch)
60
        other.commit('merge my change', rev_id='O2')
61
        mine.merge_from_branch(other.branch)
62
        mine.commit('merge other', rev_id='P2')
63
        mine.branch.push(target.branch)
64
        self.assertEqual(['P1', 'P2'], target.branch.revision_history())
65
66
    def test_push_to_checkout_updates_master(self):
67
        """Pushing into a checkout updates the checkout and the master branch"""
68
        master_tree = self.make_branch_and_tree('master')
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
69
        checkout = self.make_branch_and_tree('checkout')
70
        try:
71
            checkout.branch.bind(master_tree.branch)
72
        except errors.UpgradeRequired:
73
            # cant bind this format, the test is irrelevant.
74
            return
75
        rev1 = checkout.commit('master')
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
76
77
        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
78
        rev2 = other.commit('other commit')
79
        # now push, which should update both checkout and master.
80
        other.branch.push(checkout.branch)
81
        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
82
        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
83
84
    def test_push_raises_specific_error_on_master_connection_error(self):
85
        master_tree = self.make_branch_and_tree('master')
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
86
        checkout = self.make_branch_and_tree('checkout')
87
        try:
88
            checkout.branch.bind(master_tree.branch)
89
        except errors.UpgradeRequired:
90
            # cant bind this format, the test is irrelevant.
91
            return
2245.2.1 by Robert Collins
Split branch pushing out of branch pulling.
92
        other = master_tree.branch.bzrdir.sprout('other').open_workingtree()
93
        # move the branch out of the way on disk to cause a connection
94
        # error.
95
        os.rename('master', 'master_gone')
96
        # try to push, which should raise a BoundBranchConnectionFailure.
97
        self.assertRaises(errors.BoundBranchConnectionFailure,
98
                other.branch.push, checkout.branch)
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
99
2279.1.1 by John Arbash Meinel
Branch.push() only needs a read lock.
100
    def test_push_uses_read_lock(self):
101
        """Push should only need a read lock on the source side."""
102
        source = self.make_branch_and_tree('source')
103
        target = self.make_branch('target')
104
2381.1.3 by Robert Collins
Review feedback.
105
        self.build_tree(['source/a'])
2279.1.1 by John Arbash Meinel
Branch.push() only needs a read lock.
106
        source.add(['a'])
107
        source.commit('a')
108
109
        source.branch.lock_read()
110
        try:
111
            target.lock_write()
112
            try:
113
                source.branch.push(target, stop_revision=source.last_revision())
114
            finally:
115
                target.unlock()
116
        finally:
117
            source.branch.unlock()
118
2279.1.3 by John Arbash Meinel
Switch the test to being a branch_implementation test.
119
    def test_push_within_repository(self):
120
        """Push from one branch to another inside the same repository."""
121
        try:
122
            repo = self.make_repository('repo', shared=True)
123
        except (errors.IncompatibleFormat, errors.UninitializableFormat):
124
            # This Branch format cannot create shared repositories
125
            return
126
        # This is a little bit trickier because make_branch_and_tree will not
127
        # re-use a shared repository.
128
        a_bzrdir = self.make_bzrdir('repo/tree')
129
        try:
130
            a_branch = self.branch_format.initialize(a_bzrdir)
131
        except (errors.UninitializableFormat):
132
            # Cannot create these branches
133
            return
2018.5.97 by Andrew Bennetts
Fix more tests.
134
        try:
135
            tree = a_branch.bzrdir.create_workingtree()
136
        except errors.NotLocalUrl:
2018.5.130 by Robert Collins
Make all branch_implementations tests pass.
137
            if self.vfs_transport_factory is LocalURLServer:
138
                # the branch is colocated on disk, we cannot create a checkout.
139
                # hopefully callers will expect this.
140
                local_controldir= bzrdir.BzrDir.open(self.get_vfs_only_url('repo/tree'))
141
                tree = local_controldir.create_workingtree()
142
            else:
143
                tree = a_branch.create_checkout('repo/tree', lightweight=True)
2381.1.3 by Robert Collins
Review feedback.
144
        self.build_tree(['repo/tree/a'])
2279.1.3 by John Arbash Meinel
Switch the test to being a branch_implementation test.
145
        tree.add(['a'])
146
        tree.commit('a')
147
148
        to_bzrdir = self.make_bzrdir('repo/branch')
149
        to_branch = self.branch_format.initialize(to_bzrdir)
150
        tree.branch.push(to_branch)
151
152
        self.assertEqual(tree.branch.last_revision(),
153
                         to_branch.last_revision())
154
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
155
156
class TestPushHook(TestCaseWithBranch):
157
158
    def setUp(self):
159
        self.hook_calls = []
160
        TestCaseWithBranch.setUp(self)
161
2297.1.4 by Martin Pool
Push now returns a PushResult rather than just an integer.
162
    def capture_post_push_hook(self, result):
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
163
        """Capture post push hook calls to self.hook_calls.
164
        
165
        The call is logged, as is some state of the two branches.
166
        """
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
167
        if result.local_branch:
168
            local_locked = result.local_branch.is_locked()
169
            local_base = result.local_branch.base
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
170
        else:
171
            local_locked = None
172
            local_base = None
173
        self.hook_calls.append(
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
174
            ('post_push', result.source_branch, local_base,
175
             result.master_branch.base,
2297.1.4 by Martin Pool
Push now returns a PushResult rather than just an integer.
176
             result.old_revno, result.old_revid,
2297.1.6 by Martin Pool
Add docs for Results, give some members cleaner names
177
             result.new_revno, result.new_revid,
178
             result.source_branch.is_locked(), local_locked,
179
             result.master_branch.is_locked()))
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
180
181
    def test_post_push_empty_history(self):
182
        target = self.make_branch('target')
183
        source = self.make_branch('source')
184
        Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
185
        source.push(target)
186
        # with nothing there we should still get a notification, and
187
        # have both branches locked at the notification time.
2018.5.97 by Andrew Bennetts
Fix more tests.
188
        if isinstance(source, RemoteBranch):
189
            # XXX: at the moment, push on remote branches is just delegated to
190
            # the file-level branch object, so we adjust the expected result
191
            # accordingly.  In the future, when RemoteBranch implements push
192
            # directly, this should be unnecessary.
193
            source = source._real_branch
2246.1.3 by Robert Collins
New branch hooks: post_push, post_pull, post_commit, post_uncommit. These
194
        self.assertEqual([
195
            ('post_push', source, None, target.base, 0, NULL_REVISION,
196
             0, NULL_REVISION, True, None, True)
197
            ],
198
            self.hook_calls)
199
200
    def test_post_push_bound_branch(self):
201
        # pushing to a bound branch should pass in the master branch to the
202
        # hook, allowing the correct number of emails to be sent, while still
203
        # allowing hooks that want to modify the target to do so to both 
204
        # instances.
205
        target = self.make_branch('target')
206
        local = self.make_branch('local')
207
        try:
208
            local.bind(target)
209
        except errors.UpgradeRequired:
210
            # cant bind this format, the test is irrelevant.
211
            return
212
        source = self.make_branch('source')
213
        Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
214
        source.push(local)
215
        # with nothing there we should still get a notification, and
216
        # have both branches locked at the notification time.
217
        self.assertEqual([
218
            ('post_push', source, local.base, target.base, 0, NULL_REVISION,
219
             0, NULL_REVISION, True, True, True)
220
            ],
221
            self.hook_calls)
222
223
    def test_post_push_nonempty_history(self):
224
        target = self.make_branch_and_memory_tree('target')
225
        target.lock_write()
226
        target.add('')
227
        rev1 = target.commit('rev 1')
228
        target.unlock()
229
        sourcedir = target.bzrdir.clone(self.get_url('source'))
230
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
231
        rev2 = source.commit('rev 2')
232
        Branch.hooks.install_hook('post_push', self.capture_post_push_hook)
233
        source.branch.push(target.branch)
234
        # with nothing there we should still get a notification, and
235
        # have both branches locked at the notification time.
236
        self.assertEqual([
237
            ('post_push', source.branch, None, target.branch.base, 1, rev1,
238
             2, rev2, True, None, True)
239
            ],
240
            self.hook_calls)