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 (
40
from bzrlib.transport import memory
43
def load_tests(standard_tests, module, loader):
44
"""Multiply tests for the push command."""
45
result = loader.suiteClass()
47
# one for each king of change
48
changes_tests, remaining_tests = tests.split_suite_by_condition(
49
standard_tests, tests.condition_isinstance((
50
TestPushStrictWithChanges,
54
dict(_changes_type= '_uncommitted_changes')),
56
dict(_changes_type= '_pending_merges')),
58
dict(_changes_type= '_out_of_sync_trees')),
60
tests.multiply_tests(changes_tests, changes_scenarios, result)
61
# No parametrization for the remaining tests
62
result.addTests(remaining_tests)
67
class TestPush(tests.TestCaseWithTransport):
69
def test_push_error_on_vfs_http(self):
70
""" pushing a branch to a HTTP server fails cleanly. """
71
# the trunk is published on a web server
72
self.transport_readonly_server = http_server.HttpServer
73
self.make_branch('source')
74
public_url = self.get_readonly_url('target')
75
self.run_bzr_error(['http does not support mkdir'],
42
79
def test_push_remember(self):
43
80
"""Push changes from one branch to another and test push location."""
61
98
self.assertEqual(None, branch_b.get_push_location())
63
100
# test push for failure without push location set
65
out = self.run_bzr('push', retcode=3)
101
out = self.run_bzr('push', working_dir='branch_a', retcode=3)
66
102
self.assertEquals(out,
67
103
('','bzr: ERROR: No push location known or specified.\n'))
69
105
# 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)
106
self.run_bzr('push path/which/doesnt/exist',
107
working_dir='branch_a', retcode=3)
108
out = self.run_bzr('push', working_dir='branch_a', retcode=3)
72
109
self.assertEquals(
73
110
('', 'bzr: ERROR: No push location known or specified.\n'),
76
113
# test implicit --remember when no push location set, push fails
77
out = self.run_bzr('push ../branch_b', retcode=3)
114
out = self.run_bzr('push ../branch_b',
115
working_dir='branch_a', retcode=3)
78
116
self.assertEquals(out,
79
117
('','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))
118
'See "bzr help diverged-branches" for more information.\n'))
119
self.assertEquals(osutils.abspath(branch_a.get_push_location()),
120
osutils.abspath(branch_b.bzrdir.root_transport.base))
84
122
# test implicit --remember after resolving previous failure
85
uncommit(branch=branch_b, tree=tree_b)
123
uncommit.uncommit(branch=branch_b, tree=tree_b)
86
124
transport.delete('branch_b/c')
87
out, err = self.run_bzr('push')
125
out, err = self.run_bzr('push', working_dir='branch_a')
88
126
path = branch_a.get_push_location()
89
127
self.assertEquals(out,
90
'Using saved push location: %s\n'
91
'Pushed up to revision 2.\n'
92
% local_path_from_url(path))
128
'Using saved push location: %s\n'
129
% urlutils.local_path_from_url(path))
93
130
self.assertEqual(err,
94
'All changes applied successfully.\n')
131
'All changes applied successfully.\n'
132
'Pushed up to revision 2.\n')
95
133
self.assertEqual(path,
96
134
branch_b.bzrdir.root_transport.base)
97
135
# test explicit --remember
98
self.run_bzr('push ../branch_c --remember')
136
self.run_bzr('push ../branch_c --remember', working_dir='branch_a')
99
137
self.assertEquals(branch_a.get_push_location(),
100
138
branch_c.bzrdir.root_transport.base)
102
140
def test_push_without_tree(self):
103
141
# bzr push from a branch that does not have a checkout should work.
104
142
b = self.make_branch('.')
105
143
out, err = self.run_bzr('push pushed-location')
106
144
self.assertEqual('', out)
107
145
self.assertEqual('Created new branch.\n', err)
108
b2 = Branch.open('pushed-location')
146
b2 = branch.Branch.open('pushed-location')
109
147
self.assertEndsWith(b2.base, 'pushed-location/')
111
149
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
150
# bzr push of a branch with revisions to a new location
151
# should print the number of revisions equal to the length of the
115
153
t = self.make_branch_and_tree('tree')
116
154
self.build_tree(['tree/file'])
118
156
t.commit('commit 1')
120
out, err = self.run_bzr('push pushed-to')
157
out, err = self.run_bzr('push -d tree pushed-to')
122
158
self.assertEqual('', out)
123
159
self.assertEqual('Created new branch.\n', err)
125
161
def test_push_only_pushes_history(self):
126
162
# Knit branches should only push the history for the current revision.
127
format = BzrDirMetaFormat1()
128
format.repository_format = RepositoryFormatKnit1()
163
format = bzrdir.BzrDirMetaFormat1()
164
format.repository_format = knitrepo.RepositoryFormatKnit1()
129
165
shared_repo = self.make_repository('repo', format=format, shared=True)
130
166
shared_repo.set_make_working_trees(True)
132
168
def make_shared_tree(path):
133
169
shared_repo.bzrdir.root_transport.mkdir(path)
134
170
shared_repo.bzrdir.create_branch_convenience('repo/' + path)
135
return WorkingTree.open('repo/' + path)
171
return workingtree.WorkingTree.open('repo/' + path)
136
172
tree_a = make_shared_tree('a')
137
173
self.build_tree(['repo/a/file'])
138
174
tree_a.add('file')
175
209
message='first commit')
176
210
self.run_bzr('push -d from to-one')
177
211
self.failUnlessExists('to-one')
178
self.run_bzr('push -d %s %s'
212
self.run_bzr('push -d %s %s'
179
213
% tuple(map(urlutils.local_path_to_url, ['from', 'to-two'])))
180
214
self.failUnlessExists('to-two')
216
def test_push_smart_non_stacked_streaming_acceptance(self):
217
self.setup_smart_server_with_call_log()
218
t = self.make_branch_and_tree('from')
219
t.commit(allow_pointless=True, message='first commit')
220
self.reset_smart_call_log()
221
self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
222
# This figure represent the amount of work to perform this use case. It
223
# is entirely ok to reduce this number if a test fails due to rpc_count
224
# being too low. If rpc_count increases, more network roundtrips have
225
# become necessary for this use case. Please do not adjust this number
226
# upwards without agreement from bzr's network support maintainers.
227
self.assertLength(9, self.hpss_calls)
229
def test_push_smart_stacked_streaming_acceptance(self):
230
self.setup_smart_server_with_call_log()
231
parent = self.make_branch_and_tree('parent', format='1.9')
232
parent.commit(message='first commit')
233
local = parent.bzrdir.sprout('local').open_workingtree()
234
local.commit(message='local commit')
235
self.reset_smart_call_log()
236
self.run_bzr(['push', '--stacked', '--stacked-on', '../parent',
237
self.get_url('public')], working_dir='local')
238
# This figure represent the amount of work to perform this use case. It
239
# is entirely ok to reduce this number if a test fails due to rpc_count
240
# being too low. If rpc_count increases, more network roundtrips have
241
# become necessary for this use case. Please do not adjust this number
242
# upwards without agreement from bzr's network support maintainers.
243
self.assertLength(14, self.hpss_calls)
244
remote = branch.Branch.open('public')
245
self.assertEndsWith(remote.get_stacked_on_url(), '/parent')
247
def test_push_smart_tags_streaming_acceptance(self):
248
self.setup_smart_server_with_call_log()
249
t = self.make_branch_and_tree('from')
250
rev_id = t.commit(allow_pointless=True, message='first commit')
251
t.branch.tags.set_tag('new-tag', rev_id)
252
self.reset_smart_call_log()
253
self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
254
# This figure represent the amount of work to perform this use case. It
255
# is entirely ok to reduce this number if a test fails due to rpc_count
256
# being too low. If rpc_count increases, more network roundtrips have
257
# become necessary for this use case. Please do not adjust this number
258
# upwards without agreement from bzr's network support maintainers.
259
self.assertLength(11, self.hpss_calls)
261
def test_push_smart_incremental_acceptance(self):
262
self.setup_smart_server_with_call_log()
263
t = self.make_branch_and_tree('from')
264
rev_id1 = t.commit(allow_pointless=True, message='first commit')
265
rev_id2 = t.commit(allow_pointless=True, message='second commit')
267
['push', self.get_url('to-one'), '-r1'], working_dir='from')
268
self.reset_smart_call_log()
269
self.run_bzr(['push', self.get_url('to-one')], working_dir='from')
270
# This figure represent the amount of work to perform this use case. It
271
# is entirely ok to reduce this number if a test fails due to rpc_count
272
# being too low. If rpc_count increases, more network roundtrips have
273
# become necessary for this use case. Please do not adjust this number
274
# upwards without agreement from bzr's network support maintainers.
275
self.assertLength(11, self.hpss_calls)
277
def test_push_smart_with_default_stacking_url_path_segment(self):
278
# If the default stacked-on location is a path element then branches
279
# we push there over the smart server are stacked and their
280
# stacked_on_url is that exact path segment. Added to nail bug 385132.
281
self.setup_smart_server_with_call_log()
282
self.make_branch('stack-on', format='1.9')
283
self.make_bzrdir('.').get_config().set_default_stack_on(
285
self.make_branch('from', format='1.9')
286
out, err = self.run_bzr(['push', '-d', 'from', self.get_url('to')])
287
b = branch.Branch.open(self.get_url('to'))
288
self.assertEqual('/extra/stack-on', b.get_stacked_on_url())
290
def test_push_smart_with_default_stacking_relative_path(self):
291
# If the default stacked-on location is a relative path then branches
292
# we push there over the smart server are stacked and their
293
# stacked_on_url is a relative path. Added to nail bug 385132.
294
self.setup_smart_server_with_call_log()
295
self.make_branch('stack-on', format='1.9')
296
self.make_bzrdir('.').get_config().set_default_stack_on('stack-on')
297
self.make_branch('from', format='1.9')
298
out, err = self.run_bzr(['push', '-d', 'from', self.get_url('to')])
299
b = branch.Branch.open(self.get_url('to'))
300
self.assertEqual('../stack-on', b.get_stacked_on_url())
182
302
def create_simple_tree(self):
183
303
tree = self.make_branch_and_tree('tree')
184
304
self.build_tree(['tree/a'])
343
477
self.assertFalse(self.get_transport('published').has('.'))
345
479
def test_push_notifies_default_stacking(self):
346
self.make_branch('stack_on', format='development1')
480
self.make_branch('stack_on', format='1.6')
347
481
self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
348
self.make_branch('from', format='development1')
482
self.make_branch('from', format='1.6')
349
483
out, err = self.run_bzr('push -d from to')
350
484
self.assertContainsRe(err,
351
485
'Using default stacking branch stack_on at .*')
354
class RedirectingMemoryTransport(MemoryTransport):
356
def mkdir(self, path, mode=None):
357
path = self.abspath(path)[len(self._scheme):]
358
if path == '/source':
359
raise errors.RedirectRequested(
360
path, self._scheme + '/target', is_permanent=True)
361
elif path == '/infinite-loop':
362
raise errors.RedirectRequested(
363
path, self._scheme + '/infinite-loop', is_permanent=True)
487
def test_push_stacks_with_default_stacking_if_target_is_stackable(self):
488
self.make_branch('stack_on', format='1.6')
489
self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
490
self.make_branch('from', format='pack-0.92')
491
out, err = self.run_bzr('push -d from to')
492
b = branch.Branch.open('to')
493
self.assertEqual('../stack_on', b.get_stacked_on_url())
495
def test_push_does_not_change_format_with_default_if_target_cannot(self):
496
self.make_branch('stack_on', format='pack-0.92')
497
self.make_bzrdir('.').get_config().set_default_stack_on('stack_on')
498
self.make_branch('from', format='pack-0.92')
499
out, err = self.run_bzr('push -d from to')
500
b = branch.Branch.open('to')
501
self.assertRaises(errors.UnstackableBranchFormat, b.get_stacked_on_url)
503
def test_push_doesnt_create_broken_branch(self):
504
"""Pushing a new standalone branch works even when there's a default
505
stacking policy at the destination.
507
The new branch will preserve the repo format (even if it isn't the
508
default for the branch), and will be stacked when the repo format
509
allows (which means that the branch format isn't necessarly preserved).
511
self.make_repository('repo', shared=True, format='1.6')
512
builder = self.make_branch_builder('repo/local', format='pack-0.92')
513
builder.start_series()
514
builder.build_snapshot('rev-1', None, [
515
('add', ('', 'root-id', 'directory', '')),
516
('add', ('filename', 'f-id', 'file', 'content\n'))])
517
builder.build_snapshot('rev-2', ['rev-1'], [])
518
builder.build_snapshot('rev-3', ['rev-2'],
519
[('modify', ('f-id', 'new-content\n'))])
520
builder.finish_series()
521
branch = builder.get_branch()
522
# Push rev-1 to "trunk", so that we can stack on it.
523
self.run_bzr('push -d repo/local trunk -r 1')
524
# Set a default stacking policy so that new branches will automatically
526
self.make_bzrdir('.').get_config().set_default_stack_on('trunk')
527
# Push rev-2 to a new branch "remote". It will be stacked on "trunk".
528
out, err = self.run_bzr('push -d repo/local remote -r 2')
529
self.assertContainsRe(
530
err, 'Using default stacking branch trunk at .*')
531
# Push rev-3 onto "remote". If "remote" not stacked and is missing the
532
# fulltext record for f-id @ rev-1, then this will fail.
533
out, err = self.run_bzr('push -d repo/local remote -r 3')
535
def test_push_verbose_shows_log(self):
536
tree = self.make_branch_and_tree('source')
538
out, err = self.run_bzr('push -v -d source target')
539
# initial push contains log
540
self.assertContainsRe(out, 'rev1')
542
out, err = self.run_bzr('push -v -d source target')
543
# subsequent push contains log
544
self.assertContainsRe(out, 'rev2')
545
# subsequent log is accurate
546
self.assertNotContainsRe(out, 'rev1')
548
def test_push_from_subdir(self):
549
t = self.make_branch_and_tree('tree')
550
self.build_tree(['tree/dir/', 'tree/dir/file'])
551
t.add('dir', 'dir/file')
553
out, err = self.run_bzr('push ../../pushloc', working_dir='tree/dir')
554
self.assertEqual('', out)
555
self.assertEqual('Created new branch.\n', err)
558
class RedirectingMemoryTransport(memory.MemoryTransport):
560
def mkdir(self, relpath, mode=None):
561
if self._cwd == '/source/':
562
raise errors.RedirectRequested(self.abspath(relpath),
563
self.abspath('../target'),
565
elif self._cwd == '/infinite-loop/':
566
raise errors.RedirectRequested(self.abspath(relpath),
567
self.abspath('../infinite-loop'),
365
570
return super(RedirectingMemoryTransport, self).mkdir(
369
class RedirectingMemoryServer(MemoryServer):
573
def get(self, relpath):
574
if self.clone(relpath)._cwd == '/infinite-loop/':
575
raise errors.RedirectRequested(self.abspath(relpath),
576
self.abspath('../infinite-loop'),
579
return super(RedirectingMemoryTransport, self).get(relpath)
581
def _redirected_to(self, source, target):
582
# We do accept redirections
583
return transport.get_transport(target)
586
class RedirectingMemoryServer(memory.MemoryServer):
588
def start_server(self):
372
589
self._dirs = {'/': None}
375
592
self._scheme = 'redirecting-memory+%s:///' % id(self)
376
register_transport(self._scheme, self._memory_factory)
593
transport.register_transport(self._scheme, self._memory_factory)
378
595
def _memory_factory(self, url):
379
596
result = RedirectingMemoryTransport(url)
420
633
"""Push fails gracefully if the mkdir generates a large number of
424
636
destination_url = self.memory_server.get_url() + 'infinite-loop'
425
637
out, err = self.run_bzr_error(
426
638
['Too many redirections trying to make %s\\.\n'
427
639
% re.escape(destination_url)],
428
'push %s' % destination_url, retcode=3)
640
['push', '-d', 'tree', destination_url], retcode=3)
430
641
self.assertEqual('', out)
644
class TestPushStrictMixin(object):
646
def make_local_branch_and_tree(self):
647
self.tree = self.make_branch_and_tree('local')
648
self.build_tree_contents([('local/file', 'initial')])
649
self.tree.add('file')
650
self.tree.commit('adding file', rev_id='added')
651
self.build_tree_contents([('local/file', 'modified')])
652
self.tree.commit('modify file', rev_id='modified')
654
def set_config_push_strict(self, value):
655
# set config var (any of bazaar.conf, locations.conf, branch.conf
657
conf = self.tree.branch.get_config()
658
conf.set_user_option('push_strict', value)
660
_default_command = ['push', '../to']
661
_default_wd = 'local'
662
_default_errors = ['Working tree ".*/local/" has uncommitted '
663
'changes \(See bzr status\)\.',]
664
_default_pushed_revid = 'modified'
666
def assertPushFails(self, args):
667
self.run_bzr_error(self._default_errors, self._default_command + args,
668
working_dir=self._default_wd, retcode=3)
670
def assertPushSucceeds(self, args, pushed_revid=None):
671
self.run_bzr(self._default_command + args,
672
working_dir=self._default_wd)
673
if pushed_revid is None:
674
pushed_revid = self._default_pushed_revid
675
tree_to = workingtree.WorkingTree.open('to')
676
repo_to = tree_to.branch.repository
677
self.assertTrue(repo_to.has_revision(pushed_revid))
678
self.assertEqual(tree_to.branch.last_revision_info()[1], pushed_revid)
682
class TestPushStrictWithoutChanges(tests.TestCaseWithTransport,
683
TestPushStrictMixin):
686
super(TestPushStrictWithoutChanges, self).setUp()
687
self.make_local_branch_and_tree()
689
def test_push_default(self):
690
self.assertPushSucceeds([])
692
def test_push_strict(self):
693
self.assertPushSucceeds(['--strict'])
695
def test_push_no_strict(self):
696
self.assertPushSucceeds(['--no-strict'])
698
def test_push_config_var_strict(self):
699
self.set_config_push_strict('true')
700
self.assertPushSucceeds([])
702
def test_push_config_var_no_strict(self):
703
self.set_config_push_strict('false')
704
self.assertPushSucceeds([])
707
class TestPushStrictWithChanges(tests.TestCaseWithTransport,
708
TestPushStrictMixin):
710
_changes_type = None # Set by load_tests
713
super(TestPushStrictWithChanges, self).setUp()
714
# Apply the changes defined in load_tests: one of _uncommitted_changes,
715
# _pending_merges or _out_of_sync_trees
716
getattr(self, self._changes_type)()
718
def _uncommitted_changes(self):
719
self.make_local_branch_and_tree()
720
# Make a change without committing it
721
self.build_tree_contents([('local/file', 'in progress')])
723
def _pending_merges(self):
724
self.make_local_branch_and_tree()
725
# Create 'other' branch containing a new file
726
other_bzrdir = self.tree.bzrdir.sprout('other')
727
other_tree = other_bzrdir.open_workingtree()
728
self.build_tree_contents([('other/other-file', 'other')])
729
other_tree.add('other-file')
730
other_tree.commit('other commit', rev_id='other')
731
# Merge and revert, leaving a pending merge
732
self.tree.merge_from_branch(other_tree.branch)
733
self.tree.revert(filenames=['other-file'], backups=False)
735
def _out_of_sync_trees(self):
736
self.make_local_branch_and_tree()
737
self.run_bzr(['checkout', '--lightweight', 'local', 'checkout'])
738
# Make a change and commit it
739
self.build_tree_contents([('local/file', 'modified in local')])
740
self.tree.commit('modify file', rev_id='modified-in-local')
741
# Exercise commands from the checkout directory
742
self._default_wd = 'checkout'
743
self._default_errors = ["Working tree is out of date, please run"
745
self._default_pushed_revid = 'modified-in-local'
747
def test_push_default(self):
748
self.assertPushFails([])
750
def test_push_with_revision(self):
751
self.assertPushSucceeds(['-r', 'revid:added'], pushed_revid='added')
753
def test_push_no_strict(self):
754
self.assertPushSucceeds(['--no-strict'])
756
def test_push_strict_with_changes(self):
757
self.assertPushFails(['--strict'])
759
def test_push_respect_config_var_strict(self):
760
self.set_config_push_strict('true')
761
self.assertPushFails([])
763
def test_push_bogus_config_var_ignored(self):
764
self.set_config_push_strict("I don't want you to be strict")
765
self.assertPushFails([])
767
def test_push_no_strict_command_line_override_config(self):
768
self.set_config_push_strict('yES')
769
self.assertPushFails([])
770
self.assertPushSucceeds(['--no-strict'])
772
def test_push_strict_command_line_override_config(self):
773
self.set_config_push_strict('oFF')
774
self.assertPushFails(['--strict'])
775
self.assertPushSucceeds([])
778
class TestPushForeign(blackbox.ExternalBase):
781
super(TestPushForeign, self).setUp()
782
test_foreign.register_dummy_foreign_for_test(self)
784
def make_dummy_builder(self, relpath):
785
builder = self.make_branch_builder(
786
relpath, format=test_foreign.DummyForeignVcsDirFormat())
787
builder.build_snapshot('revid', None,
788
[('add', ('', 'TREE_ROOT', 'directory', None)),
789
('add', ('foo', 'fooid', 'file', 'bar'))])
792
def test_no_roundtripping(self):
793
target_branch = self.make_dummy_builder('dp').get_branch()
794
source_tree = self.make_branch_and_tree("dc")
795
output, error = self.run_bzr("push -d dc dp", retcode=3)
796
self.assertEquals("", output)
797
self.assertEquals(error, "bzr: ERROR: It is not possible to losslessly"
798
" push to dummy. You may want to use dpush instead.\n")