~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_branch/test_push.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2007 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for branch.push behaviour."""
18
18
 
 
19
from cStringIO import StringIO
19
20
import os
20
 
 
21
 
from bzrlib import bzrdir, errors
22
 
from bzrlib.branch import Branch
23
 
from bzrlib.bzrdir import BzrDir
24
 
from bzrlib.memorytree import MemoryTree
25
 
from bzrlib.remote import RemoteBranch
26
 
from bzrlib.revision import NULL_REVISION
27
 
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
28
 
from bzrlib.transport.local import LocalURLServer
29
 
 
30
 
 
31
 
class TestPush(TestCaseWithBranch):
 
21
 
 
22
from bzrlib import (
 
23
    branch,
 
24
    builtins,
 
25
    controldir,
 
26
    check,
 
27
    errors,
 
28
    memorytree,
 
29
    push,
 
30
    revision,
 
31
    symbol_versioning,
 
32
    tests,
 
33
    transport,
 
34
    )
 
35
from bzrlib.smart import (
 
36
    client,
 
37
    )
 
38
from bzrlib.tests import (
 
39
    per_branch,
 
40
    test_server,
 
41
    )
 
42
 
 
43
 
 
44
class TestPush(per_branch.TestCaseWithBranch):
32
45
 
33
46
    def test_push_convergence_simple(self):
34
 
        # when revisions are pushed, the left-most accessible parents must 
 
47
        # when revisions are pushed, the left-most accessible parents must
35
48
        # become the revision-history.
36
49
        mine = self.make_branch_and_tree('mine')
37
50
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
40
53
        mine.merge_from_branch(other.branch)
41
54
        mine.commit('merge my change', rev_id='P2')
42
55
        result = mine.branch.push(other.branch)
43
 
        self.assertEqual(['P1', 'P2'], other.branch.revision_history())
 
56
        self.assertEqual('P2', other.branch.last_revision())
44
57
        # result object contains some structured data
45
58
        self.assertEqual(result.old_revid, 'M1')
46
59
        self.assertEqual(result.new_revid, 'P2')
47
 
        # and it can be treated as an integer for compatibility
48
 
        self.assertEqual(int(result), 0)
49
60
 
50
61
    def test_push_merged_indirect(self):
51
62
        # it should be possible to do a push from one branch into another
62
73
        mine.merge_from_branch(other.branch)
63
74
        mine.commit('merge other', rev_id='P2')
64
75
        mine.branch.push(target.branch)
65
 
        self.assertEqual(['P1', 'P2'], target.branch.revision_history())
 
76
        self.assertEqual('P2', target.branch.last_revision())
66
77
 
67
78
    def test_push_to_checkout_updates_master(self):
68
79
        """Pushing into a checkout updates the checkout and the master branch"""
79
90
        rev2 = other.commit('other commit')
80
91
        # now push, which should update both checkout and master.
81
92
        other.branch.push(checkout.branch)
82
 
        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
83
 
        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
 
93
        self.assertEqual(rev2, checkout.branch.last_revision())
 
94
        self.assertEqual(rev2, master_tree.branch.last_revision())
84
95
 
85
96
    def test_push_raises_specific_error_on_master_connection_error(self):
86
97
        master_tree = self.make_branch_and_tree('master')
98
109
        self.assertRaises(errors.BoundBranchConnectionFailure,
99
110
                other.branch.push, checkout.branch)
100
111
 
 
112
    def test_push_new_tag_to_bound_branch(self):
 
113
        master = self.make_branch('master')
 
114
        bound = self.make_branch('bound')
 
115
        try:
 
116
            bound.bind(master)
 
117
        except errors.UpgradeRequired:
 
118
            raise tests.TestNotApplicable(
 
119
                'Format does not support bound branches')
 
120
        other = bound.bzrdir.sprout('other').open_branch()
 
121
        try:
 
122
            other.tags.set_tag('new-tag', 'some-rev')
 
123
        except errors.TagsNotSupported:
 
124
            raise tests.TestNotApplicable('Format does not support tags')
 
125
        other.push(bound)
 
126
        self.assertEqual({'new-tag': 'some-rev'}, bound.tags.get_tag_dict())
 
127
        self.assertEqual({'new-tag': 'some-rev'}, master.tags.get_tag_dict())
 
128
 
101
129
    def test_push_uses_read_lock(self):
102
130
        """Push should only need a read lock on the source side."""
103
131
        source = self.make_branch_and_tree('source')
124
152
        except (errors.IncompatibleFormat, errors.UninitializableFormat):
125
153
            # This Branch format cannot create shared repositories
126
154
            return
 
155
        if not repo._format.supports_nesting_repositories:
 
156
            return
127
157
        # This is a little bit trickier because make_branch_and_tree will not
128
158
        # re-use a shared repository.
129
159
        a_bzrdir = self.make_bzrdir('repo/tree')
135
165
        try:
136
166
            tree = a_branch.bzrdir.create_workingtree()
137
167
        except errors.NotLocalUrl:
138
 
            if self.vfs_transport_factory is LocalURLServer:
 
168
            if self.vfs_transport_factory is test_server.LocalURLServer:
139
169
                # the branch is colocated on disk, we cannot create a checkout.
140
170
                # hopefully callers will expect this.
141
 
                local_controldir= bzrdir.BzrDir.open(self.get_vfs_only_url('repo/tree'))
 
171
                local_controldir = controldir.ControlDir.open(
 
172
                    self.get_vfs_only_url('repo/tree'))
142
173
                tree = local_controldir.create_workingtree()
143
174
            else:
144
175
                tree = a_branch.create_checkout('repo/tree', lightweight=True)
153
184
        self.assertEqual(tree.branch.last_revision(),
154
185
                         to_branch.last_revision())
155
186
 
 
187
    def test_push_overwrite_with_older_mainline_rev(self):
 
188
        """Pushing an older mainline revision with overwrite.
 
189
 
 
190
        This was <https://bugs.launchpad.net/bzr/+bug/386576>.
 
191
        """
 
192
        source = self.make_branch_and_tree('source')
 
193
        target = self.make_branch('target')
 
194
 
 
195
        source.commit('1st commit')
 
196
        source.commit('2nd commit', rev_id='rev-2')
 
197
        source.commit('3rd commit')
 
198
        source.branch.push(target)
 
199
        source.branch.push(target, stop_revision='rev-2', overwrite=True)
 
200
        self.assertEqual('rev-2', target.last_revision())
 
201
 
156
202
    def test_push_overwrite_of_non_tip_with_stop_revision(self):
157
203
        """Combining the stop_revision and overwrite options works.
158
 
        
 
204
 
159
205
        This was <https://bugs.launchpad.net/bzr/+bug/234229>.
160
206
        """
161
207
        source = self.make_branch_and_tree('source')
169
215
        source.branch.push(target, stop_revision='rev-2', overwrite=True)
170
216
        self.assertEqual('rev-2', target.last_revision())
171
217
 
172
 
 
173
 
class TestPushHook(TestCaseWithBranch):
 
218
    def test_push_repository_no_branch_doesnt_fetch_all_revs(self):
 
219
        # See https://bugs.launchpad.net/bzr/+bug/465517
 
220
        t = self.get_transport('target')
 
221
        t.ensure_base()
 
222
        bzrdir = self.bzrdir_format.initialize_on_transport(t)
 
223
        try:
 
224
            bzrdir.open_branch()
 
225
        except errors.NotBranchError:
 
226
            pass
 
227
        else:
 
228
            raise tests.TestNotApplicable('older formats can\'t have a repo'
 
229
                                          ' without a branch')
 
230
        try:
 
231
            source = self.make_branch_builder('source',
 
232
                                              format=self.bzrdir_format)
 
233
        except errors.UninitializableFormat:
 
234
            raise tests.TestNotApplicable('cannot initialize this format')
 
235
        source.start_series()
 
236
        source.build_snapshot('A', None, [
 
237
            ('add', ('', 'root-id', 'directory', None))])
 
238
        source.build_snapshot('B', ['A'], [])
 
239
        source.build_snapshot('C', ['A'], [])
 
240
        source.finish_series()
 
241
        b = source.get_branch()
 
242
        # Note: We can't read lock the source branch. Some formats take a write
 
243
        # lock to 'set_push_location', which breaks
 
244
        self.addCleanup(b.lock_write().unlock)
 
245
        repo = bzrdir.create_repository()
 
246
        # This means 'push the source branch into this dir'
 
247
        bzrdir.push_branch(b)
 
248
        self.addCleanup(repo.lock_read().unlock)
 
249
        # We should have pushed 'C', but not 'B', since it isn't in the
 
250
        # ancestry
 
251
        self.assertEqual(['A', 'C'], sorted(repo.all_revision_ids()))
 
252
 
 
253
    def test_push_with_default_stacking_does_not_create_broken_branch(self):
 
254
        """Pushing a new standalone branch works even when there's a default
 
255
        stacking policy at the destination.
 
256
 
 
257
        The new branch will preserve the repo format (even if it isn't the
 
258
        default for the branch), and will be stacked when the repo format
 
259
        allows (which means that the branch format isn't necessarly preserved).
 
260
        """
 
261
        if self.bzrdir_format.fixed_components:
 
262
            raise tests.TestNotApplicable('Not a metadir format.')
 
263
        if isinstance(self.branch_format, branch.BranchReferenceFormat):
 
264
            # This test could in principle apply to BranchReferenceFormat, but
 
265
            # make_branch_builder doesn't support it.
 
266
            raise tests.TestSkipped(
 
267
                "BranchBuilder can't make reference branches.")
 
268
        # Make a branch called "local" in a stackable repository
 
269
        # The branch has 3 revisions:
 
270
        #   - rev-1, adds a file
 
271
        #   - rev-2, no changes
 
272
        #   - rev-3, modifies the file.
 
273
        repo = self.make_repository('repo', shared=True, format='1.6')
 
274
        builder = self.make_branch_builder('repo/local')
 
275
        builder.start_series()
 
276
        builder.build_snapshot('rev-1', None, [
 
277
            ('add', ('', 'root-id', 'directory', '')),
 
278
            ('add', ('filename', 'f-id', 'file', 'content\n'))])
 
279
        builder.build_snapshot('rev-2', ['rev-1'], [])
 
280
        builder.build_snapshot('rev-3', ['rev-2'],
 
281
            [('modify', ('f-id', 'new-content\n'))])
 
282
        builder.finish_series()
 
283
        trunk = builder.get_branch()
 
284
        # Sprout rev-1 to "trunk", so that we can stack on it.
 
285
        trunk.bzrdir.sprout(self.get_url('trunk'), revision_id='rev-1')
 
286
        # Set a default stacking policy so that new branches will automatically
 
287
        # stack on trunk.
 
288
        self.make_bzrdir('.').get_config().set_default_stack_on('trunk')
 
289
        # Push rev-2 to a new branch "remote".  It will be stacked on "trunk".
 
290
        output = StringIO()
 
291
        push._show_push_branch(trunk, 'rev-2', self.get_url('remote'), output)
 
292
        # Push rev-3 onto "remote".  If "remote" not stacked and is missing the
 
293
        # fulltext record for f-id @ rev-1, then this will fail.
 
294
        remote_branch = branch.Branch.open(self.get_url('remote'))
 
295
        trunk.push(remote_branch)
 
296
        check.check_dwim(remote_branch.base, False, True, True)
 
297
 
 
298
 
 
299
class TestPushHook(per_branch.TestCaseWithBranch):
174
300
 
175
301
    def setUp(self):
176
302
        self.hook_calls = []
177
 
        TestCaseWithBranch.setUp(self)
 
303
        super(TestPushHook, self).setUp()
178
304
 
179
305
    def capture_post_push_hook(self, result):
180
306
        """Capture post push hook calls to self.hook_calls.
181
 
        
 
307
 
182
308
        The call is logged, as is some state of the two branches.
183
309
        """
184
310
        if result.local_branch:
198
324
    def test_post_push_empty_history(self):
199
325
        target = self.make_branch('target')
200
326
        source = self.make_branch('source')
201
 
        Branch.hooks.install_named_hook('post_push',
202
 
                                        self.capture_post_push_hook, None)
 
327
        branch.Branch.hooks.install_named_hook(
 
328
            'post_push', self.capture_post_push_hook, None)
203
329
        source.push(target)
204
330
        # with nothing there we should still get a notification, and
205
331
        # have both branches locked at the notification time.
206
332
        self.assertEqual([
207
 
            ('post_push', source, None, target.base, 0, NULL_REVISION,
208
 
             0, NULL_REVISION, True, None, True)
 
333
            ('post_push', source, None, target.base, 0, revision.NULL_REVISION,
 
334
             0, revision.NULL_REVISION, True, None, True)
209
335
            ],
210
336
            self.hook_calls)
211
337
 
212
338
    def test_post_push_bound_branch(self):
213
339
        # pushing to a bound branch should pass in the master branch to the
214
340
        # hook, allowing the correct number of emails to be sent, while still
215
 
        # allowing hooks that want to modify the target to do so to both 
 
341
        # allowing hooks that want to modify the target to do so to both
216
342
        # instances.
217
343
        target = self.make_branch('target')
218
344
        local = self.make_branch('local')
224
350
            # remotebranches can't be bound.  Let's instead make a new local
225
351
            # branch of the default type, which does allow binding.
226
352
            # See https://bugs.launchpad.net/bzr/+bug/112020
227
 
            local = BzrDir.create_branch_convenience('local2')
 
353
            local = controldir.ControlDir.create_branch_convenience('local2')
228
354
            local.bind(target)
229
355
        source = self.make_branch('source')
230
 
        Branch.hooks.install_named_hook('post_push',
231
 
                                        self.capture_post_push_hook, None)
 
356
        branch.Branch.hooks.install_named_hook(
 
357
            'post_push', self.capture_post_push_hook, None)
232
358
        source.push(local)
233
359
        # with nothing there we should still get a notification, and
234
360
        # have both branches locked at the notification time.
235
361
        self.assertEqual([
236
 
            ('post_push', source, local.base, target.base, 0, NULL_REVISION,
237
 
             0, NULL_REVISION, True, True, True)
 
362
            ('post_push', source, local.base, target.base, 0,
 
363
             revision.NULL_REVISION, 0, revision.NULL_REVISION,
 
364
             True, True, True)
238
365
            ],
239
366
            self.hook_calls)
240
367
 
245
372
        rev1 = target.commit('rev 1')
246
373
        target.unlock()
247
374
        sourcedir = target.bzrdir.clone(self.get_url('source'))
248
 
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
 
375
        source = memorytree.MemoryTree.create_on_branch(sourcedir.open_branch())
249
376
        rev2 = source.commit('rev 2')
250
 
        Branch.hooks.install_named_hook('post_push',
251
 
                                        self.capture_post_push_hook, None)
 
377
        branch.Branch.hooks.install_named_hook(
 
378
            'post_push', self.capture_post_push_hook, None)
252
379
        source.branch.push(target.branch)
253
380
        # with nothing there we should still get a notification, and
254
381
        # have both branches locked at the notification time.
257
384
             2, rev2, True, None, True)
258
385
            ],
259
386
            self.hook_calls)
 
387
 
 
388
 
 
389
class EmptyPushSmartEffortTests(per_branch.TestCaseWithBranch):
 
390
    """Tests that a push of 0 revisions should make a limited number of smart
 
391
    protocol RPCs.
 
392
    """
 
393
 
 
394
    def setUp(self):
 
395
        # Skip some scenarios that don't apply to these tests.
 
396
        if (self.transport_server is not None
 
397
            and issubclass(self.transport_server,
 
398
                           test_server.SmartTCPServer_for_testing)):
 
399
            raise tests.TestNotApplicable(
 
400
                'Does not apply when remote backing branch is also '
 
401
                'a smart branch')
 
402
        if not self.branch_format.supports_leaving_lock():
 
403
            raise tests.TestNotApplicable(
 
404
                'Branch format is not usable via HPSS.')
 
405
        super(EmptyPushSmartEffortTests, self).setUp()
 
406
        # Create a smart server that publishes whatever the backing VFS server
 
407
        # does.
 
408
        self.smart_server = test_server.SmartTCPServer_for_testing()
 
409
        self.start_server(self.smart_server, self.get_server())
 
410
        # Make two empty branches, 'empty' and 'target'.
 
411
        self.empty_branch = self.make_branch('empty')
 
412
        self.make_branch('target')
 
413
        # Log all HPSS calls into self.hpss_calls.
 
414
        client._SmartClient.hooks.install_named_hook(
 
415
            'call', self.capture_hpss_call, None)
 
416
        self.hpss_calls = []
 
417
 
 
418
    def capture_hpss_call(self, params):
 
419
        self.hpss_calls.append(params.method)
 
420
 
 
421
    def test_empty_branch_api(self):
 
422
        """The branch_obj.push API should make a limited number of HPSS calls.
 
423
        """
 
424
        t = transport.get_transport_from_url(self.smart_server.get_url()).clone('target')
 
425
        target = branch.Branch.open_from_transport(t)
 
426
        self.empty_branch.push(target)
 
427
        self.assertEqual(
 
428
            ['BzrDir.open_2.1',
 
429
             'BzrDir.open_branchV3',
 
430
             'BzrDir.find_repositoryV3',
 
431
             'Branch.get_stacked_on_url',
 
432
             'Branch.lock_write',
 
433
             'Branch.last_revision_info',
 
434
             'Branch.unlock'],
 
435
            self.hpss_calls)
 
436
 
 
437
    def test_empty_branch_command(self):
 
438
        """The 'bzr push' command should make a limited number of HPSS calls.
 
439
        """
 
440
        cmd = builtins.cmd_push()
 
441
        cmd.outf = tests.StringIOWrapper()
 
442
        cmd.run(
 
443
            directory=self.get_url('empty'),
 
444
            location=self.smart_server.get_url() + 'target')
 
445
        # HPSS calls as of 2008/09/22:
 
446
        # [BzrDir.open, BzrDir.open_branch, BzrDir.find_repositoryV2,
 
447
        # Branch.get_stacked_on_url, get, get, Branch.lock_write,
 
448
        # Branch.last_revision_info, Branch.unlock]
 
449
        self.assertTrue(len(self.hpss_calls) <= 9, self.hpss_calls)
 
450
 
 
451
 
 
452
class TestLossyPush(per_branch.TestCaseWithBranch):
 
453
 
 
454
    def setUp(self):
 
455
        self.hook_calls = []
 
456
        super(TestLossyPush, self).setUp()
 
457
 
 
458
    def test_lossy_push_raises_same_vcs(self):
 
459
        target = self.make_branch('target')
 
460
        source = self.make_branch('source')
 
461
        self.assertRaises(errors.LossyPushToSameVCS, source.push, target, lossy=True)