~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-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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