~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tests for branch.push behaviour."""
18
18
 
19
 
from cStringIO import StringIO
20
19
import os
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):
 
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):
45
32
 
46
33
    def test_push_convergence_simple(self):
47
 
        # when revisions are pushed, the left-most accessible parents must
 
34
        # when revisions are pushed, the left-most accessible parents must 
48
35
        # become the revision-history.
49
36
        mine = self.make_branch_and_tree('mine')
50
37
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
53
40
        mine.merge_from_branch(other.branch)
54
41
        mine.commit('merge my change', rev_id='P2')
55
42
        result = mine.branch.push(other.branch)
56
 
        self.assertEqual('P2', other.branch.last_revision())
 
43
        self.assertEqual(['P1', 'P2'], other.branch.revision_history())
57
44
        # result object contains some structured data
58
45
        self.assertEqual(result.old_revid, 'M1')
59
46
        self.assertEqual(result.new_revid, 'P2')
 
47
        # and it can be treated as an integer for compatibility
 
48
        self.assertEqual(int(result), 0)
60
49
 
61
50
    def test_push_merged_indirect(self):
62
51
        # it should be possible to do a push from one branch into another
73
62
        mine.merge_from_branch(other.branch)
74
63
        mine.commit('merge other', rev_id='P2')
75
64
        mine.branch.push(target.branch)
76
 
        self.assertEqual('P2', target.branch.last_revision())
 
65
        self.assertEqual(['P1', 'P2'], target.branch.revision_history())
77
66
 
78
67
    def test_push_to_checkout_updates_master(self):
79
68
        """Pushing into a checkout updates the checkout and the master branch"""
90
79
        rev2 = other.commit('other commit')
91
80
        # now push, which should update both checkout and master.
92
81
        other.branch.push(checkout.branch)
93
 
        self.assertEqual(rev2, checkout.branch.last_revision())
94
 
        self.assertEqual(rev2, master_tree.branch.last_revision())
 
82
        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
 
83
        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
95
84
 
96
85
    def test_push_raises_specific_error_on_master_connection_error(self):
97
86
        master_tree = self.make_branch_and_tree('master')
109
98
        self.assertRaises(errors.BoundBranchConnectionFailure,
110
99
                other.branch.push, checkout.branch)
111
100
 
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
 
 
129
101
    def test_push_uses_read_lock(self):
130
102
        """Push should only need a read lock on the source side."""
131
103
        source = self.make_branch_and_tree('source')
152
124
        except (errors.IncompatibleFormat, errors.UninitializableFormat):
153
125
            # This Branch format cannot create shared repositories
154
126
            return
155
 
        if not repo._format.supports_nesting_repositories:
156
 
            return
157
127
        # This is a little bit trickier because make_branch_and_tree will not
158
128
        # re-use a shared repository.
159
129
        a_bzrdir = self.make_bzrdir('repo/tree')
165
135
        try:
166
136
            tree = a_branch.bzrdir.create_workingtree()
167
137
        except errors.NotLocalUrl:
168
 
            if self.vfs_transport_factory is test_server.LocalURLServer:
 
138
            if self.vfs_transport_factory is LocalURLServer:
169
139
                # the branch is colocated on disk, we cannot create a checkout.
170
140
                # hopefully callers will expect this.
171
 
                local_controldir = controldir.ControlDir.open(
172
 
                    self.get_vfs_only_url('repo/tree'))
 
141
                local_controldir= bzrdir.BzrDir.open(self.get_vfs_only_url('repo/tree'))
173
142
                tree = local_controldir.create_workingtree()
174
143
            else:
175
144
                tree = a_branch.create_checkout('repo/tree', lightweight=True)
184
153
        self.assertEqual(tree.branch.last_revision(),
185
154
                         to_branch.last_revision())
186
155
 
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
 
 
202
156
    def test_push_overwrite_of_non_tip_with_stop_revision(self):
203
157
        """Combining the stop_revision and overwrite options works.
204
 
 
 
158
        
205
159
        This was <https://bugs.launchpad.net/bzr/+bug/234229>.
206
160
        """
207
161
        source = self.make_branch_and_tree('source')
215
169
        source.branch.push(target, stop_revision='rev-2', overwrite=True)
216
170
        self.assertEqual('rev-2', target.last_revision())
217
171
 
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):
 
172
 
 
173
class TestPushHook(TestCaseWithBranch):
300
174
 
301
175
    def setUp(self):
302
176
        self.hook_calls = []
303
 
        super(TestPushHook, self).setUp()
 
177
        TestCaseWithBranch.setUp(self)
304
178
 
305
179
    def capture_post_push_hook(self, result):
306
180
        """Capture post push hook calls to self.hook_calls.
307
 
 
 
181
        
308
182
        The call is logged, as is some state of the two branches.
309
183
        """
310
184
        if result.local_branch:
324
198
    def test_post_push_empty_history(self):
325
199
        target = self.make_branch('target')
326
200
        source = self.make_branch('source')
327
 
        branch.Branch.hooks.install_named_hook(
328
 
            'post_push', self.capture_post_push_hook, None)
 
201
        Branch.hooks.install_named_hook('post_push',
 
202
                                        self.capture_post_push_hook, None)
329
203
        source.push(target)
330
204
        # with nothing there we should still get a notification, and
331
205
        # have both branches locked at the notification time.
332
206
        self.assertEqual([
333
 
            ('post_push', source, None, target.base, 0, revision.NULL_REVISION,
334
 
             0, revision.NULL_REVISION, True, None, True)
 
207
            ('post_push', source, None, target.base, 0, NULL_REVISION,
 
208
             0, NULL_REVISION, True, None, True)
335
209
            ],
336
210
            self.hook_calls)
337
211
 
338
212
    def test_post_push_bound_branch(self):
339
213
        # pushing to a bound branch should pass in the master branch to the
340
214
        # hook, allowing the correct number of emails to be sent, while still
341
 
        # allowing hooks that want to modify the target to do so to both
 
215
        # allowing hooks that want to modify the target to do so to both 
342
216
        # instances.
343
217
        target = self.make_branch('target')
344
218
        local = self.make_branch('local')
350
224
            # remotebranches can't be bound.  Let's instead make a new local
351
225
            # branch of the default type, which does allow binding.
352
226
            # See https://bugs.launchpad.net/bzr/+bug/112020
353
 
            local = controldir.ControlDir.create_branch_convenience('local2')
 
227
            local = BzrDir.create_branch_convenience('local2')
354
228
            local.bind(target)
355
229
        source = self.make_branch('source')
356
 
        branch.Branch.hooks.install_named_hook(
357
 
            'post_push', self.capture_post_push_hook, None)
 
230
        Branch.hooks.install_named_hook('post_push',
 
231
                                        self.capture_post_push_hook, None)
358
232
        source.push(local)
359
233
        # with nothing there we should still get a notification, and
360
234
        # have both branches locked at the notification time.
361
235
        self.assertEqual([
362
 
            ('post_push', source, local.base, target.base, 0,
363
 
             revision.NULL_REVISION, 0, revision.NULL_REVISION,
364
 
             True, True, True)
 
236
            ('post_push', source, local.base, target.base, 0, NULL_REVISION,
 
237
             0, NULL_REVISION, True, True, True)
365
238
            ],
366
239
            self.hook_calls)
367
240
 
372
245
        rev1 = target.commit('rev 1')
373
246
        target.unlock()
374
247
        sourcedir = target.bzrdir.clone(self.get_url('source'))
375
 
        source = memorytree.MemoryTree.create_on_branch(sourcedir.open_branch())
 
248
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
376
249
        rev2 = source.commit('rev 2')
377
 
        branch.Branch.hooks.install_named_hook(
378
 
            'post_push', self.capture_post_push_hook, None)
 
250
        Branch.hooks.install_named_hook('post_push',
 
251
                                        self.capture_post_push_hook, None)
379
252
        source.branch.push(target.branch)
380
253
        # with nothing there we should still get a notification, and
381
254
        # have both branches locked at the notification time.
384
257
             2, rev2, True, None, True)
385
258
            ],
386
259
            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)