~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
# -*- coding: utf-8 -*-
 
1
# Copyright (C) 2006-2011 Canonical Ltd
3
2
#
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
13
12
#
14
13
# You should have received a copy of the GNU General Public License
15
14
# along with this program; if not, write to the Free Software
16
 
# 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
17
16
 
18
17
 
19
18
"""Black-box tests for bzr push."""
20
19
 
21
 
import os
22
 
 
23
 
import bzrlib
24
 
from bzrlib.branch import Branch
25
 
from bzrlib.bzrdir import BzrDirMetaFormat1
26
 
from bzrlib.osutils import abspath
27
 
from bzrlib.repository import RepositoryFormatKnit1
28
 
from bzrlib.tests.blackbox import ExternalBase
29
 
from bzrlib.uncommit import uncommit
30
 
from bzrlib.urlutils import local_path_from_url
31
 
from bzrlib.workingtree import WorkingTree
32
 
 
33
 
 
34
 
class TestPush(ExternalBase):
 
20
import re
 
21
 
 
22
from bzrlib import (
 
23
    branch,
 
24
    bzrdir,
 
25
    errors,
 
26
    osutils,
 
27
    tests,
 
28
    transport,
 
29
    uncommit,
 
30
    urlutils,
 
31
    workingtree
 
32
    )
 
33
from bzrlib.repofmt import knitrepo
 
34
from bzrlib.tests import (
 
35
    blackbox,
 
36
    http_server,
 
37
    scenarios,
 
38
    test_foreign,
 
39
    test_server,
 
40
    )
 
41
from bzrlib.transport import memory
 
42
 
 
43
 
 
44
load_tests = scenarios.load_tests_apply_scenarios
 
45
 
 
46
 
 
47
class TestPush(tests.TestCaseWithTransport):
 
48
 
 
49
    def test_push_error_on_vfs_http(self):
 
50
        """ pushing a branch to a HTTP server fails cleanly. """
 
51
        # the trunk is published on a web server
 
52
        self.transport_readonly_server = http_server.HttpServer
 
53
        self.make_branch('source')
 
54
        public_url = self.get_readonly_url('target')
 
55
        self.run_bzr_error(['http does not support mkdir'],
 
56
                           ['push', public_url],
 
57
                           working_dir='source')
35
58
 
36
59
    def test_push_remember(self):
37
60
        """Push changes from one branch to another and test push location."""
55
78
        self.assertEqual(None, branch_b.get_push_location())
56
79
 
57
80
        # test push for failure without push location set
58
 
        os.chdir('branch_a')
59
 
        out = self.runbzr('push', retcode=3)
 
81
        out = self.run_bzr('push', working_dir='branch_a', retcode=3)
60
82
        self.assertEquals(out,
61
83
                ('','bzr: ERROR: No push location known or specified.\n'))
62
84
 
63
85
        # test not remembered if cannot actually push
64
 
        self.run_bzr('push', '../path/which/doesnt/exist', retcode=3)
65
 
        out = self.run_bzr('push', retcode=3)
 
86
        self.run_bzr('push path/which/doesnt/exist',
 
87
                     working_dir='branch_a', retcode=3)
 
88
        out = self.run_bzr('push', working_dir='branch_a', retcode=3)
66
89
        self.assertEquals(
67
90
                ('', 'bzr: ERROR: No push location known or specified.\n'),
68
91
                out)
69
92
 
70
93
        # test implicit --remember when no push location set, push fails
71
 
        out = self.run_bzr('push', '../branch_b', retcode=3)
 
94
        out = self.run_bzr('push ../branch_b',
 
95
                           working_dir='branch_a', retcode=3)
72
96
        self.assertEquals(out,
73
97
                ('','bzr: ERROR: These branches have diverged.  '
74
 
                    'Try using "merge" and then "push".\n'))
75
 
        self.assertEquals(abspath(branch_a.get_push_location()),
76
 
                          abspath(branch_b.bzrdir.root_transport.base))
 
98
                 'See "bzr help diverged-branches" for more information.\n'))
 
99
        self.assertEquals(osutils.abspath(branch_a.get_push_location()),
 
100
                          osutils.abspath(branch_b.bzrdir.root_transport.base))
77
101
 
78
102
        # test implicit --remember after resolving previous failure
79
 
        uncommit(branch=branch_b, tree=tree_b)
 
103
        uncommit.uncommit(branch=branch_b, tree=tree_b)
80
104
        transport.delete('branch_b/c')
81
 
        out = self.run_bzr('push')
 
105
        out, err = self.run_bzr('push', working_dir='branch_a')
82
106
        path = branch_a.get_push_location()
83
 
        self.assertEquals(('Using saved location: %s\n' 
84
 
                           % (local_path_from_url(path),)
85
 
                          , 'All changes applied successfully.\n'
86
 
                            '1 revision(s) pushed.\n'), out)
 
107
        self.assertEquals(out,
 
108
                          'Using saved push location: %s\n'
 
109
                          % urlutils.local_path_from_url(path))
 
110
        self.assertEqual(err,
 
111
                         'All changes applied successfully.\n'
 
112
                         'Pushed up to revision 2.\n')
87
113
        self.assertEqual(path,
88
114
                         branch_b.bzrdir.root_transport.base)
89
115
        # test explicit --remember
90
 
        self.run_bzr('push', '../branch_c', '--remember')
 
116
        self.run_bzr('push ../branch_c --remember', working_dir='branch_a')
91
117
        self.assertEquals(branch_a.get_push_location(),
92
118
                          branch_c.bzrdir.root_transport.base)
93
 
    
 
119
 
94
120
    def test_push_without_tree(self):
95
121
        # bzr push from a branch that does not have a checkout should work.
96
122
        b = self.make_branch('.')
97
 
        out, err = self.run_bzr('push', 'pushed-location')
 
123
        out, err = self.run_bzr('push pushed-location')
98
124
        self.assertEqual('', out)
99
 
        self.assertEqual('0 revision(s) pushed.\n', err)
100
 
        b2 = bzrlib.branch.Branch.open('pushed-location')
 
125
        self.assertEqual('Created new branch.\n', err)
 
126
        b2 = branch.Branch.open('pushed-location')
101
127
        self.assertEndsWith(b2.base, 'pushed-location/')
102
128
 
 
129
    def test_push_no_tree(self):
 
130
        # bzr push --no-tree of a branch with working trees
 
131
        b = self.make_branch_and_tree('push-from')
 
132
        self.build_tree(['push-from/file'])
 
133
        b.add('file')
 
134
        b.commit('commit 1')
 
135
        out, err = self.run_bzr('push --no-tree -d push-from push-to')
 
136
        self.assertEqual('', out)
 
137
        self.assertEqual('Created new branch.\n', err)
 
138
        self.assertPathDoesNotExist('push-to/file')
 
139
 
103
140
    def test_push_new_branch_revision_count(self):
104
 
        # bzr push of a branch with revisions to a new location 
105
 
        # should print the number of revisions equal to the length of the 
 
141
        # bzr push of a branch with revisions to a new location
 
142
        # should print the number of revisions equal to the length of the
106
143
        # local branch.
107
144
        t = self.make_branch_and_tree('tree')
108
145
        self.build_tree(['tree/file'])
109
146
        t.add('file')
110
147
        t.commit('commit 1')
111
 
        os.chdir('tree')
112
 
        out, err = self.run_bzr('push', 'pushed-to')
113
 
        os.chdir('..')
 
148
        out, err = self.run_bzr('push -d tree pushed-to')
114
149
        self.assertEqual('', out)
115
 
        self.assertEqual('1 revision(s) pushed.\n', err)
 
150
        self.assertEqual('Created new branch.\n', err)
116
151
 
117
152
    def test_push_only_pushes_history(self):
118
153
        # Knit branches should only push the history for the current revision.
119
 
        format = BzrDirMetaFormat1()
120
 
        format.repository_format = RepositoryFormatKnit1()
 
154
        format = bzrdir.BzrDirMetaFormat1()
 
155
        format.repository_format = knitrepo.RepositoryFormatKnit1()
121
156
        shared_repo = self.make_repository('repo', format=format, shared=True)
122
157
        shared_repo.set_make_working_trees(True)
123
158
 
124
159
        def make_shared_tree(path):
125
160
            shared_repo.bzrdir.root_transport.mkdir(path)
126
161
            shared_repo.bzrdir.create_branch_convenience('repo/' + path)
127
 
            return WorkingTree.open('repo/' + path)
 
162
            return workingtree.WorkingTree.open('repo/' + path)
128
163
        tree_a = make_shared_tree('a')
129
164
        self.build_tree(['repo/a/file'])
130
165
        tree_a.add('file')
145
180
 
146
181
        # Now that we have a repository with shared files, make sure
147
182
        # that things aren't copied out by a 'push'
148
 
        os.chdir('repo/b')
149
 
        self.run_bzr('push', '../../push-b')
150
 
        pushed_tree = WorkingTree.open('../../push-b')
 
183
        self.run_bzr('push ../../push-b', working_dir='repo/b')
 
184
        pushed_tree = workingtree.WorkingTree.open('push-b')
151
185
        pushed_repo = pushed_tree.branch.repository
152
186
        self.assertFalse(pushed_repo.has_revision('a-1'))
153
187
        self.assertFalse(pushed_repo.has_revision('a-2'))
155
189
 
156
190
    def test_push_funky_id(self):
157
191
        t = self.make_branch_and_tree('tree')
158
 
        os.chdir('tree')
159
 
        self.build_tree(['filename'])
 
192
        self.build_tree(['tree/filename'])
160
193
        t.add('filename', 'funky-chars<>%&;"\'')
161
194
        t.commit('commit filename')
162
 
        self.run_bzr('push', '../new-tree')
 
195
        self.run_bzr('push -d tree new-tree')
 
196
 
 
197
    def test_push_dash_d(self):
 
198
        t = self.make_branch_and_tree('from')
 
199
        t.commit(allow_pointless=True,
 
200
                message='first commit')
 
201
        self.run_bzr('push -d from to-one')
 
202
        self.assertPathExists('to-one')
 
203
        self.run_bzr('push -d %s %s'
 
204
            % tuple(map(urlutils.local_path_to_url, ['from', 'to-two'])))
 
205
        self.assertPathExists('to-two')
 
206
 
 
207
    def test_push_repository_no_branch_doesnt_fetch_all_revs(self):
 
208
        # See https://bugs.launchpad.net/bzr/+bug/465517
 
209
        target_repo = self.make_repository('target')
 
210
        source = self.make_branch_builder('source')
 
211
        source.start_series()
 
212
        source.build_snapshot('A', None, [
 
213
            ('add', ('', 'root-id', 'directory', None))])
 
214
        source.build_snapshot('B', ['A'], [])
 
215
        source.build_snapshot('C', ['A'], [])
 
216
        source.finish_series()
 
217
        self.run_bzr('push target -d source')
 
218
        self.addCleanup(target_repo.lock_read().unlock)
 
219
        # We should have pushed 'C', but not 'B', since it isn't in the
 
220
        # ancestry
 
221
        self.assertEqual([('A',), ('C',)], sorted(target_repo.revisions.keys()))
 
222
 
 
223
    def test_push_smart_non_stacked_streaming_acceptance(self):
 
224
        self.setup_smart_server_with_call_log()
 
225
        t = self.make_branch_and_tree('from')
 
226
        t.commit(allow_pointless=True, message='first commit')
 
227
        self.reset_smart_call_log()
 
228
        self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
 
229
        # This figure represent the amount of work to perform this use case. It
 
230
        # is entirely ok to reduce this number if a test fails due to rpc_count
 
231
        # being too low. If rpc_count increases, more network roundtrips have
 
232
        # become necessary for this use case. Please do not adjust this number
 
233
        # upwards without agreement from bzr's network support maintainers.
 
234
        self.assertLength(9, self.hpss_calls)
 
235
 
 
236
    def test_push_smart_stacked_streaming_acceptance(self):
 
237
        self.setup_smart_server_with_call_log()
 
238
        parent = self.make_branch_and_tree('parent', format='1.9')
 
239
        parent.commit(message='first commit')
 
240
        local = parent.bzrdir.sprout('local').open_workingtree()
 
241
        local.commit(message='local commit')
 
242
        self.reset_smart_call_log()
 
243
        self.run_bzr(['push', '--stacked', '--stacked-on', '../parent',
 
244
            self.get_url('public')], working_dir='local')
 
245
        # This figure represent the amount of work to perform this use case. It
 
246
        # is entirely ok to reduce this number if a test fails due to rpc_count
 
247
        # being too low. If rpc_count increases, more network roundtrips have
 
248
        # become necessary for this use case. Please do not adjust this number
 
249
        # upwards without agreement from bzr's network support maintainers.
 
250
        self.assertLength(13, self.hpss_calls)
 
251
        remote = branch.Branch.open('public')
 
252
        self.assertEndsWith(remote.get_stacked_on_url(), '/parent')
 
253
 
 
254
    def test_push_smart_tags_streaming_acceptance(self):
 
255
        self.setup_smart_server_with_call_log()
 
256
        t = self.make_branch_and_tree('from')
 
257
        rev_id = t.commit(allow_pointless=True, message='first commit')
 
258
        t.branch.tags.set_tag('new-tag', rev_id)
 
259
        self.reset_smart_call_log()
 
260
        self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
 
261
        # This figure represent the amount of work to perform this use case. It
 
262
        # is entirely ok to reduce this number if a test fails due to rpc_count
 
263
        # being too low. If rpc_count increases, more network roundtrips have
 
264
        # become necessary for this use case. Please do not adjust this number
 
265
        # upwards without agreement from bzr's network support maintainers.
 
266
        self.assertLength(11, self.hpss_calls)
 
267
 
 
268
    def test_push_smart_incremental_acceptance(self):
 
269
        self.setup_smart_server_with_call_log()
 
270
        t = self.make_branch_and_tree('from')
 
271
        rev_id1 = t.commit(allow_pointless=True, message='first commit')
 
272
        rev_id2 = t.commit(allow_pointless=True, message='second commit')
 
273
        self.run_bzr(
 
274
            ['push', self.get_url('to-one'), '-r1'], working_dir='from')
 
275
        self.reset_smart_call_log()
 
276
        self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
 
277
        # This figure represent the amount of work to perform this use case. It
 
278
        # is entirely ok to reduce this number if a test fails due to rpc_count
 
279
        # being too low. If rpc_count increases, more network roundtrips have
 
280
        # become necessary for this use case. Please do not adjust this number
 
281
        # upwards without agreement from bzr's network support maintainers.
 
282
        self.assertLength(11, self.hpss_calls)
 
283
 
 
284
    def test_push_smart_with_default_stacking_url_path_segment(self):
 
285
        # If the default stacked-on location is a path element then branches
 
286
        # we push there over the smart server are stacked and their
 
287
        # stacked_on_url is that exact path segment. Added to nail bug 385132.
 
288
        self.setup_smart_server_with_call_log()
 
289
        self.make_branch('stack-on', format='1.9')
 
290
        self.make_bzrdir('.').get_config().set_default_stack_on(
 
291
            '/stack-on')
 
292
        self.make_branch('from', format='1.9')
 
293
        out, err = self.run_bzr(['push', '-d', 'from', self.get_url('to')])
 
294
        b = branch.Branch.open(self.get_url('to'))
 
295
        self.assertEqual('/extra/stack-on', b.get_stacked_on_url())
 
296
 
 
297
    def test_push_smart_with_default_stacking_relative_path(self):
 
298
        # If the default stacked-on location is a relative path then branches
 
299
        # we push there over the smart server are stacked and their
 
300
        # stacked_on_url is a relative path. Added to nail bug 385132.
 
301
        self.setup_smart_server_with_call_log()
 
302
        self.make_branch('stack-on', format='1.9')
 
303
        self.make_bzrdir('.').get_config().set_default_stack_on('stack-on')
 
304
        self.make_branch('from', format='1.9')
 
305
        out, err = self.run_bzr(['push', '-d', 'from', self.get_url('to')])
 
306
        b = branch.Branch.open(self.get_url('to'))
 
307
        self.assertEqual('../stack-on', b.get_stacked_on_url())
 
308
 
 
309
    def create_simple_tree(self):
 
310
        tree = self.make_branch_and_tree('tree')
 
311
        self.build_tree(['tree/a'])
 
312
        tree.add(['a'], ['a-id'])
 
313
        tree.commit('one', rev_id='r1')
 
314
        return tree
 
315
 
 
316
    def test_push_create_prefix(self):
 
317
        """'bzr push --create-prefix' will create leading directories."""
 
318
        tree = self.create_simple_tree()
 
319
 
 
320
        self.run_bzr_error(['Parent directory of ../new/tree does not exist'],
 
321
                           'push ../new/tree',
 
322
                           working_dir='tree')
 
323
        self.run_bzr('push ../new/tree --create-prefix',
 
324
                     working_dir='tree')
 
325
        new_tree = workingtree.WorkingTree.open('new/tree')
 
326
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
 
327
        self.assertPathExists('new/tree/a')
 
328
 
 
329
    def test_push_use_existing(self):
 
330
        """'bzr push --use-existing-dir' can push into an existing dir.
 
331
 
 
332
        By default, 'bzr push' will not use an existing, non-versioned dir.
 
333
        """
 
334
        tree = self.create_simple_tree()
 
335
        self.build_tree(['target/'])
 
336
 
 
337
        self.run_bzr_error(['Target directory ../target already exists',
 
338
                            'Supply --use-existing-dir',
 
339
                           ],
 
340
                           'push ../target', working_dir='tree')
 
341
 
 
342
        self.run_bzr('push --use-existing-dir ../target',
 
343
                     working_dir='tree')
 
344
 
 
345
        new_tree = workingtree.WorkingTree.open('target')
 
346
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
 
347
        # The push should have created target/a
 
348
        self.assertPathExists('target/a')
 
349
 
 
350
    def test_push_use_existing_into_empty_bzrdir(self):
 
351
        """'bzr push --use-existing-dir' into a dir with an empty .bzr dir
 
352
        fails.
 
353
        """
 
354
        tree = self.create_simple_tree()
 
355
        self.build_tree(['target/', 'target/.bzr/'])
 
356
        self.run_bzr_error(
 
357
            ['Target directory ../target already contains a .bzr directory, '
 
358
             'but it is not valid.'],
 
359
            'push ../target --use-existing-dir', working_dir='tree')
 
360
 
 
361
    def test_push_onto_repo(self):
 
362
        """We should be able to 'bzr push' into an existing bzrdir."""
 
363
        tree = self.create_simple_tree()
 
364
        repo = self.make_repository('repo', shared=True)
 
365
 
 
366
        self.run_bzr('push ../repo',
 
367
                     working_dir='tree')
 
368
 
 
369
        # Pushing onto an existing bzrdir will create a repository and
 
370
        # branch as needed, but will only create a working tree if there was
 
371
        # no BzrDir before.
 
372
        self.assertRaises(errors.NoWorkingTree,
 
373
                          workingtree.WorkingTree.open, 'repo')
 
374
        new_branch = branch.Branch.open('repo')
 
375
        self.assertEqual(tree.last_revision(), new_branch.last_revision())
 
376
 
 
377
    def test_push_onto_just_bzrdir(self):
 
378
        """We don't handle when the target is just a bzrdir.
 
379
 
 
380
        Because you shouldn't be able to create *just* a bzrdir in the wild.
 
381
        """
 
382
        # TODO: jam 20070109 Maybe it would be better to create the repository
 
383
        #       if at this point
 
384
        tree = self.create_simple_tree()
 
385
        a_bzrdir = self.make_bzrdir('dir')
 
386
 
 
387
        self.run_bzr_error(['At ../dir you have a valid .bzr control'],
 
388
                'push ../dir',
 
389
                working_dir='tree')
 
390
 
 
391
    def test_push_with_revisionspec(self):
 
392
        """We should be able to push a revision older than the tip."""
 
393
        tree_from = self.make_branch_and_tree('from')
 
394
        tree_from.commit("One.", rev_id="from-1")
 
395
        tree_from.commit("Two.", rev_id="from-2")
 
396
 
 
397
        self.run_bzr('push -r1 ../to', working_dir='from')
 
398
 
 
399
        tree_to = workingtree.WorkingTree.open('to')
 
400
        repo_to = tree_to.branch.repository
 
401
        self.assertTrue(repo_to.has_revision('from-1'))
 
402
        self.assertFalse(repo_to.has_revision('from-2'))
 
403
        self.assertEqual(tree_to.branch.last_revision_info()[1], 'from-1')
 
404
 
 
405
        self.run_bzr_error(
 
406
            ['bzr: ERROR: bzr push --revision '
 
407
             'takes exactly one revision identifier\n'],
 
408
            'push -r0..2 ../to', working_dir='from')
 
409
 
 
410
    def create_trunk_and_feature_branch(self):
 
411
        # We have a mainline
 
412
        trunk_tree = self.make_branch_and_tree('target',
 
413
            format='1.9')
 
414
        trunk_tree.commit('mainline')
 
415
        # and a branch from it
 
416
        branch_tree = self.make_branch_and_tree('branch',
 
417
            format='1.9')
 
418
        branch_tree.pull(trunk_tree.branch)
 
419
        branch_tree.branch.set_parent(trunk_tree.branch.base)
 
420
        # with some work on it
 
421
        branch_tree.commit('moar work plz')
 
422
        return trunk_tree, branch_tree
 
423
 
 
424
    def assertPublished(self, branch_revid, stacked_on):
 
425
        """Assert that the branch 'published' has been published correctly."""
 
426
        published_branch = branch.Branch.open('published')
 
427
        # The published branch refers to the mainline
 
428
        self.assertEqual(stacked_on, published_branch.get_stacked_on_url())
 
429
        # and the branch's work was pushed
 
430
        self.assertTrue(published_branch.repository.has_revision(branch_revid))
 
431
 
 
432
    def test_push_new_branch_stacked_on(self):
 
433
        """Pushing a new branch with --stacked-on creates a stacked branch."""
 
434
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
 
435
        # we publish branch_tree with a reference to the mainline.
 
436
        out, err = self.run_bzr(['push', '--stacked-on', trunk_tree.branch.base,
 
437
            self.get_url('published')], working_dir='branch')
 
438
        self.assertEqual('', out)
 
439
        self.assertEqual('Created new stacked branch referring to %s.\n' %
 
440
            trunk_tree.branch.base, err)
 
441
        self.assertPublished(branch_tree.last_revision(),
 
442
            trunk_tree.branch.base)
 
443
 
 
444
    def test_push_new_branch_stacked_uses_parent_when_no_public_url(self):
 
445
        """When the parent has no public url the parent is used as-is."""
 
446
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
 
447
        # now we do a stacked push, which should determine the public location
 
448
        # for us.
 
449
        out, err = self.run_bzr(['push', '--stacked',
 
450
            self.get_url('published')], working_dir='branch')
 
451
        self.assertEqual('', out)
 
452
        self.assertEqual('Created new stacked branch referring to %s.\n' %
 
453
            trunk_tree.branch.base, err)
 
454
        self.assertPublished(branch_tree.last_revision(),
 
455
                             trunk_tree.branch.base)
 
456
 
 
457
    def test_push_new_branch_stacked_uses_parent_public(self):
 
458
        """Pushing a new branch with --stacked creates a stacked branch."""
 
459
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
 
460
        # the trunk is published on a web server
 
461
        self.transport_readonly_server = http_server.HttpServer
 
462
        trunk_public = self.make_branch('public_trunk', format='1.9')
 
463
        trunk_public.pull(trunk_tree.branch)
 
464
        trunk_public_url = self.get_readonly_url('public_trunk')
 
465
        trunk_tree.branch.set_public_branch(trunk_public_url)
 
466
        # now we do a stacked push, which should determine the public location
 
467
        # for us.
 
468
        out, err = self.run_bzr(['push', '--stacked',
 
469
            self.get_url('published')], working_dir='branch')
 
470
        self.assertEqual('', out)
 
471
        self.assertEqual('Created new stacked branch referring to %s.\n' %
 
472
            trunk_public_url, err)
 
473
        self.assertPublished(branch_tree.last_revision(), trunk_public_url)
 
474
 
 
475
    def test_push_new_branch_stacked_no_parent(self):
 
476
        """Pushing with --stacked and no parent branch errors."""
 
477
        branch = self.make_branch_and_tree('branch', format='1.9')
 
478
        # now we do a stacked push, which should fail as the place to refer too
 
479
        # cannot be determined.
 
480
        out, err = self.run_bzr_error(
 
481
            ['Could not determine branch to refer to\\.'], ['push', '--stacked',
 
482
            self.get_url('published')], working_dir='branch')
 
483
        self.assertEqual('', out)
 
484
        self.assertFalse(self.get_transport('published').has('.'))
 
485
 
 
486
    def test_push_notifies_default_stacking(self):
 
487
        self.make_branch('stack_on', format='1.6')
 
488
        self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
 
489
        self.make_branch('from', format='1.6')
 
490
        out, err = self.run_bzr('push -d from to')
 
491
        self.assertContainsRe(err,
 
492
                              'Using default stacking branch stack_on at .*')
 
493
 
 
494
    def test_push_stacks_with_default_stacking_if_target_is_stackable(self):
 
495
        self.make_branch('stack_on', format='1.6')
 
496
        self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
 
497
        self.make_branch('from', format='pack-0.92')
 
498
        out, err = self.run_bzr('push -d from to')
 
499
        b = branch.Branch.open('to')
 
500
        self.assertEqual('../stack_on', b.get_stacked_on_url())
 
501
 
 
502
    def test_push_does_not_change_format_with_default_if_target_cannot(self):
 
503
        self.make_branch('stack_on', format='pack-0.92')
 
504
        self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
 
505
        self.make_branch('from', format='pack-0.92')
 
506
        out, err = self.run_bzr('push -d from to')
 
507
        b = branch.Branch.open('to')
 
508
        self.assertRaises(errors.UnstackableBranchFormat, b.get_stacked_on_url)
 
509
 
 
510
    def test_push_doesnt_create_broken_branch(self):
 
511
        """Pushing a new standalone branch works even when there's a default
 
512
        stacking policy at the destination.
 
513
 
 
514
        The new branch will preserve the repo format (even if it isn't the
 
515
        default for the branch), and will be stacked when the repo format
 
516
        allows (which means that the branch format isn't necessarly preserved).
 
517
        """
 
518
        self.make_repository('repo', shared=True, format='1.6')
 
519
        builder = self.make_branch_builder('repo/local', format='pack-0.92')
 
520
        builder.start_series()
 
521
        builder.build_snapshot('rev-1', None, [
 
522
            ('add', ('', 'root-id', 'directory', '')),
 
523
            ('add', ('filename', 'f-id', 'file', 'content\n'))])
 
524
        builder.build_snapshot('rev-2', ['rev-1'], [])
 
525
        builder.build_snapshot('rev-3', ['rev-2'],
 
526
            [('modify', ('f-id', 'new-content\n'))])
 
527
        builder.finish_series()
 
528
        branch = builder.get_branch()
 
529
        # Push rev-1 to "trunk", so that we can stack on it.
 
530
        self.run_bzr('push -d repo/local trunk -r 1')
 
531
        # Set a default stacking policy so that new branches will automatically
 
532
        # stack on trunk.
 
533
        self.make_bzrdir('.').get_config().set_default_stack_on('trunk')
 
534
        # Push rev-2 to a new branch "remote".  It will be stacked on "trunk".
 
535
        out, err = self.run_bzr('push -d repo/local remote -r 2')
 
536
        self.assertContainsRe(
 
537
            err, 'Using default stacking branch trunk at .*')
 
538
        # Push rev-3 onto "remote".  If "remote" not stacked and is missing the
 
539
        # fulltext record for f-id @ rev-1, then this will fail.
 
540
        out, err = self.run_bzr('push -d repo/local remote -r 3')
 
541
 
 
542
    def test_push_verbose_shows_log(self):
 
543
        tree = self.make_branch_and_tree('source')
 
544
        tree.commit('rev1')
 
545
        out, err = self.run_bzr('push -v -d source target')
 
546
        # initial push contains log
 
547
        self.assertContainsRe(out, 'rev1')
 
548
        tree.commit('rev2')
 
549
        out, err = self.run_bzr('push -v -d source target')
 
550
        # subsequent push contains log
 
551
        self.assertContainsRe(out, 'rev2')
 
552
        # subsequent log is accurate
 
553
        self.assertNotContainsRe(out, 'rev1')
 
554
 
 
555
    def test_push_from_subdir(self):
 
556
        t = self.make_branch_and_tree('tree')
 
557
        self.build_tree(['tree/dir/', 'tree/dir/file'])
 
558
        t.add('dir', 'dir/file')
 
559
        t.commit('r1')
 
560
        out, err = self.run_bzr('push ../../pushloc', working_dir='tree/dir')
 
561
        self.assertEqual('', out)
 
562
        self.assertEqual('Created new branch.\n', err)
 
563
 
 
564
 
 
565
class RedirectingMemoryTransport(memory.MemoryTransport):
 
566
 
 
567
    def mkdir(self, relpath, mode=None):
 
568
        if self._cwd == '/source/':
 
569
            raise errors.RedirectRequested(self.abspath(relpath),
 
570
                                           self.abspath('../target'),
 
571
                                           is_permanent=True)
 
572
        elif self._cwd == '/infinite-loop/':
 
573
            raise errors.RedirectRequested(self.abspath(relpath),
 
574
                                           self.abspath('../infinite-loop'),
 
575
                                           is_permanent=True)
 
576
        else:
 
577
            return super(RedirectingMemoryTransport, self).mkdir(
 
578
                relpath, mode)
 
579
 
 
580
    def get(self, relpath):
 
581
        if self.clone(relpath)._cwd == '/infinite-loop/':
 
582
            raise errors.RedirectRequested(self.abspath(relpath),
 
583
                                           self.abspath('../infinite-loop'),
 
584
                                           is_permanent=True)
 
585
        else:
 
586
            return super(RedirectingMemoryTransport, self).get(relpath)
 
587
 
 
588
    def _redirected_to(self, source, target):
 
589
        # We do accept redirections
 
590
        return transport.get_transport(target)
 
591
 
 
592
 
 
593
class RedirectingMemoryServer(memory.MemoryServer):
 
594
 
 
595
    def start_server(self):
 
596
        self._dirs = {'/': None}
 
597
        self._files = {}
 
598
        self._locks = {}
 
599
        self._scheme = 'redirecting-memory+%s:///' % id(self)
 
600
        transport.register_transport(self._scheme, self._memory_factory)
 
601
 
 
602
    def _memory_factory(self, url):
 
603
        result = RedirectingMemoryTransport(url)
 
604
        result._dirs = self._dirs
 
605
        result._files = self._files
 
606
        result._locks = self._locks
 
607
        return result
 
608
 
 
609
    def stop_server(self):
 
610
        transport.unregister_transport(self._scheme, self._memory_factory)
 
611
 
 
612
 
 
613
class TestPushRedirect(tests.TestCaseWithTransport):
 
614
 
 
615
    def setUp(self):
 
616
        tests.TestCaseWithTransport.setUp(self)
 
617
        self.memory_server = RedirectingMemoryServer()
 
618
        self.start_server(self.memory_server)
 
619
        # Make the branch and tree that we'll be pushing.
 
620
        t = self.make_branch_and_tree('tree')
 
621
        self.build_tree(['tree/file'])
 
622
        t.add('file')
 
623
        t.commit('commit 1')
 
624
 
 
625
    def test_push_redirects_on_mkdir(self):
 
626
        """If the push requires a mkdir, push respects redirect requests.
 
627
 
 
628
        This is added primarily to handle lp:/ URI support, so that users can
 
629
        push to new branches by specifying lp:/ URIs.
 
630
        """
 
631
        destination_url = self.memory_server.get_url() + 'source'
 
632
        self.run_bzr(['push', '-d', 'tree', destination_url])
 
633
 
 
634
        local_revision = branch.Branch.open('tree').last_revision()
 
635
        remote_revision = branch.Branch.open(
 
636
            self.memory_server.get_url() + 'target').last_revision()
 
637
        self.assertEqual(remote_revision, local_revision)
 
638
 
 
639
    def test_push_gracefully_handles_too_many_redirects(self):
 
640
        """Push fails gracefully if the mkdir generates a large number of
 
641
        redirects.
 
642
        """
 
643
        destination_url = self.memory_server.get_url() + 'infinite-loop'
 
644
        out, err = self.run_bzr_error(
 
645
            ['Too many redirections trying to make %s\\.\n'
 
646
             % re.escape(destination_url)],
 
647
            ['push', '-d', 'tree', destination_url], retcode=3)
 
648
        self.assertEqual('', out)
 
649
 
 
650
 
 
651
class TestPushStrictMixin(object):
 
652
 
 
653
    def make_local_branch_and_tree(self):
 
654
        self.tree = self.make_branch_and_tree('local')
 
655
        self.build_tree_contents([('local/file', 'initial')])
 
656
        self.tree.add('file')
 
657
        self.tree.commit('adding file', rev_id='added')
 
658
        self.build_tree_contents([('local/file', 'modified')])
 
659
        self.tree.commit('modify file', rev_id='modified')
 
660
 
 
661
    def set_config_push_strict(self, value):
 
662
        # set config var (any of bazaar.conf, locations.conf, branch.conf
 
663
        # should do)
 
664
        conf = self.tree.branch.get_config()
 
665
        conf.set_user_option('push_strict', value)
 
666
 
 
667
    _default_command = ['push', '../to']
 
668
    _default_wd = 'local'
 
669
    _default_errors = ['Working tree ".*/local/" has uncommitted '
 
670
                       'changes \(See bzr status\)\.',]
 
671
    _default_additional_error = 'Use --no-strict to force the push.\n'
 
672
    _default_additional_warning = 'Uncommitted changes will not be pushed.'
 
673
 
 
674
 
 
675
    def assertPushFails(self, args):
 
676
        out, err = self.run_bzr_error(self._default_errors,
 
677
                                      self._default_command + args,
 
678
                                      working_dir=self._default_wd, retcode=3)
 
679
        self.assertContainsRe(err, self._default_additional_error)
 
680
 
 
681
    def assertPushSucceeds(self, args, with_warning=False, revid_to_push=None):
 
682
        if with_warning:
 
683
            error_regexes = self._default_errors
 
684
        else:
 
685
            error_regexes = []
 
686
        out, err = self.run_bzr(self._default_command + args,
 
687
                                working_dir=self._default_wd,
 
688
                                error_regexes=error_regexes)
 
689
        if with_warning:
 
690
            self.assertContainsRe(err, self._default_additional_warning)
 
691
        else:
 
692
            self.assertNotContainsRe(err, self._default_additional_warning)
 
693
        branch_from = branch.Branch.open(self._default_wd)
 
694
        if revid_to_push is None:
 
695
            revid_to_push = branch_from.last_revision()
 
696
        branch_to = branch.Branch.open('to')
 
697
        repo_to = branch_to.repository
 
698
        self.assertTrue(repo_to.has_revision(revid_to_push))
 
699
        self.assertEqual(revid_to_push, branch_to.last_revision())
 
700
 
 
701
 
 
702
 
 
703
class TestPushStrictWithoutChanges(tests.TestCaseWithTransport,
 
704
                                   TestPushStrictMixin):
 
705
 
 
706
    def setUp(self):
 
707
        super(TestPushStrictWithoutChanges, self).setUp()
 
708
        self.make_local_branch_and_tree()
 
709
 
 
710
    def test_push_default(self):
 
711
        self.assertPushSucceeds([])
 
712
 
 
713
    def test_push_strict(self):
 
714
        self.assertPushSucceeds(['--strict'])
 
715
 
 
716
    def test_push_no_strict(self):
 
717
        self.assertPushSucceeds(['--no-strict'])
 
718
 
 
719
    def test_push_config_var_strict(self):
 
720
        self.set_config_push_strict('true')
 
721
        self.assertPushSucceeds([])
 
722
 
 
723
    def test_push_config_var_no_strict(self):
 
724
        self.set_config_push_strict('false')
 
725
        self.assertPushSucceeds([])
 
726
 
 
727
 
 
728
strict_push_change_scenarios = [
 
729
    ('uncommitted',
 
730
        dict(_changes_type= '_uncommitted_changes')),
 
731
    ('pending-merges',
 
732
        dict(_changes_type= '_pending_merges')),
 
733
    ('out-of-sync-trees',
 
734
        dict(_changes_type= '_out_of_sync_trees')),
 
735
    ]
 
736
 
 
737
 
 
738
class TestPushStrictWithChanges(tests.TestCaseWithTransport,
 
739
                                TestPushStrictMixin):
 
740
 
 
741
    scenarios = strict_push_change_scenarios 
 
742
    _changes_type = None # Set by load_tests
 
743
 
 
744
    def setUp(self):
 
745
        super(TestPushStrictWithChanges, self).setUp()
 
746
        # Apply the changes defined in load_tests: one of _uncommitted_changes,
 
747
        # _pending_merges or _out_of_sync_trees
 
748
        getattr(self, self._changes_type)()
 
749
 
 
750
    def _uncommitted_changes(self):
 
751
        self.make_local_branch_and_tree()
 
752
        # Make a change without committing it
 
753
        self.build_tree_contents([('local/file', 'in progress')])
 
754
 
 
755
    def _pending_merges(self):
 
756
        self.make_local_branch_and_tree()
 
757
        # Create 'other' branch containing a new file
 
758
        other_bzrdir = self.tree.bzrdir.sprout('other')
 
759
        other_tree = other_bzrdir.open_workingtree()
 
760
        self.build_tree_contents([('other/other-file', 'other')])
 
761
        other_tree.add('other-file')
 
762
        other_tree.commit('other commit', rev_id='other')
 
763
        # Merge and revert, leaving a pending merge
 
764
        self.tree.merge_from_branch(other_tree.branch)
 
765
        self.tree.revert(filenames=['other-file'], backups=False)
 
766
 
 
767
    def _out_of_sync_trees(self):
 
768
        self.make_local_branch_and_tree()
 
769
        self.run_bzr(['checkout', '--lightweight', 'local', 'checkout'])
 
770
        # Make a change and commit it
 
771
        self.build_tree_contents([('local/file', 'modified in local')])
 
772
        self.tree.commit('modify file', rev_id='modified-in-local')
 
773
        # Exercise commands from the checkout directory
 
774
        self._default_wd = 'checkout'
 
775
        self._default_errors = ["Working tree is out of date, please run"
 
776
                                " 'bzr update'\.",]
 
777
 
 
778
    def test_push_default(self):
 
779
        self.assertPushSucceeds([], with_warning=True)
 
780
 
 
781
    def test_push_with_revision(self):
 
782
        self.assertPushSucceeds(['-r', 'revid:added'], revid_to_push='added')
 
783
 
 
784
    def test_push_no_strict(self):
 
785
        self.assertPushSucceeds(['--no-strict'])
 
786
 
 
787
    def test_push_strict_with_changes(self):
 
788
        self.assertPushFails(['--strict'])
 
789
 
 
790
    def test_push_respect_config_var_strict(self):
 
791
        self.set_config_push_strict('true')
 
792
        self.assertPushFails([])
 
793
 
 
794
    def test_push_bogus_config_var_ignored(self):
 
795
        self.set_config_push_strict("I don't want you to be strict")
 
796
        self.assertPushSucceeds([], with_warning=True)
 
797
 
 
798
    def test_push_no_strict_command_line_override_config(self):
 
799
        self.set_config_push_strict('yES')
 
800
        self.assertPushFails([])
 
801
        self.assertPushSucceeds(['--no-strict'])
 
802
 
 
803
    def test_push_strict_command_line_override_config(self):
 
804
        self.set_config_push_strict('oFF')
 
805
        self.assertPushFails(['--strict'])
 
806
        self.assertPushSucceeds([])
 
807
 
 
808
 
 
809
class TestPushForeign(tests.TestCaseWithTransport):
 
810
 
 
811
    def setUp(self):
 
812
        super(TestPushForeign, self).setUp()
 
813
        test_foreign.register_dummy_foreign_for_test(self)
 
814
 
 
815
    def make_dummy_builder(self, relpath):
 
816
        builder = self.make_branch_builder(
 
817
            relpath, format=test_foreign.DummyForeignVcsDirFormat())
 
818
        builder.build_snapshot('revid', None,
 
819
            [('add', ('', 'TREE_ROOT', 'directory', None)),
 
820
             ('add', ('foo', 'fooid', 'file', 'bar'))])
 
821
        return builder
 
822
 
 
823
    def test_no_roundtripping(self):
 
824
        target_branch = self.make_dummy_builder('dp').get_branch()
 
825
        source_tree = self.make_branch_and_tree("dc")
 
826
        output, error = self.run_bzr("push -d dc dp", retcode=3)
 
827
        self.assertEquals("", output)
 
828
        self.assertEquals(error, "bzr: ERROR: It is not possible to losslessly"
 
829
            " push to dummy. You may want to use dpush instead.\n")