~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Martin Packman
  • Date: 2012-02-27 12:16:28 UTC
  • mto: (6437.23.25 2.5)
  • mto: This revision was merged to the branch mainline in revision 6499.
  • Revision ID: martin.packman@canonical.com-20120227121628-9q8krey0u73v56k3
Get default stream for help output via ui_factory instead of using stdout directly

Show diffs side-by-side

added added

removed removed

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