~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2009, 2010 Canonical Ltd
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Tests for branch.push behaviour."""
18
19
from cStringIO import StringIO
20
5317.1.1 by Robert Collins
Make the interbranch test_no_get_parent_map_after_insert_stream test work for looms - a little ugly, will want a generic hook-out to foreign formats in some form, at some point.
21
from testtools.matchers import (
22
    Equals,
23
    MatchesAny,
24
    )
25
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
26
from bzrlib import (
27
    branch,
4332.3.35 by Robert Collins
Fix failing tests.
28
    check,
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
29
    controldir,
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
30
    errors,
31
    push,
5348.1.2 by Martin Pool
Deprecate casting PushResult and PullResult to int to get the relative revno change
32
    symbol_versioning,
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
33
    tests,
5863.4.2 by Jelmer Vernooij
Fix test.
34
    vf_repository,
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
35
    )
36
from bzrlib.branch import Branch
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
37
from bzrlib.controldir import ControlDir
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
38
from bzrlib.memorytree import MemoryTree
39
from bzrlib.revision import NULL_REVISION
40
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
41
from bzrlib.tests.per_interbranch import (
42
    TestCaseWithInterBranch,
43
    )
5017.3.41 by Vincent Ladeuil
-s bt.per_interbranch passing
44
from bzrlib.tests import test_server
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
45
46
4211.1.6 by Jelmer Vernooij
Review from Ian.
47
# These tests are based on similar tests in 
4523.1.1 by Martin Pool
Rename tests.branch_implementations to per_branch
48
# bzrlib.tests.per_branch.test_push.
4211.1.6 by Jelmer Vernooij
Review from Ian.
49
50
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
51
class TestPush(TestCaseWithInterBranch):
52
53
    def test_push_convergence_simple(self):
54
        # when revisions are pushed, the left-most accessible parents must
55
        # become the revision-history.
56
        mine = self.make_from_branch_and_tree('mine')
57
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
58
        other = self.sprout_to(mine.bzrdir, 'other').open_workingtree()
59
        other.commit('my change', rev_id='M1', allow_pointless=True)
60
        mine.merge_from_branch(other.branch)
61
        mine.commit('merge my change', rev_id='P2')
62
        result = mine.branch.push(other.branch)
6165.4.6 by Jelmer Vernooij
Avoid more uses of revision_history.
63
        self.assertEqual('P2', other.branch.last_revision())
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
64
        # result object contains some structured data
65
        self.assertEqual(result.old_revid, 'M1')
66
        self.assertEqual(result.new_revid, 'P2')
67
68
    def test_push_merged_indirect(self):
69
        # it should be possible to do a push from one branch into another
70
        # when the tip of the target was merged into the source branch
71
        # via a third branch - so its buried in the ancestry and is not
72
        # directly accessible.
73
        mine = self.make_from_branch_and_tree('mine')
74
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
75
        target = self.sprout_to(mine.bzrdir, 'target').open_workingtree()
76
        target.commit('my change', rev_id='M1', allow_pointless=True)
77
        other = self.sprout_to(mine.bzrdir, 'other').open_workingtree()
78
        other.merge_from_branch(target.branch)
79
        other.commit('merge my change', rev_id='O2')
80
        mine.merge_from_branch(other.branch)
81
        mine.commit('merge other', rev_id='P2')
82
        mine.branch.push(target.branch)
6165.4.6 by Jelmer Vernooij
Avoid more uses of revision_history.
83
        self.assertEqual('P2', target.branch.last_revision())
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
84
85
    def test_push_to_checkout_updates_master(self):
86
        """Pushing into a checkout updates the checkout and the master branch"""
87
        master_tree = self.make_to_branch_and_tree('master')
88
        checkout = self.make_to_branch_and_tree('checkout')
89
        try:
90
            checkout.branch.bind(master_tree.branch)
91
        except errors.UpgradeRequired:
92
            # cant bind this format, the test is irrelevant.
93
            return
94
        rev1 = checkout.commit('master')
95
4211.1.5 by Jelmer Vernooij
Fix copyright year, number of columns used.
96
        other_bzrdir = self.sprout_from(master_tree.branch.bzrdir, 'other')
97
        other = other_bzrdir.open_workingtree()
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
98
        rev2 = other.commit('other commit')
99
        # now push, which should update both checkout and master.
100
        other.branch.push(checkout.branch)
6165.4.6 by Jelmer Vernooij
Avoid more uses of revision_history.
101
        self.assertEqual(rev2, checkout.branch.last_revision())
102
        self.assertEqual(rev2, master_tree.branch.last_revision())
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
103
104
    def test_push_raises_specific_error_on_master_connection_error(self):
105
        master_tree = self.make_to_branch_and_tree('master')
106
        checkout = self.make_to_branch_and_tree('checkout')
107
        try:
108
            checkout.branch.bind(master_tree.branch)
109
        except errors.UpgradeRequired:
110
            # cant bind this format, the test is irrelevant.
111
            return
4211.1.5 by Jelmer Vernooij
Fix copyright year, number of columns used.
112
        other_bzrdir = self.sprout_from(master_tree.branch.bzrdir, 'other')
113
        other = other_bzrdir.open_workingtree()
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
114
        # move the branch out of the way on disk to cause a connection
115
        # error.
6162.5.3 by Jelmer Vernooij
Use destroy_branch, as some branches don't actually exist on disk.
116
        master_tree.bzrdir.destroy_branch()
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
117
        # try to push, which should raise a BoundBranchConnectionFailure.
118
        self.assertRaises(errors.BoundBranchConnectionFailure,
119
                other.branch.push, checkout.branch)
120
121
    def test_push_uses_read_lock(self):
122
        """Push should only need a read lock on the source side."""
123
        source = self.make_from_branch_and_tree('source')
124
        target = self.make_to_branch('target')
125
126
        self.build_tree(['source/a'])
127
        source.add(['a'])
128
        source.commit('a')
129
130
        source.branch.lock_read()
131
        try:
132
            target.lock_write()
133
            try:
134
                source.branch.push(target, stop_revision=source.last_revision())
135
            finally:
136
                target.unlock()
137
        finally:
138
            source.branch.unlock()
139
140
    def test_push_within_repository(self):
141
        """Push from one branch to another inside the same repository."""
142
        try:
143
            repo = self.make_repository('repo', shared=True)
144
        except (errors.IncompatibleFormat, errors.UninitializableFormat):
145
            # This Branch format cannot create shared repositories
146
            return
5297.2.2 by Robert Collins
Fixup tests in per_interbranch not being strict about making the from format the configured one.
147
        # This is a little bit trickier because make_from_branch_and_tree will not
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
148
        # re-use a shared repository.
149
        try:
150
            a_branch = self.make_from_branch('repo/tree')
151
        except (errors.UninitializableFormat):
152
            # Cannot create these branches
153
            return
154
        try:
155
            tree = a_branch.bzrdir.create_workingtree()
6127.1.9 by Jelmer Vernooij
Add lightweight option to _get_checkout_format().
156
        except errors.UnsupportedOperation:
157
            self.assertFalse(a_branch.bzrdir._format.supports_workingtrees)
158
            tree = a_branch.create_checkout('repo/tree', lightweight=True)
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
159
        except errors.NotLocalUrl:
5017.3.41 by Vincent Ladeuil
-s bt.per_interbranch passing
160
            if self.vfs_transport_factory is test_server.LocalURLServer:
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
161
                # the branch is colocated on disk, we cannot create a checkout.
162
                # hopefully callers will expect this.
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
163
                local_controldir = controldir.ControlDir.open(self.get_vfs_only_url('repo/tree'))
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
164
                tree = local_controldir.create_workingtree()
165
            else:
166
                tree = a_branch.create_checkout('repo/tree', lightweight=True)
167
        self.build_tree(['repo/tree/a'])
168
        tree.add(['a'])
169
        tree.commit('a')
170
171
        to_branch = self.make_to_branch('repo/branch')
172
        tree.branch.push(to_branch)
173
174
        self.assertEqual(tree.branch.last_revision(),
175
                         to_branch.last_revision())
176
177
    def test_push_overwrite_of_non_tip_with_stop_revision(self):
178
        """Combining the stop_revision and overwrite options works.
179
180
        This was <https://bugs.launchpad.net/bzr/+bug/234229>.
181
        """
182
        source = self.make_from_branch_and_tree('source')
183
        target = self.make_to_branch('target')
184
185
        source.commit('1st commit')
186
        source.branch.push(target)
187
        source.commit('2nd commit', rev_id='rev-2')
188
        source.commit('3rd commit')
189
190
        source.branch.push(target, stop_revision='rev-2', overwrite=True)
191
        self.assertEqual('rev-2', target.last_revision())
192
193
    def test_push_with_default_stacking_does_not_create_broken_branch(self):
194
        """Pushing a new standalone branch works even when there's a default
195
        stacking policy at the destination.
196
197
        The new branch will preserve the repo format (even if it isn't the
198
        default for the branch), and will be stacked when the repo format
199
        allows (which means that the branch format isn't necessarly preserved).
200
        """
201
        if isinstance(self.branch_format_from, branch.BranchReferenceFormat):
202
            # This test could in principle apply to BranchReferenceFormat, but
203
            # make_branch_builder doesn't support it.
204
            raise tests.TestSkipped(
205
                "BranchBuilder can't make reference branches.")
206
        # Make a branch called "local" in a stackable repository
207
        # The branch has 3 revisions:
208
        #   - rev-1, adds a file
209
        #   - rev-2, no changes
210
        #   - rev-3, modifies the file.
211
        repo = self.make_repository('repo', shared=True, format='1.6')
212
        builder = self.make_from_branch_builder('repo/local')
213
        builder.start_series()
214
        builder.build_snapshot('rev-1', None, [
215
            ('add', ('', 'root-id', 'directory', '')),
216
            ('add', ('filename', 'f-id', 'file', 'content\n'))])
217
        builder.build_snapshot('rev-2', ['rev-1'], [])
218
        builder.build_snapshot('rev-3', ['rev-2'],
219
            [('modify', ('f-id', 'new-content\n'))])
220
        builder.finish_series()
221
        trunk = builder.get_branch()
222
        # Sprout rev-1 to "trunk", so that we can stack on it.
223
        trunk.bzrdir.sprout(self.get_url('trunk'), revision_id='rev-1')
224
        # Set a default stacking policy so that new branches will automatically
225
        # stack on trunk.
226
        self.make_bzrdir('.').get_config().set_default_stack_on('trunk')
227
        # Push rev-2 to a new branch "remote".  It will be stacked on "trunk".
228
        output = StringIO()
229
        push._show_push_branch(trunk, 'rev-2', self.get_url('remote'), output)
230
        # Push rev-3 onto "remote".  If "remote" not stacked and is missing the
231
        # fulltext record for f-id @ rev-1, then this will fail.
232
        remote_branch = Branch.open(self.get_url('remote'))
233
        trunk.push(remote_branch)
4332.3.35 by Robert Collins
Fix failing tests.
234
        check.check_dwim(remote_branch.base, False, True, True)
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
235
236
    def test_no_get_parent_map_after_insert_stream(self):
237
        # Effort test for bug 331823
238
        self.setup_smart_server_with_call_log()
239
        # Make a local branch with four revisions.  Four revisions because:
240
        # one to push, one there for _walk_to_common_revisions to find, one we
241
        # don't want to access, one for luck :)
242
        if isinstance(self.branch_format_from, branch.BranchReferenceFormat):
243
            # This test could in principle apply to BranchReferenceFormat, but
244
            # make_branch_builder doesn't support it.
245
            raise tests.TestSkipped(
246
                "BranchBuilder can't make reference branches.")
247
        try:
248
            builder = self.make_from_branch_builder('local')
249
        except (errors.TransportNotPossible, errors.UninitializableFormat):
250
            raise tests.TestNotApplicable('format not directly constructable')
251
        builder.start_series()
252
        builder.build_snapshot('first', None, [
253
            ('add', ('', 'root-id', 'directory', ''))])
254
        builder.build_snapshot('second', ['first'], [])
255
        builder.build_snapshot('third', ['second'], [])
256
        builder.build_snapshot('fourth', ['third'], [])
257
        builder.finish_series()
258
        local = branch.Branch.open(self.get_vfs_only_url('local'))
259
        # Initial push of three revisions
260
        remote_bzrdir = local.bzrdir.sprout(
261
            self.get_url('remote'), revision_id='third')
262
        remote = remote_bzrdir.open_branch()
263
        # Push fourth revision
264
        self.reset_smart_call_log()
265
        self.disableOptimisticGetParentMap()
266
        self.assertFalse(local.is_locked())
267
        local.push(remote)
268
        hpss_call_names = [item.call.method for item in self.hpss_calls]
4476.3.82 by Andrew Bennetts
Mention another bug fix in NEWS, and update verb name, comments, and NEWS additions for landing on 1.19 rather than 1.18.
269
        self.assertTrue('Repository.insert_stream_1.19' in hpss_call_names)
4476.3.18 by Andrew Bennetts
Update some tests that were expecting the pre-1.17 insert_stream verb.
270
        insert_stream_idx = hpss_call_names.index(
4476.3.82 by Andrew Bennetts
Mention another bug fix in NEWS, and update verb name, comments, and NEWS additions for landing on 1.19 rather than 1.18.
271
            'Repository.insert_stream_1.19')
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
272
        calls_after_insert_stream = hpss_call_names[insert_stream_idx:]
273
        # After inserting the stream the client has no reason to query the
274
        # remote graph any further.
5317.1.1 by Robert Collins
Make the interbranch test_no_get_parent_map_after_insert_stream test work for looms - a little ugly, will want a generic hook-out to foreign formats in some form, at some point.
275
        bzr_core_trace = Equals(
276
            ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
6282.6.33 by Jelmer Vernooij
Fix some tests.
277
             'Branch.set_last_revision_info', 'Branch.unlock'])
5317.1.1 by Robert Collins
Make the interbranch test_no_get_parent_map_after_insert_stream test work for looms - a little ugly, will want a generic hook-out to foreign formats in some form, at some point.
278
        bzr_loom_trace = Equals(
279
            ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
6282.6.33 by Jelmer Vernooij
Fix some tests.
280
             'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
5317.1.1 by Robert Collins
Make the interbranch test_no_get_parent_map_after_insert_stream test work for looms - a little ugly, will want a generic hook-out to foreign formats in some form, at some point.
281
        self.assertThat(calls_after_insert_stream,
282
            MatchesAny(bzr_core_trace, bzr_loom_trace))
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
283
284
    def disableOptimisticGetParentMap(self):
285
        # Tweak some class variables to stop remote get_parent_map calls asking
286
        # for or receiving more data than the caller asked for.
5863.4.2 by Jelmer Vernooij
Fix test.
287
        self.overrideAttr(vf_repository.InterVersionedFileRepository,
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
288
                          '_walk_to_common_revisions_batch_size', 1)
289
        self.overrideAttr(SmartServerRepositoryGetParentMap,
290
                            'no_extra_results', True)
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
291
292
293
class TestPushHook(TestCaseWithInterBranch):
294
295
    def setUp(self):
296
        self.hook_calls = []
6552.1.4 by Vincent Ladeuil
Remaining tests matching setup(self) that can be rewritten with super().
297
        super(TestPushHook, self).setUp()
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
298
299
    def capture_post_push_hook(self, result):
300
        """Capture post push hook calls to self.hook_calls.
301
302
        The call is logged, as is some state of the two branches.
303
        """
304
        if result.local_branch:
305
            local_locked = result.local_branch.is_locked()
306
            local_base = result.local_branch.base
307
        else:
308
            local_locked = None
309
            local_base = None
310
        self.hook_calls.append(
311
            ('post_push', result.source_branch, local_base,
312
             result.master_branch.base,
313
             result.old_revno, result.old_revid,
314
             result.new_revno, result.new_revid,
315
             result.source_branch.is_locked(), local_locked,
316
             result.master_branch.is_locked()))
317
318
    def test_post_push_empty_history(self):
319
        target = self.make_to_branch('target')
320
        source = self.make_from_branch('source')
321
        Branch.hooks.install_named_hook('post_push',
322
                                        self.capture_post_push_hook, None)
323
        source.push(target)
324
        # with nothing there we should still get a notification, and
325
        # have both branches locked at the notification time.
326
        self.assertEqual([
327
            ('post_push', source, None, target.base, 0, NULL_REVISION,
328
             0, NULL_REVISION, True, None, True)
329
            ],
330
            self.hook_calls)
331
332
    def test_post_push_bound_branch(self):
333
        # pushing to a bound branch should pass in the master branch to the
334
        # hook, allowing the correct number of emails to be sent, while still
335
        # allowing hooks that want to modify the target to do so to both
336
        # instances.
337
        target = self.make_to_branch('target')
338
        local = self.make_from_branch('local')
339
        try:
340
            local.bind(target)
341
        except errors.UpgradeRequired:
342
            # We can't bind this format to itself- typically it is the local
343
            # branch that doesn't support binding.  As of May 2007
344
            # remotebranches can't be bound.  Let's instead make a new local
345
            # branch of the default type, which does allow binding.
346
            # See https://bugs.launchpad.net/bzr/+bug/112020
6472.2.2 by Jelmer Vernooij
Use controldir rather than bzrdir in a couple more places.
347
            local = ControlDir.create_branch_convenience('local2')
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
348
            local.bind(target)
349
        source = self.make_from_branch('source')
350
        Branch.hooks.install_named_hook('post_push',
351
                                        self.capture_post_push_hook, None)
352
        source.push(local)
353
        # with nothing there we should still get a notification, and
354
        # have both branches locked at the notification time.
355
        self.assertEqual([
356
            ('post_push', source, local.base, target.base, 0, NULL_REVISION,
357
             0, NULL_REVISION, True, True, True)
358
            ],
359
            self.hook_calls)
360
361
    def test_post_push_nonempty_history(self):
362
        target = self.make_to_branch_and_tree('target')
363
        target.lock_write()
364
        target.add('')
365
        rev1 = target.commit('rev 1')
366
        target.unlock()
6217.3.3 by Jelmer Vernooij
Fix test when workingtree and branch are not colocated.
367
        sourcedir = target.branch.bzrdir.clone(self.get_url('source'))
4211.1.4 by Jelmer Vernooij
add InterBranch.push() tests.
368
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
369
        rev2 = source.commit('rev 2')
370
        Branch.hooks.install_named_hook('post_push',
371
                                        self.capture_post_push_hook, None)
372
        source.branch.push(target.branch)
373
        # with nothing there we should still get a notification, and
374
        # have both branches locked at the notification time.
375
        self.assertEqual([
376
            ('post_push', source.branch, None, target.branch.base, 1, rev1,
377
             2, rev2, True, None, True)
378
            ],
379
            self.hook_calls)