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