~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Robert Collins
  • Date: 2005-10-18 12:53:07 UTC
  • mfrom: (1442.1.70)
  • Revision ID: robertc@robertcollins.net-20051018125307-730e47c1a034cb6f
Merge in various improvements to pull, testing and configuration.

* 'bzr pull' now accepts '--clobber' which will discard local changes
  and make this branch identical to the source branch. (Robert Collins)

* There is a new method for TestCaseInTempDir, assertFileEqual, which
  will check that a given content is equal to the content of the named
  file. (Robert Collins)

* 'pull' has been factored out of the command as WorkingTree.pull().
  A new option to WorkingTree.pull has been added, clobber, which will
  ignore diverged history and pull anyway.
  (Robert Collins)

* config.Config has a 'get_user_option' call that accepts an option name.
  This will be looked up in branches.conf and bazaar.conf as normal.
  It is intended that this be used by plugins to support options -
  options of built in programs should have specific methods on the config.
  (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
18
 
"""Black-box tests for bzr push."""
19
 
 
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')
58
 
 
59
 
    def test_push_remember(self):
60
 
        """Push changes from one branch to another and test push location."""
61
 
        transport = self.get_transport()
62
 
        tree_a = self.make_branch_and_tree('branch_a')
63
 
        branch_a = tree_a.branch
64
 
        self.build_tree(['branch_a/a'])
65
 
        tree_a.add('a')
66
 
        tree_a.commit('commit a')
67
 
        tree_b = branch_a.bzrdir.sprout('branch_b').open_workingtree()
68
 
        branch_b = tree_b.branch
69
 
        tree_c = branch_a.bzrdir.sprout('branch_c').open_workingtree()
70
 
        branch_c = tree_c.branch
71
 
        self.build_tree(['branch_a/b'])
72
 
        tree_a.add('b')
73
 
        tree_a.commit('commit b')
74
 
        self.build_tree(['branch_b/c'])
75
 
        tree_b.add('c')
76
 
        tree_b.commit('commit c')
77
 
        # initial push location must be empty
78
 
        self.assertEqual(None, branch_b.get_push_location())
79
 
 
80
 
        # test push for failure without push location set
81
 
        out = self.run_bzr('push', working_dir='branch_a', retcode=3)
82
 
        self.assertEquals(out,
83
 
                ('','bzr: ERROR: No push location known or specified.\n'))
84
 
 
85
 
        # test not remembered if cannot actually push
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)
89
 
        self.assertEquals(
90
 
                ('', 'bzr: ERROR: No push location known or specified.\n'),
91
 
                out)
92
 
 
93
 
        # test implicit --remember when no push location set, push fails
94
 
        out = self.run_bzr('push ../branch_b',
95
 
                           working_dir='branch_a', retcode=3)
96
 
        self.assertEquals(out,
97
 
                ('','bzr: ERROR: These branches have diverged.  '
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))
101
 
 
102
 
        # test implicit --remember after resolving previous failure
103
 
        uncommit.uncommit(branch=branch_b, tree=tree_b)
104
 
        transport.delete('branch_b/c')
105
 
        out, err = self.run_bzr('push', working_dir='branch_a')
106
 
        path = branch_a.get_push_location()
107
 
        self.assertEqual(err,
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))
112
 
        self.assertEqual(path,
113
 
                         branch_b.bzrdir.root_transport.base)
114
 
        # test explicit --remember
115
 
        self.run_bzr('push ../branch_c --remember', working_dir='branch_a')
116
 
        self.assertEquals(branch_a.get_push_location(),
117
 
                          branch_c.bzrdir.root_transport.base)
118
 
 
119
 
    def test_push_without_tree(self):
120
 
        # bzr push from a branch that does not have a checkout should work.
121
 
        b = self.make_branch('.')
122
 
        out, err = self.run_bzr('push pushed-location')
123
 
        self.assertEqual('', out)
124
 
        self.assertEqual('Created new branch.\n', err)
125
 
        b2 = branch.Branch.open('pushed-location')
126
 
        self.assertEndsWith(b2.base, 'pushed-location/')
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
 
 
139
 
    def test_push_new_branch_revision_count(self):
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
142
 
        # local branch.
143
 
        t = self.make_branch_and_tree('tree')
144
 
        self.build_tree(['tree/file'])
145
 
        t.add('file')
146
 
        t.commit('commit 1')
147
 
        out, err = self.run_bzr('push -d tree pushed-to')
148
 
        self.assertEqual('', out)
149
 
        self.assertEqual('Created new branch.\n', err)
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
 
 
165
 
    def test_push_only_pushes_history(self):
166
 
        # Knit branches should only push the history for the current revision.
167
 
        format = bzrdir.BzrDirMetaFormat1()
168
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
169
 
        shared_repo = self.make_repository('repo', format=format, shared=True)
170
 
        shared_repo.set_make_working_trees(True)
171
 
 
172
 
        def make_shared_tree(path):
173
 
            shared_repo.bzrdir.root_transport.mkdir(path)
174
 
            shared_repo.bzrdir.create_branch_convenience('repo/' + path)
175
 
            return workingtree.WorkingTree.open('repo/' + path)
176
 
        tree_a = make_shared_tree('a')
177
 
        self.build_tree(['repo/a/file'])
178
 
        tree_a.add('file')
179
 
        tree_a.commit('commit a-1', rev_id='a-1')
180
 
        f = open('repo/a/file', 'ab')
181
 
        f.write('more stuff\n')
182
 
        f.close()
183
 
        tree_a.commit('commit a-2', rev_id='a-2')
184
 
 
185
 
        tree_b = make_shared_tree('b')
186
 
        self.build_tree(['repo/b/file'])
187
 
        tree_b.add('file')
188
 
        tree_b.commit('commit b-1', rev_id='b-1')
189
 
 
190
 
        self.assertTrue(shared_repo.has_revision('a-1'))
191
 
        self.assertTrue(shared_repo.has_revision('a-2'))
192
 
        self.assertTrue(shared_repo.has_revision('b-1'))
193
 
 
194
 
        # Now that we have a repository with shared files, make sure
195
 
        # that things aren't copied out by a 'push'
196
 
        self.run_bzr('push ../../push-b', working_dir='repo/b')
197
 
        pushed_tree = workingtree.WorkingTree.open('push-b')
198
 
        pushed_repo = pushed_tree.branch.repository
199
 
        self.assertFalse(pushed_repo.has_revision('a-1'))
200
 
        self.assertFalse(pushed_repo.has_revision('a-2'))
201
 
        self.assertTrue(pushed_repo.has_revision('b-1'))
202
 
 
203
 
    def test_push_funky_id(self):
204
 
        t = self.make_branch_and_tree('tree')
205
 
        self.build_tree(['tree/filename'])
206
 
        t.add('filename', 'funky-chars<>%&;"\'')
207
 
        t.commit('commit filename')
208
 
        self.run_bzr('push -d tree new-tree')
209
 
 
210
 
    def test_push_dash_d(self):
211
 
        t = self.make_branch_and_tree('from')
212
 
        t.commit(allow_pointless=True,
213
 
                message='first commit')
214
 
        self.run_bzr('push -d from to-one')
215
 
        self.assertPathExists('to-one')
216
 
        self.run_bzr('push -d %s %s'
217
 
            % tuple(map(urlutils.local_path_to_url, ['from', '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())
321
 
 
322
 
    def create_simple_tree(self):
323
 
        tree = self.make_branch_and_tree('tree')
324
 
        self.build_tree(['tree/a'])
325
 
        tree.add(['a'], ['a-id'])
326
 
        tree.commit('one', rev_id='r1')
327
 
        return tree
328
 
 
329
 
    def test_push_create_prefix(self):
330
 
        """'bzr push --create-prefix' will create leading directories."""
331
 
        tree = self.create_simple_tree()
332
 
 
333
 
        self.run_bzr_error(['Parent directory of ../new/tree does not exist'],
334
 
                           'push ../new/tree',
335
 
                           working_dir='tree')
336
 
        self.run_bzr('push ../new/tree --create-prefix',
337
 
                     working_dir='tree')
338
 
        new_tree = workingtree.WorkingTree.open('new/tree')
339
 
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
340
 
        self.assertPathExists('new/tree/a')
341
 
 
342
 
    def test_push_use_existing(self):
343
 
        """'bzr push --use-existing-dir' can push into an existing dir.
344
 
 
345
 
        By default, 'bzr push' will not use an existing, non-versioned dir.
346
 
        """
347
 
        tree = self.create_simple_tree()
348
 
        self.build_tree(['target/'])
349
 
 
350
 
        self.run_bzr_error(['Target directory ../target already exists',
351
 
                            'Supply --use-existing-dir',
352
 
                           ],
353
 
                           'push ../target', working_dir='tree')
354
 
 
355
 
        self.run_bzr('push --use-existing-dir ../target',
356
 
                     working_dir='tree')
357
 
 
358
 
        new_tree = workingtree.WorkingTree.open('target')
359
 
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
360
 
        # The push should have created 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')
373
 
 
374
 
    def test_push_onto_repo(self):
375
 
        """We should be able to 'bzr push' into an existing bzrdir."""
376
 
        tree = self.create_simple_tree()
377
 
        repo = self.make_repository('repo', shared=True)
378
 
 
379
 
        self.run_bzr('push ../repo',
380
 
                     working_dir='tree')
381
 
 
382
 
        # Pushing onto an existing bzrdir will create a repository and
383
 
        # branch as needed, but will only create a working tree if there was
384
 
        # no BzrDir before.
385
 
        self.assertRaises(errors.NoWorkingTree,
386
 
                          workingtree.WorkingTree.open, 'repo')
387
 
        new_branch = branch.Branch.open('repo')
388
 
        self.assertEqual(tree.last_revision(), new_branch.last_revision())
389
 
 
390
 
    def test_push_onto_just_bzrdir(self):
391
 
        """We don't handle when the target is just a bzrdir.
392
 
 
393
 
        Because you shouldn't be able to create *just* a bzrdir in the wild.
394
 
        """
395
 
        # TODO: jam 20070109 Maybe it would be better to create the repository
396
 
        #       if at this point
397
 
        tree = self.create_simple_tree()
398
 
        a_bzrdir = self.make_bzrdir('dir')
399
 
 
400
 
        self.run_bzr_error(['At ../dir you have a valid .bzr control'],
401
 
                'push ../dir',
402
 
                working_dir='tree')
403
 
 
404
 
    def test_push_with_revisionspec(self):
405
 
        """We should be able to push a revision older than the tip."""
406
 
        tree_from = self.make_branch_and_tree('from')
407
 
        tree_from.commit("One.", rev_id="from-1")
408
 
        tree_from.commit("Two.", rev_id="from-2")
409
 
 
410
 
        self.run_bzr('push -r1 ../to', working_dir='from')
411
 
 
412
 
        tree_to = workingtree.WorkingTree.open('to')
413
 
        repo_to = tree_to.branch.repository
414
 
        self.assertTrue(repo_to.has_revision('from-1'))
415
 
        self.assertFalse(repo_to.has_revision('from-2'))
416
 
        self.assertEqual(tree_to.branch.last_revision_info()[1], 'from-1')
417
 
 
418
 
        self.run_bzr_error(
419
 
            ['bzr: ERROR: bzr push --revision '
420
 
             'takes exactly one revision identifier\n'],
421
 
            'push -r0..2 ../to', working_dir='from')
422
 
 
423
 
    def create_trunk_and_feature_branch(self):
424
 
        # We have a mainline
425
 
        trunk_tree = self.make_branch_and_tree('target',
426
 
            format='1.9')
427
 
        trunk_tree.commit('mainline')
428
 
        # and a branch from it
429
 
        branch_tree = self.make_branch_and_tree('branch',
430
 
            format='1.9')
431
 
        branch_tree.pull(trunk_tree.branch)
432
 
        branch_tree.branch.set_parent(trunk_tree.branch.base)
433
 
        # with some work on it
434
 
        branch_tree.commit('moar work plz')
435
 
        return trunk_tree, branch_tree
436
 
 
437
 
    def assertPublished(self, branch_revid, stacked_on):
438
 
        """Assert that the branch 'published' has been published correctly."""
439
 
        published_branch = branch.Branch.open('published')
440
 
        # The published branch refers to the mainline
441
 
        self.assertEqual(stacked_on, published_branch.get_stacked_on_url())
442
 
        # and the branch's work was pushed
443
 
        self.assertTrue(published_branch.repository.has_revision(branch_revid))
444
 
 
445
 
    def test_push_new_branch_stacked_on(self):
446
 
        """Pushing a new branch with --stacked-on creates a stacked branch."""
447
 
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
448
 
        # we publish branch_tree with a reference to the mainline.
449
 
        out, err = self.run_bzr(['push', '--stacked-on', trunk_tree.branch.base,
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_when_no_public_url(self):
458
 
        """When the parent has no public url the parent is used as-is."""
459
 
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
460
 
        # now we do a stacked push, which should determine the public location
461
 
        # for us.
462
 
        out, err = self.run_bzr(['push', '--stacked',
463
 
            self.get_url('published')], working_dir='branch')
464
 
        self.assertEqual('', out)
465
 
        self.assertEqual('Created new stacked branch referring to %s.\n' %
466
 
            trunk_tree.branch.base, err)
467
 
        self.assertPublished(branch_tree.last_revision(),
468
 
                             trunk_tree.branch.base)
469
 
 
470
 
    def test_push_new_branch_stacked_uses_parent_public(self):
471
 
        """Pushing a new branch with --stacked creates a stacked branch."""
472
 
        trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
473
 
        # the trunk is published on a web server
474
 
        self.transport_readonly_server = http_server.HttpServer
475
 
        trunk_public = self.make_branch('public_trunk', format='1.9')
476
 
        trunk_public.pull(trunk_tree.branch)
477
 
        trunk_public_url = self.get_readonly_url('public_trunk')
478
 
        trunk_tree.branch.set_public_branch(trunk_public_url)
479
 
        # now we do a stacked push, which should determine the public location
480
 
        # for us.
481
 
        out, err = self.run_bzr(['push', '--stacked',
482
 
            self.get_url('published')], working_dir='branch')
483
 
        self.assertEqual('', out)
484
 
        self.assertEqual('Created new stacked branch referring to %s.\n' %
485
 
            trunk_public_url, err)
486
 
        self.assertPublished(branch_tree.last_revision(), trunk_public_url)
487
 
 
488
 
    def test_push_new_branch_stacked_no_parent(self):
489
 
        """Pushing with --stacked and no parent branch errors."""
490
 
        branch = self.make_branch_and_tree('branch', format='1.9')
491
 
        # now we do a stacked push, which should fail as the place to refer too
492
 
        # cannot be determined.
493
 
        out, err = self.run_bzr_error(
494
 
            ['Could not determine branch to refer to\\.'], ['push', '--stacked',
495
 
            self.get_url('published')], working_dir='branch')
496
 
        self.assertEqual('', out)
497
 
        self.assertFalse(self.get_transport('published').has('.'))
498
 
 
499
 
    def test_push_notifies_default_stacking(self):
500
 
        self.make_branch('stack_on', format='1.6')
501
 
        self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
502
 
        self.make_branch('from', format='1.6')
503
 
        out, err = self.run_bzr('push -d from to')
504
 
        self.assertContainsRe(err,
505
 
                              'Using default stacking branch stack_on at .*')
506
 
 
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)
589
 
        else:
590
 
            return super(RedirectingMemoryTransport, self).mkdir(
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):
609
 
        self._dirs = {'/': None}
610
 
        self._files = {}
611
 
        self._locks = {}
612
 
        self._scheme = 'redirecting-memory+%s:///' % id(self)
613
 
        transport.register_transport(self._scheme, self._memory_factory)
614
 
 
615
 
    def _memory_factory(self, url):
616
 
        result = RedirectingMemoryTransport(url)
617
 
        result._dirs = self._dirs
618
 
        result._files = self._files
619
 
        result._locks = self._locks
620
 
        return result
621
 
 
622
 
    def stop_server(self):
623
 
        transport.unregister_transport(self._scheme, self._memory_factory)
624
 
 
625
 
 
626
 
class TestPushRedirect(tests.TestCaseWithTransport):
627
 
 
628
 
    def setUp(self):
629
 
        tests.TestCaseWithTransport.setUp(self)
630
 
        self.memory_server = RedirectingMemoryServer()
631
 
        self.start_server(self.memory_server)
632
 
        # Make the branch and tree that we'll be pushing.
633
 
        t = self.make_branch_and_tree('tree')
634
 
        self.build_tree(['tree/file'])
635
 
        t.add('file')
636
 
        t.commit('commit 1')
637
 
 
638
 
    def test_push_redirects_on_mkdir(self):
639
 
        """If the push requires a mkdir, push respects redirect requests.
640
 
 
641
 
        This is added primarily to handle lp:/ URI support, so that users can
642
 
        push to new branches by specifying lp:/ URIs.
643
 
        """
644
 
        destination_url = self.memory_server.get_url() + 'source'
645
 
        self.run_bzr(['push', '-d', 'tree', destination_url])
646
 
 
647
 
        local_revision = branch.Branch.open('tree').last_revision()
648
 
        remote_revision = branch.Branch.open(
649
 
            self.memory_server.get_url() + 'target').last_revision()
650
 
        self.assertEqual(remote_revision, local_revision)
651
 
 
652
 
    def test_push_gracefully_handles_too_many_redirects(self):
653
 
        """Push fails gracefully if the mkdir generates a large number of
654
 
        redirects.
655
 
        """
656
 
        destination_url = self.memory_server.get_url() + 'infinite-loop'
657
 
        out, err = self.run_bzr_error(
658
 
            ['Too many redirections trying to make %s\\.\n'
659
 
             % re.escape(destination_url)],
660
 
            ['push', '-d', 'tree', destination_url], retcode=3)
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")