13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
"""Black-box tests for bzr push."""
23
22
from bzrlib import (
27
from bzrlib.branch import Branch
28
from bzrlib.bzrdir import BzrDirMetaFormat1
29
from bzrlib.osutils import abspath
30
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
31
from bzrlib.tests.blackbox import ExternalBase
32
from bzrlib.tests.http_server import HttpServer
33
from bzrlib.transport import register_transport, unregister_transport
34
from bzrlib.transport.memory import MemoryServer, MemoryTransport
35
from bzrlib.uncommit import uncommit
36
from bzrlib.urlutils import local_path_from_url
37
from bzrlib.workingtree import WorkingTree
40
class TestPush(ExternalBase):
33
from bzrlib.repofmt import knitrepo
34
from bzrlib.tests import http_server
35
from bzrlib.transport import memory
38
def load_tests(standard_tests, module, loader):
39
"""Multiply tests for the push command."""
40
result = loader.suiteClass()
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,
49
dict(_changes_type= '_uncommitted_changes')),
51
dict(_changes_type= '_pending_merges')),
53
dict(_changes_type= '_out_of_sync_trees')),
55
tests.multiply_tests(changes_tests, changes_scenarios, result)
56
# No parametrization for the remaining tests
57
result.addTests(remaining_tests)
62
class TestPush(tests.TestCaseWithTransport):
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'],
42
74
def test_push_remember(self):
43
75
"""Push changes from one branch to another and test push location."""
61
93
self.assertEqual(None, branch_b.get_push_location())
63
95
# test push for failure without push location set
65
out = self.run_bzr('push', retcode=3)
96
out = self.run_bzr('push', working_dir='branch_a', retcode=3)
66
97
self.assertEquals(out,
67
98
('','bzr: ERROR: No push location known or specified.\n'))
69
100
# test not remembered if cannot actually push
70
self.run_bzr('push ../path/which/doesnt/exist', retcode=3)
71
out = self.run_bzr('push', retcode=3)
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)
72
104
self.assertEquals(
73
105
('', 'bzr: ERROR: No push location known or specified.\n'),
76
108
# test implicit --remember when no push location set, push fails
77
out = self.run_bzr('push ../branch_b', retcode=3)
109
out = self.run_bzr('push ../branch_b',
110
working_dir='branch_a', retcode=3)
78
111
self.assertEquals(out,
79
112
('','bzr: ERROR: These branches have diverged. '
80
'Try using "merge" and then "push".\n'))
81
self.assertEquals(abspath(branch_a.get_push_location()),
82
abspath(branch_b.bzrdir.root_transport.base))
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))
84
117
# test implicit --remember after resolving previous failure
85
uncommit(branch=branch_b, tree=tree_b)
118
uncommit.uncommit(branch=branch_b, tree=tree_b)
86
119
transport.delete('branch_b/c')
87
out, err = self.run_bzr('push')
120
out, err = self.run_bzr('push', working_dir='branch_a')
88
121
path = branch_a.get_push_location()
89
122
self.assertEquals(out,
90
'Using saved location: %s\n'
91
'Pushed up to revision 2.\n'
92
% local_path_from_url(path))
123
'Using saved push location: %s\n'
124
% urlutils.local_path_from_url(path))
93
125
self.assertEqual(err,
94
'All changes applied successfully.\n')
126
'All changes applied successfully.\n'
127
'Pushed up to revision 2.\n')
95
128
self.assertEqual(path,
96
129
branch_b.bzrdir.root_transport.base)
97
130
# test explicit --remember
98
self.run_bzr('push ../branch_c --remember')
131
self.run_bzr('push ../branch_c --remember', working_dir='branch_a')
99
132
self.assertEquals(branch_a.get_push_location(),
100
133
branch_c.bzrdir.root_transport.base)
102
135
def test_push_without_tree(self):
103
136
# bzr push from a branch that does not have a checkout should work.
104
137
b = self.make_branch('.')
105
138
out, err = self.run_bzr('push pushed-location')
106
139
self.assertEqual('', out)
107
140
self.assertEqual('Created new branch.\n', err)
108
b2 = Branch.open('pushed-location')
141
b2 = branch.Branch.open('pushed-location')
109
142
self.assertEndsWith(b2.base, 'pushed-location/')
111
144
def test_push_new_branch_revision_count(self):
112
# bzr push of a branch with revisions to a new location
113
# should print the number of revisions equal to the length of the
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
115
148
t = self.make_branch_and_tree('tree')
116
149
self.build_tree(['tree/file'])
118
151
t.commit('commit 1')
120
out, err = self.run_bzr('push pushed-to')
152
out, err = self.run_bzr('push -d tree pushed-to')
122
153
self.assertEqual('', out)
123
154
self.assertEqual('Created new branch.\n', err)
125
156
def test_push_only_pushes_history(self):
126
157
# Knit branches should only push the history for the current revision.
127
format = BzrDirMetaFormat1()
128
format.repository_format = RepositoryFormatKnit1()
158
format = bzrdir.BzrDirMetaFormat1()
159
format.repository_format = knitrepo.RepositoryFormatKnit1()
129
160
shared_repo = self.make_repository('repo', format=format, shared=True)
130
161
shared_repo.set_make_working_trees(True)
132
163
def make_shared_tree(path):
133
164
shared_repo.bzrdir.root_transport.mkdir(path)
134
165
shared_repo.bzrdir.create_branch_convenience('repo/' + path)
135
return WorkingTree.open('repo/' + path)
166
return workingtree.WorkingTree.open('repo/' + path)
136
167
tree_a = make_shared_tree('a')
137
168
self.build_tree(['repo/a/file'])
138
169
tree_a.add('file')
175
204
message='first commit')
176
205
self.run_bzr('push -d from to-one')
177
206
self.failUnlessExists('to-one')
178
self.run_bzr('push -d %s %s'
207
self.run_bzr('push -d %s %s'
179
208
% tuple(map(urlutils.local_path_to_url, ['from', 'to-two'])))
180
209
self.failUnlessExists('to-two')
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)
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')
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)
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(
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())
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())
182
281
def create_simple_tree(self):
183
282
tree = self.make_branch_and_tree('tree')
184
283
self.build_tree(['tree/a'])
284
396
def assertPublished(self, branch_revid, stacked_on):
285
397
"""Assert that the branch 'published' has been published correctly."""
286
published_branch = Branch.open('published')
398
published_branch = branch.Branch.open('published')
287
399
# The published branch refers to the mainline
288
self.assertEqual(stacked_on, published_branch.get_stacked_on())
400
self.assertEqual(stacked_on, published_branch.get_stacked_on_url())
289
401
# and the branch's work was pushed
290
402
self.assertTrue(published_branch.repository.has_revision(branch_revid))
292
def test_push_new_branch_reference(self):
293
"""Pushing a new branch with --reference creates a stacked branch."""
404
def test_push_new_branch_stacked_on(self):
405
"""Pushing a new branch with --stacked-on creates a stacked branch."""
294
406
trunk_tree, branch_tree = self.create_trunk_and_feature_branch()
295
407
# we publish branch_tree with a reference to the mainline.
296
out, err = self.run_bzr(['push', '--reference', trunk_tree.branch.base,
408
out, err = self.run_bzr(['push', '--stacked-on', trunk_tree.branch.base,
297
409
self.get_url('published')], working_dir='branch')
298
410
self.assertEqual('', out)
299
411
self.assertEqual('Created new stacked branch referring to %s.\n' %
342
455
self.assertEqual('', out)
343
456
self.assertFalse(self.get_transport('published').has('.'))
346
class RedirectingMemoryTransport(MemoryTransport):
348
def mkdir(self, path, mode=None):
349
path = self.abspath(path)[len(self._scheme):]
350
if path == '/source':
351
raise errors.RedirectRequested(
352
path, self._scheme + '/target', is_permanent=True)
353
elif path == '/infinite-loop':
354
raise errors.RedirectRequested(
355
path, self._scheme + '/infinite-loop', is_permanent=True)
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 .*')
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())
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)
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.
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).
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
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')
514
def test_push_verbose_shows_log(self):
515
tree = self.make_branch_and_tree('source')
517
out, err = self.run_bzr('push -v -d source target')
518
# initial push contains log
519
self.assertContainsRe(out, 'rev1')
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')
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')
532
out, err = self.run_bzr('push ../../pushloc', working_dir='tree/dir')
533
self.assertEqual('', out)
534
self.assertEqual('Created new branch.\n', err)
537
class RedirectingMemoryTransport(memory.MemoryTransport):
539
def mkdir(self, relpath, mode=None):
540
if self._cwd == '/source/':
541
raise errors.RedirectRequested(self.abspath(relpath),
542
self.abspath('../target'),
544
elif self._cwd == '/infinite-loop/':
545
raise errors.RedirectRequested(self.abspath(relpath),
546
self.abspath('../infinite-loop'),
357
549
return super(RedirectingMemoryTransport, self).mkdir(
361
class RedirectingMemoryServer(MemoryServer):
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'),
558
return super(RedirectingMemoryTransport, self).get(relpath)
560
def _redirected_to(self, source, target):
561
# We do accept redirections
562
return transport.get_transport(target)
565
class RedirectingMemoryServer(memory.MemoryServer):
364
568
self._dirs = {'/': None}
367
571
self._scheme = 'redirecting-memory+%s:///' % id(self)
368
register_transport(self._scheme, self._memory_factory)
572
transport.register_transport(self._scheme, self._memory_factory)
370
574
def _memory_factory(self, url):
371
575
result = RedirectingMemoryTransport(url)
412
614
"""Push fails gracefully if the mkdir generates a large number of
416
617
destination_url = self.memory_server.get_url() + 'infinite-loop'
417
618
out, err = self.run_bzr_error(
418
619
['Too many redirections trying to make %s\\.\n'
419
620
% re.escape(destination_url)],
420
'push %s' % destination_url, retcode=3)
621
['push', '-d', 'tree', destination_url], retcode=3)
422
622
self.assertEqual('', out)
625
class TestPushStrictMixin(object):
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')
635
def set_config_push_strict(self, value):
636
# set config var (any of bazaar.conf, locations.conf, branch.conf
638
conf = self.tree.branch.get_config()
639
conf.set_user_option('push_strict', value)
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'
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)
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)
663
class TestPushStrictWithoutChanges(tests.TestCaseWithTransport,
664
TestPushStrictMixin):
667
super(TestPushStrictWithoutChanges, self).setUp()
668
self.make_local_branch_and_tree()
670
def test_push_default(self):
671
self.assertPushSucceeds([])
673
def test_push_strict(self):
674
self.assertPushSucceeds(['--strict'])
676
def test_push_no_strict(self):
677
self.assertPushSucceeds(['--no-strict'])
679
def test_push_config_var_strict(self):
680
self.set_config_push_strict('true')
681
self.assertPushSucceeds([])
683
def test_push_config_var_no_strict(self):
684
self.set_config_push_strict('false')
685
self.assertPushSucceeds([])
688
class TestPushStrictWithChanges(tests.TestCaseWithTransport,
689
TestPushStrictMixin):
691
_changes_type = None # Set by load_tests
694
super(TestPushStrictWithChanges, self).setUp()
695
getattr(self, self._changes_type)()
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')])
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)
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"
724
self._default_pushed_revid = 'modified-in-local'
726
def test_push_default(self):
727
self.assertPushFails([])
729
def test_push_with_revision(self):
730
self.assertPushSucceeds(['-r', 'revid:added'], pushed_revid='added')
732
def test_push_no_strict(self):
733
self.assertPushSucceeds(['--no-strict'])
735
def test_push_strict_with_changes(self):
736
self.assertPushFails(['--strict'])
738
def test_push_respect_config_var_strict(self):
739
self.set_config_push_strict('true')
740
self.assertPushFails([])
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([])
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'])
751
def test_push_strict_command_line_override_config(self):
752
self.set_config_push_strict('oFF')
753
self.assertPushFails(['--strict'])
754
self.assertPushSucceeds([])