1
# Copyright (C) 2006-2011 Canonical Ltd
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.
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.
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
18
"""Black-box tests for bzr push."""
33
from bzrlib.repofmt import knitrepo
34
from bzrlib.tests import (
41
from bzrlib.transport import memory
44
load_tests = scenarios.load_tests_apply_scenarios
47
class TestPush(tests.TestCaseWithTransport):
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'],
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'])
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'])
73
tree_a.commit('commit b')
74
self.build_tree(['branch_b/c'])
76
tree_b.commit('commit c')
77
# initial push location must be empty
78
self.assertEqual(None, branch_b.get_push_location())
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'))
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)
90
('', 'bzr: ERROR: No push location known or specified.\n'),
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))
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)
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/')
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'])
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')
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
143
t = self.make_branch_and_tree('tree')
144
self.build_tree(['tree/file'])
147
out, err = self.run_bzr('push -d tree pushed-to')
148
self.assertEqual('', out)
149
self.assertEqual('Created new branch.\n', err)
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'])
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)
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)
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'])
179
tree_a.commit('commit a-1', rev_id='a-1')
180
f = open('repo/a/file', 'ab')
181
f.write('more stuff\n')
183
tree_a.commit('commit a-2', rev_id='a-2')
185
tree_b = make_shared_tree('b')
186
self.build_tree(['repo/b/file'])
188
tree_b.commit('commit b-1', rev_id='b-1')
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'))
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'))
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')
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')
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
234
self.assertEqual([('A',), ('C',)], sorted(target_repo.revisions.keys()))
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)
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')
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)
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')
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)
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(
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())
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())
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')
329
def test_push_create_prefix(self):
330
"""'bzr push --create-prefix' will create leading directories."""
331
tree = self.create_simple_tree()
333
self.run_bzr_error(['Parent directory of ../new/tree does not exist'],
336
self.run_bzr('push ../new/tree --create-prefix',
338
new_tree = workingtree.WorkingTree.open('new/tree')
339
self.assertEqual(tree.last_revision(), new_tree.last_revision())
340
self.assertPathExists('new/tree/a')
342
def test_push_use_existing(self):
343
"""'bzr push --use-existing-dir' can push into an existing dir.
345
By default, 'bzr push' will not use an existing, non-versioned dir.
347
tree = self.create_simple_tree()
348
self.build_tree(['target/'])
350
self.run_bzr_error(['Target directory ../target already exists',
351
'Supply --use-existing-dir',
353
'push ../target', working_dir='tree')
355
self.run_bzr('push --use-existing-dir ../target',
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')
363
def test_push_use_existing_into_empty_bzrdir(self):
364
"""'bzr push --use-existing-dir' into a dir with an empty .bzr dir
367
tree = self.create_simple_tree()
368
self.build_tree(['target/', 'target/.bzr/'])
370
['Target directory ../target already contains a .bzr directory, '
371
'but it is not valid.'],
372
'push ../target --use-existing-dir', working_dir='tree')
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)
379
self.run_bzr('push ../repo',
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
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())
390
def test_push_onto_just_bzrdir(self):
391
"""We don't handle when the target is just a bzrdir.
393
Because you shouldn't be able to create *just* a bzrdir in the wild.
395
# TODO: jam 20070109 Maybe it would be better to create the repository
397
tree = self.create_simple_tree()
398
a_bzrdir = self.make_bzrdir('dir')
400
self.run_bzr_error(['At ../dir you have a valid .bzr control'],
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")
410
self.run_bzr('push -r1 ../to', working_dir='from')
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')
419
['bzr: ERROR: bzr push --revision '
420
'takes exactly one revision identifier\n'],
421
'push -r0..2 ../to', working_dir='from')
423
def create_trunk_and_feature_branch(self):
425
trunk_tree = self.make_branch_and_tree('target',
427
trunk_tree.commit('mainline')
428
# and a branch from it
429
branch_tree = self.make_branch_and_tree('branch',
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
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))
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)
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
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)
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
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)
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('.'))
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 .*')
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())
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)
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.
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).
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
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')
555
def test_push_verbose_shows_log(self):
556
tree = self.make_branch_and_tree('source')
558
out, err = self.run_bzr('push -v -d source target')
559
# initial push contains log
560
self.assertContainsRe(out, 'rev1')
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')
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')
573
out, err = self.run_bzr('push ../../pushloc', working_dir='tree/dir')
574
self.assertEqual('', out)
575
self.assertEqual('Created new branch.\n', err)
578
class RedirectingMemoryTransport(memory.MemoryTransport):
580
def mkdir(self, relpath, mode=None):
581
if self._cwd == '/source/':
582
raise errors.RedirectRequested(self.abspath(relpath),
583
self.abspath('../target'),
585
elif self._cwd == '/infinite-loop/':
586
raise errors.RedirectRequested(self.abspath(relpath),
587
self.abspath('../infinite-loop'),
590
return super(RedirectingMemoryTransport, self).mkdir(
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'),
599
return super(RedirectingMemoryTransport, self).get(relpath)
601
def _redirected_to(self, source, target):
602
# We do accept redirections
603
return transport.get_transport(target)
606
class RedirectingMemoryServer(memory.MemoryServer):
608
def start_server(self):
609
self._dirs = {'/': None}
612
self._scheme = 'redirecting-memory+%s:///' % id(self)
613
transport.register_transport(self._scheme, self._memory_factory)
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
622
def stop_server(self):
623
transport.unregister_transport(self._scheme, self._memory_factory)
626
class TestPushRedirect(tests.TestCaseWithTransport):
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'])
638
def test_push_redirects_on_mkdir(self):
639
"""If the push requires a mkdir, push respects redirect requests.
641
This is added primarily to handle lp:/ URI support, so that users can
642
push to new branches by specifying lp:/ URIs.
644
destination_url = self.memory_server.get_url() + 'source'
645
self.run_bzr(['push', '-d', 'tree', destination_url])
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)
652
def test_push_gracefully_handles_too_many_redirects(self):
653
"""Push fails gracefully if the mkdir generates a large number of
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)
664
class TestPushStrictMixin(object):
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')
674
def set_config_push_strict(self, value):
675
# set config var (any of bazaar.conf, locations.conf, branch.conf
677
conf = self.tree.branch.get_config()
678
conf.set_user_option('push_strict', value)
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.'
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)
694
def assertPushSucceeds(self, args, with_warning=False, revid_to_push=None):
696
error_regexes = self._default_errors
699
out, err = self.run_bzr(self._default_command + args,
700
working_dir=self._default_wd,
701
error_regexes=error_regexes)
703
self.assertContainsRe(err, self._default_additional_warning)
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())
716
class TestPushStrictWithoutChanges(tests.TestCaseWithTransport,
717
TestPushStrictMixin):
720
super(TestPushStrictWithoutChanges, self).setUp()
721
self.make_local_branch_and_tree()
723
def test_push_default(self):
724
self.assertPushSucceeds([])
726
def test_push_strict(self):
727
self.assertPushSucceeds(['--strict'])
729
def test_push_no_strict(self):
730
self.assertPushSucceeds(['--no-strict'])
732
def test_push_config_var_strict(self):
733
self.set_config_push_strict('true')
734
self.assertPushSucceeds([])
736
def test_push_config_var_no_strict(self):
737
self.set_config_push_strict('false')
738
self.assertPushSucceeds([])
741
strict_push_change_scenarios = [
743
dict(_changes_type= '_uncommitted_changes')),
745
dict(_changes_type= '_pending_merges')),
746
('out-of-sync-trees',
747
dict(_changes_type= '_out_of_sync_trees')),
751
class TestPushStrictWithChanges(tests.TestCaseWithTransport,
752
TestPushStrictMixin):
754
scenarios = strict_push_change_scenarios
755
_changes_type = None # Set by load_tests
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)()
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')])
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)
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"
791
def test_push_default(self):
792
self.assertPushSucceeds([], with_warning=True)
794
def test_push_with_revision(self):
795
self.assertPushSucceeds(['-r', 'revid:added'], revid_to_push='added')
797
def test_push_no_strict(self):
798
self.assertPushSucceeds(['--no-strict'])
800
def test_push_strict_with_changes(self):
801
self.assertPushFails(['--strict'])
803
def test_push_respect_config_var_strict(self):
804
self.set_config_push_strict('true')
805
self.assertPushFails([])
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)
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'])
816
def test_push_strict_command_line_override_config(self):
817
self.set_config_push_strict('oFF')
818
self.assertPushFails(['--strict'])
819
self.assertPushSucceeds([])
822
class TestPushForeign(tests.TestCaseWithTransport):
825
super(TestPushForeign, self).setUp()
826
test_foreign.register_dummy_foreign_for_test(self)
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'))])
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")