~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

[merge] Storage filename escaping

Show diffs side-by-side

added added

removed removed

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