~greatmay12/+junk/test1

« back to all changes in this revision

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

  • Committer: thitipong at ndrsolution
  • Date: 2011-11-14 06:31:02 UTC
  • Revision ID: thitipong@ndrsolution.com-20111114063102-9obte3yfi2azku7d
ndr redirect version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009, 2010 Canonical Ltd
 
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
 
 
22
from testtools.matchers import (
 
23
    Equals,
 
24
    MatchesAny,
 
25
    )
 
26
 
 
27
from bzrlib import (
 
28
    branch,
 
29
    builtins,
 
30
    bzrdir,
 
31
    check,
 
32
    debug,
 
33
    errors,
 
34
    push,
 
35
    repository,
 
36
    tests,
 
37
    )
 
38
from bzrlib.branch import Branch
 
39
from bzrlib.bzrdir import BzrDir
 
40
from bzrlib.memorytree import MemoryTree
 
41
from bzrlib.revision import NULL_REVISION
 
42
from bzrlib.smart import client, server
 
43
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
 
44
from bzrlib.tests.per_interbranch import (
 
45
    TestCaseWithInterBranch,
 
46
    )
 
47
from bzrlib.tests import test_server
 
48
 
 
49
 
 
50
# These tests are based on similar tests in 
 
51
# bzrlib.tests.per_branch.test_push.
 
52
 
 
53
 
 
54
class TestPush(TestCaseWithInterBranch):
 
55
 
 
56
    def test_push_convergence_simple(self):
 
57
        # when revisions are pushed, the left-most accessible parents must
 
58
        # become the revision-history.
 
59
        mine = self.make_from_branch_and_tree('mine')
 
60
        mine.commit('1st post', rev_id='P1', allow_pointless=True)
 
61
        other = self.sprout_to(mine.bzrdir, 'other').open_workingtree()
 
62
        other.commit('my change', rev_id='M1', allow_pointless=True)
 
63
        mine.merge_from_branch(other.branch)
 
64
        mine.commit('merge my change', rev_id='P2')
 
65
        result = mine.branch.push(other.branch)
 
66
        self.assertEqual(['P1', 'P2'], other.branch.revision_history())
 
67
        # result object contains some structured data
 
68
        self.assertEqual(result.old_revid, 'M1')
 
69
        self.assertEqual(result.new_revid, 'P2')
 
70
        # and it can be treated as an integer for compatibility
 
71
        self.assertEqual(int(result), 0)
 
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)
 
88
        self.assertEqual(['P1', 'P2'], target.branch.revision_history())
 
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
 
 
101
        other_bzrdir = self.sprout_from(master_tree.branch.bzrdir, 'other')
 
102
        other = other_bzrdir.open_workingtree()
 
103
        rev2 = other.commit('other commit')
 
104
        # now push, which should update both checkout and master.
 
105
        other.branch.push(checkout.branch)
 
106
        self.assertEqual([rev1, rev2], checkout.branch.revision_history())
 
107
        self.assertEqual([rev1, rev2], master_tree.branch.revision_history())
 
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
 
117
        other_bzrdir = self.sprout_from(master_tree.branch.bzrdir, 'other')
 
118
        other = other_bzrdir.open_workingtree()
 
119
        # move the branch out of the way on disk to cause a connection
 
120
        # error.
 
121
        os.rename('master', 'master_gone')
 
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
 
152
        # This is a little bit trickier because make_from_branch_and_tree will not
 
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()
 
161
        except errors.NotLocalUrl:
 
162
            if self.vfs_transport_factory is test_server.LocalURLServer:
 
163
                # the branch is colocated on disk, we cannot create a checkout.
 
164
                # hopefully callers will expect this.
 
165
                local_controldir = bzrdir.BzrDir.open(self.get_vfs_only_url('repo/tree'))
 
166
                tree = local_controldir.create_workingtree()
 
167
            else:
 
168
                tree = a_branch.create_checkout('repo/tree', lightweight=True)
 
169
        self.build_tree(['repo/tree/a'])
 
170
        tree.add(['a'])
 
171
        tree.commit('a')
 
172
 
 
173
        to_branch = self.make_to_branch('repo/branch')
 
174
        tree.branch.push(to_branch)
 
175
 
 
176
        self.assertEqual(tree.branch.last_revision(),
 
177
                         to_branch.last_revision())
 
178
 
 
179
    def test_push_overwrite_of_non_tip_with_stop_revision(self):
 
180
        """Combining the stop_revision and overwrite options works.
 
181
 
 
182
        This was <https://bugs.launchpad.net/bzr/+bug/234229>.
 
183
        """
 
184
        source = self.make_from_branch_and_tree('source')
 
185
        target = self.make_to_branch('target')
 
186
 
 
187
        source.commit('1st commit')
 
188
        source.branch.push(target)
 
189
        source.commit('2nd commit', rev_id='rev-2')
 
190
        source.commit('3rd commit')
 
191
 
 
192
        source.branch.push(target, stop_revision='rev-2', overwrite=True)
 
193
        self.assertEqual('rev-2', target.last_revision())
 
194
 
 
195
    def test_push_with_default_stacking_does_not_create_broken_branch(self):
 
196
        """Pushing a new standalone branch works even when there's a default
 
197
        stacking policy at the destination.
 
198
 
 
199
        The new branch will preserve the repo format (even if it isn't the
 
200
        default for the branch), and will be stacked when the repo format
 
201
        allows (which means that the branch format isn't necessarly preserved).
 
202
        """
 
203
        if isinstance(self.branch_format_from, branch.BzrBranchFormat4):
 
204
            raise tests.TestNotApplicable('Not a metadir format.')
 
205
        if isinstance(self.branch_format_from, branch.BranchReferenceFormat):
 
206
            # This test could in principle apply to BranchReferenceFormat, but
 
207
            # make_branch_builder doesn't support it.
 
208
            raise tests.TestSkipped(
 
209
                "BranchBuilder can't make reference branches.")
 
210
        # Make a branch called "local" in a stackable repository
 
211
        # The branch has 3 revisions:
 
212
        #   - rev-1, adds a file
 
213
        #   - rev-2, no changes
 
214
        #   - rev-3, modifies the file.
 
215
        repo = self.make_repository('repo', shared=True, format='1.6')
 
216
        builder = self.make_from_branch_builder('repo/local')
 
217
        builder.start_series()
 
218
        builder.build_snapshot('rev-1', None, [
 
219
            ('add', ('', 'root-id', 'directory', '')),
 
220
            ('add', ('filename', 'f-id', 'file', 'content\n'))])
 
221
        builder.build_snapshot('rev-2', ['rev-1'], [])
 
222
        builder.build_snapshot('rev-3', ['rev-2'],
 
223
            [('modify', ('f-id', 'new-content\n'))])
 
224
        builder.finish_series()
 
225
        trunk = builder.get_branch()
 
226
        # Sprout rev-1 to "trunk", so that we can stack on it.
 
227
        trunk.bzrdir.sprout(self.get_url('trunk'), revision_id='rev-1')
 
228
        # Set a default stacking policy so that new branches will automatically
 
229
        # stack on trunk.
 
230
        self.make_bzrdir('.').get_config().set_default_stack_on('trunk')
 
231
        # Push rev-2 to a new branch "remote".  It will be stacked on "trunk".
 
232
        output = StringIO()
 
233
        push._show_push_branch(trunk, 'rev-2', self.get_url('remote'), output)
 
234
        # Push rev-3 onto "remote".  If "remote" not stacked and is missing the
 
235
        # fulltext record for f-id @ rev-1, then this will fail.
 
236
        remote_branch = Branch.open(self.get_url('remote'))
 
237
        trunk.push(remote_branch)
 
238
        check.check_dwim(remote_branch.base, False, True, True)
 
239
 
 
240
    def test_no_get_parent_map_after_insert_stream(self):
 
241
        # Effort test for bug 331823
 
242
        self.setup_smart_server_with_call_log()
 
243
        # Make a local branch with four revisions.  Four revisions because:
 
244
        # one to push, one there for _walk_to_common_revisions to find, one we
 
245
        # don't want to access, one for luck :)
 
246
        if isinstance(self.branch_format_from, branch.BranchReferenceFormat):
 
247
            # This test could in principle apply to BranchReferenceFormat, but
 
248
            # make_branch_builder doesn't support it.
 
249
            raise tests.TestSkipped(
 
250
                "BranchBuilder can't make reference branches.")
 
251
        try:
 
252
            builder = self.make_from_branch_builder('local')
 
253
        except (errors.TransportNotPossible, errors.UninitializableFormat):
 
254
            raise tests.TestNotApplicable('format not directly constructable')
 
255
        builder.start_series()
 
256
        builder.build_snapshot('first', None, [
 
257
            ('add', ('', 'root-id', 'directory', ''))])
 
258
        builder.build_snapshot('second', ['first'], [])
 
259
        builder.build_snapshot('third', ['second'], [])
 
260
        builder.build_snapshot('fourth', ['third'], [])
 
261
        builder.finish_series()
 
262
        local = branch.Branch.open(self.get_vfs_only_url('local'))
 
263
        # Initial push of three revisions
 
264
        remote_bzrdir = local.bzrdir.sprout(
 
265
            self.get_url('remote'), revision_id='third')
 
266
        remote = remote_bzrdir.open_branch()
 
267
        # Push fourth revision
 
268
        self.reset_smart_call_log()
 
269
        self.disableOptimisticGetParentMap()
 
270
        self.assertFalse(local.is_locked())
 
271
        local.push(remote)
 
272
        hpss_call_names = [item.call.method for item in self.hpss_calls]
 
273
        self.assertTrue('Repository.insert_stream_1.19' in hpss_call_names)
 
274
        insert_stream_idx = hpss_call_names.index(
 
275
            'Repository.insert_stream_1.19')
 
276
        calls_after_insert_stream = hpss_call_names[insert_stream_idx:]
 
277
        # After inserting the stream the client has no reason to query the
 
278
        # remote graph any further.
 
279
        bzr_core_trace = Equals(
 
280
            ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
 
281
             'get', 'Branch.set_last_revision_info', 'Branch.unlock'])
 
282
        bzr_loom_trace = Equals(
 
283
            ['Repository.insert_stream_1.19', 'Repository.insert_stream_1.19',
 
284
             'get', 'Branch.set_last_revision_info', 'get', 'Branch.unlock'])
 
285
        self.assertThat(calls_after_insert_stream,
 
286
            MatchesAny(bzr_core_trace, bzr_loom_trace))
 
287
 
 
288
    def disableOptimisticGetParentMap(self):
 
289
        # Tweak some class variables to stop remote get_parent_map calls asking
 
290
        # for or receiving more data than the caller asked for.
 
291
        self.overrideAttr(repository.InterRepository,
 
292
                          '_walk_to_common_revisions_batch_size', 1)
 
293
        self.overrideAttr(SmartServerRepositoryGetParentMap,
 
294
                            'no_extra_results', True)
 
295
 
 
296
 
 
297
class TestPushHook(TestCaseWithInterBranch):
 
298
 
 
299
    def setUp(self):
 
300
        self.hook_calls = []
 
301
        TestCaseWithInterBranch.setUp(self)
 
302
 
 
303
    def capture_post_push_hook(self, result):
 
304
        """Capture post push hook calls to self.hook_calls.
 
305
 
 
306
        The call is logged, as is some state of the two branches.
 
307
        """
 
308
        if result.local_branch:
 
309
            local_locked = result.local_branch.is_locked()
 
310
            local_base = result.local_branch.base
 
311
        else:
 
312
            local_locked = None
 
313
            local_base = None
 
314
        self.hook_calls.append(
 
315
            ('post_push', result.source_branch, local_base,
 
316
             result.master_branch.base,
 
317
             result.old_revno, result.old_revid,
 
318
             result.new_revno, result.new_revid,
 
319
             result.source_branch.is_locked(), local_locked,
 
320
             result.master_branch.is_locked()))
 
321
 
 
322
    def test_post_push_empty_history(self):
 
323
        target = self.make_to_branch('target')
 
324
        source = self.make_from_branch('source')
 
325
        Branch.hooks.install_named_hook('post_push',
 
326
                                        self.capture_post_push_hook, None)
 
327
        source.push(target)
 
328
        # with nothing there we should still get a notification, and
 
329
        # have both branches locked at the notification time.
 
330
        self.assertEqual([
 
331
            ('post_push', source, None, target.base, 0, NULL_REVISION,
 
332
             0, NULL_REVISION, True, None, True)
 
333
            ],
 
334
            self.hook_calls)
 
335
 
 
336
    def test_post_push_bound_branch(self):
 
337
        # pushing to a bound branch should pass in the master branch to the
 
338
        # hook, allowing the correct number of emails to be sent, while still
 
339
        # allowing hooks that want to modify the target to do so to both
 
340
        # instances.
 
341
        target = self.make_to_branch('target')
 
342
        local = self.make_from_branch('local')
 
343
        try:
 
344
            local.bind(target)
 
345
        except errors.UpgradeRequired:
 
346
            # We can't bind this format to itself- typically it is the local
 
347
            # branch that doesn't support binding.  As of May 2007
 
348
            # remotebranches can't be bound.  Let's instead make a new local
 
349
            # branch of the default type, which does allow binding.
 
350
            # See https://bugs.launchpad.net/bzr/+bug/112020
 
351
            local = BzrDir.create_branch_convenience('local2')
 
352
            local.bind(target)
 
353
        source = self.make_from_branch('source')
 
354
        Branch.hooks.install_named_hook('post_push',
 
355
                                        self.capture_post_push_hook, None)
 
356
        source.push(local)
 
357
        # with nothing there we should still get a notification, and
 
358
        # have both branches locked at the notification time.
 
359
        self.assertEqual([
 
360
            ('post_push', source, local.base, target.base, 0, NULL_REVISION,
 
361
             0, NULL_REVISION, True, True, True)
 
362
            ],
 
363
            self.hook_calls)
 
364
 
 
365
    def test_post_push_nonempty_history(self):
 
366
        target = self.make_to_branch_and_tree('target')
 
367
        target.lock_write()
 
368
        target.add('')
 
369
        rev1 = target.commit('rev 1')
 
370
        target.unlock()
 
371
        sourcedir = target.bzrdir.clone(self.get_url('source'))
 
372
        source = MemoryTree.create_on_branch(sourcedir.open_branch())
 
373
        rev2 = source.commit('rev 2')
 
374
        Branch.hooks.install_named_hook('post_push',
 
375
                                        self.capture_post_push_hook, None)
 
376
        source.branch.push(target.branch)
 
377
        # with nothing there we should still get a notification, and
 
378
        # have both branches locked at the notification time.
 
379
        self.assertEqual([
 
380
            ('post_push', source.branch, None, target.branch.base, 1, rev1,
 
381
             2, rev2, True, None, True)
 
382
            ],
 
383
            self.hook_calls)