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