14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for bzrdir implementations - tests a bzrdir format."""
17
"""Tests for control directory implementations - tests a controldir format."""
19
from cStringIO import StringIO
21
19
from itertools import izip
23
from stat import S_ISDIR
25
22
import bzrlib.branch
26
23
from bzrlib import (
34
revision as _mod_revision,
41
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
42
from bzrlib.errors import (FileExists,
45
UninitializableFormat,
48
35
import bzrlib.revision
49
36
from bzrlib.tests import (
52
TestCaseWithTransport,
56
from bzrlib.tests.per_bzrdir import TestCaseWithBzrDir
57
from bzrlib.trace import mutter
41
from bzrlib.tests.per_controldir import TestCaseWithControlDir
58
42
from bzrlib.transport.local import LocalTransport
59
43
from bzrlib.ui import (
60
44
CannedInputUIFactory,
62
from bzrlib.upgrade import upgrade
63
46
from bzrlib.remote import RemoteBzrDir, RemoteRepository
64
47
from bzrlib.repofmt import weaverepo
67
class TestBzrDir(TestCaseWithBzrDir):
68
# Many of these tests test for disk equality rather than checking
69
# for semantic equivalence. This works well for some tests but
70
# is not good at handling changes in representation or the addition
71
# or removal of control data. It would be nice to for instance:
72
# sprout a new branch, check that the nickname has been reset by hand
73
# and then set the nickname to match the source branch, at which point
74
# a semantic equivalence should pass
76
def assertDirectoriesEqual(self, source, target, ignore_list=[]):
77
"""Assert that the content of source and target are identical.
79
paths in ignore list will be completely ignored.
81
We ignore paths that represent data which is allowed to change during
82
a clone or sprout: for instance, inventory.knit contains gzip fragements
83
which have timestamps in them, and as we have read the inventory from
84
the source knit, the already-read data is recompressed rather than
85
reading it again, which leads to changed timestamps. This is ok though,
86
because the inventory.kndx file is not ignored, and the integrity of
87
knit joins is tested by test_knit and test_versionedfile.
89
:seealso: Additionally, assertRepositoryHasSameItems provides value
90
rather than representation checking of repositories for
96
dir = directories.pop()
97
for path in set(source.list_dir(dir) + target.list_dir(dir)):
98
path = dir + '/' + path
99
if path in ignore_list:
102
stat = source.stat(path)
103
except errors.NoSuchFile:
104
self.fail('%s not in source' % path)
105
if S_ISDIR(stat.st_mode):
106
self.assertTrue(S_ISDIR(target.stat(path).st_mode))
107
directories.append(path)
109
self.assertEqualDiff(source.get(path).read(),
110
target.get(path).read(),
111
"text for file %r differs:\n" % path)
113
def assertRepositoryHasSameItems(self, left_repo, right_repo):
114
"""require left_repo and right_repo to contain the same data."""
115
# XXX: TODO: Doesn't work yet, because we need to be able to compare
116
# local repositories to remote ones... but this is an as-yet unsolved
117
# aspect of format management and the Remote protocols...
118
# self.assertEqual(left_repo._format.__class__,
119
# right_repo._format.__class__)
120
left_repo.lock_read()
122
right_repo.lock_read()
125
all_revs = left_repo.all_revision_ids()
126
self.assertEqual(left_repo.all_revision_ids(),
127
right_repo.all_revision_ids())
128
for rev_id in left_repo.all_revision_ids():
129
self.assertEqual(left_repo.get_revision(rev_id),
130
right_repo.get_revision(rev_id))
131
# Assert the revision trees (and thus the inventories) are equal
132
sort_key = lambda rev_tree: rev_tree.get_revision_id()
133
rev_trees_a = sorted(
134
left_repo.revision_trees(all_revs), key=sort_key)
135
rev_trees_b = sorted(
136
right_repo.revision_trees(all_revs), key=sort_key)
137
for tree_a, tree_b in zip(rev_trees_a, rev_trees_b):
138
self.assertEqual([], list(tree_a.iter_changes(tree_b)))
140
text_index = left_repo._generate_text_key_index()
141
self.assertEqual(text_index,
142
right_repo._generate_text_key_index())
144
for file_id, revision_id in text_index.iterkeys():
145
desired_files.append(
146
(file_id, revision_id, (file_id, revision_id)))
147
left_texts = list(left_repo.iter_files_bytes(desired_files))
148
right_texts = list(right_repo.iter_files_bytes(desired_files))
151
self.assertEqual(left_texts, right_texts)
153
for rev_id in all_revs:
155
left_text = left_repo.get_signature_text(rev_id)
156
except NoSuchRevision:
158
right_text = right_repo.get_signature_text(rev_id)
159
self.assertEqual(left_text, right_text)
50
class TestControlDir(TestCaseWithControlDir):
165
52
def skipIfNoWorkingTree(self, a_bzrdir):
166
53
"""Raises TestSkipped if a_bzrdir doesn't have a working tree.
264
151
bzrdir.open_repository()
266
153
def test_open_workingtree_raises_no_working_tree(self):
267
"""BzrDir.open_workingtree() should raise NoWorkingTree (rather than
154
"""ControlDir.open_workingtree() should raise NoWorkingTree (rather than
268
155
e.g. NotLocalUrl) if there is no working tree.
270
157
dir = self.make_bzrdir('source')
271
158
vfs_dir = bzrdir.BzrDir.open(self.get_vfs_only_url('source'))
272
159
if vfs_dir.has_workingtree():
273
# This BzrDir format doesn't support BzrDirs without working trees,
274
# so this test is irrelevant.
160
# This ControlDir format doesn't support ControlDirs without
161
# working trees, so this test is irrelevant.
276
163
self.assertRaises(errors.NoWorkingTree, dir.open_workingtree)
278
def test_clone_on_transport(self):
279
a_dir = self.make_bzrdir('source')
280
target_transport = a_dir.root_transport.clone('..').clone('target')
281
target = a_dir.clone_on_transport(target_transport)
282
self.assertNotEqual(a_dir.transport.base, target.transport.base)
283
self.assertDirectoriesEqual(a_dir.root_transport, target.root_transport,
284
['./.bzr/merge-hashes'])
286
def test_clone_bzrdir_empty(self):
287
dir = self.make_bzrdir('source')
288
target = dir.clone(self.get_url('target'))
289
self.assertNotEqual(dir.transport.base, target.transport.base)
290
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
291
['./.bzr/merge-hashes'])
293
def test_clone_bzrdir_empty_force_new_ignored(self):
294
# the force_new_repo parameter should have no effect on an empty
295
# bzrdir's clone logic
296
dir = self.make_bzrdir('source')
297
target = dir.clone(self.get_url('target'), force_new_repo=True)
298
self.assertNotEqual(dir.transport.base, target.transport.base)
299
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
300
['./.bzr/merge-hashes'])
302
def test_clone_bzrdir_repository(self):
303
tree = self.make_branch_and_tree('commit_tree')
304
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
306
tree.commit('revision 1', rev_id='1')
307
dir = self.make_bzrdir('source')
308
repo = dir.create_repository()
309
repo.fetch(tree.branch.repository)
310
self.assertTrue(repo.has_revision('1'))
311
target = dir.clone(self.get_url('target'))
312
self.assertNotEqual(dir.transport.base, target.transport.base)
313
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
315
'./.bzr/merge-hashes',
318
self.assertRepositoryHasSameItems(tree.branch.repository,
319
target.open_repository())
321
165
def test_clone_bzrdir_repository_under_shared(self):
322
166
tree = self.make_branch_and_tree('commit_tree')
323
167
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
393
237
self.assertFalse(branch.repository.make_working_trees())
394
238
self.assertTrue(branch.repository.is_shared())
396
def test_clone_bzrdir_repository_under_shared_force_new_repo(self):
397
tree = self.make_branch_and_tree('commit_tree')
398
self.build_tree(['commit_tree/foo'])
400
tree.commit('revision 1', rev_id='1')
401
dir = self.make_bzrdir('source')
402
repo = dir.create_repository()
403
repo.fetch(tree.branch.repository)
404
self.assertTrue(repo.has_revision('1'))
406
self.make_repository('target', shared=True)
407
except errors.IncompatibleFormat:
409
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
410
self.assertNotEqual(dir.transport.base, target.transport.base)
411
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
412
['./.bzr/repository',
414
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
416
240
def test_clone_bzrdir_repository_revision(self):
417
241
# test for revision limiting, [smoke test, not corner case checks].
418
242
# make a repository with some revisions,
452
276
tree_repo.get_signature_text(rev1),
453
277
target.repository.get_signature_text(rev1))
455
def test_clone_bzrdir_branch_and_repo(self):
456
tree = self.make_branch_and_tree('commit_tree')
457
self.build_tree(['commit_tree/foo'])
459
tree.commit('revision 1')
460
source = self.make_branch('source')
461
tree.branch.repository.copy_content_into(source.repository)
462
tree.branch.copy_content_into(source)
464
target = dir.clone(self.get_url('target'))
465
self.assertNotEqual(dir.transport.base, target.transport.base)
466
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
468
'./.bzr/basis-inventory-cache',
469
'./.bzr/checkout/stat-cache',
470
'./.bzr/merge-hashes',
474
self.assertRepositoryHasSameItems(
475
tree.branch.repository, target.open_repository())
477
279
def test_clone_bzrdir_branch_and_repo_into_shared_repo(self):
478
280
# by default cloning into a shared repo uses the shared repo.
479
281
tree = self.make_branch_and_tree('commit_tree')
494
296
self.assertEqual(source.revision_history(),
495
297
target.open_branch().revision_history())
497
def test_clone_bzrdir_branch_and_repo_into_shared_repo_force_new_repo(self):
498
# by default cloning into a shared repo uses the shared repo.
499
tree = self.make_branch_and_tree('commit_tree')
500
self.build_tree(['commit_tree/foo'])
502
tree.commit('revision 1')
503
source = self.make_branch('source')
504
tree.branch.repository.copy_content_into(source.repository)
505
tree.branch.copy_content_into(source)
507
self.make_repository('target', shared=True)
508
except errors.IncompatibleFormat:
511
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
512
self.assertNotEqual(dir.transport.base, target.transport.base)
513
repo = target.open_repository()
514
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
515
['./.bzr/repository',
517
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
519
def test_clone_bzrdir_branch_reference(self):
520
# cloning should preserve the reference status of the branch in a bzrdir
521
referenced_branch = self.make_branch('referencced')
522
dir = self.make_bzrdir('source')
524
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
525
target_branch=referenced_branch)
526
except errors.IncompatibleFormat:
527
# this is ok too, not all formats have to support references.
529
target = dir.clone(self.get_url('target'))
530
self.assertNotEqual(dir.transport.base, target.transport.base)
531
self.assertDirectoriesEqual(dir.root_transport, target.root_transport)
533
299
def test_clone_bzrdir_branch_revision(self):
534
300
# test for revision limiting, [smoke test, not corner case checks].
535
301
# make a branch with some revisions,
547
313
target = dir.clone(self.get_url('target'), revision_id='1')
548
314
self.assertEqual('1', target.open_branch().last_revision())
550
def test_clone_bzrdir_tree_branch_repo(self):
551
tree = self.make_branch_and_tree('source')
552
self.build_tree(['source/foo'])
554
tree.commit('revision 1')
556
target = dir.clone(self.get_url('target'))
557
self.skipIfNoWorkingTree(target)
558
self.assertNotEqual(dir.transport.base, target.transport.base)
559
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
560
['./.bzr/stat-cache',
561
'./.bzr/checkout/dirstate',
562
'./.bzr/checkout/stat-cache',
563
'./.bzr/checkout/merge-hashes',
564
'./.bzr/merge-hashes',
567
self.assertRepositoryHasSameItems(tree.branch.repository,
568
target.open_repository())
569
target.open_workingtree().revert()
571
316
def test_clone_on_transport_preserves_repo_format(self):
572
317
if self.bzrdir_format == bzrdir.format_registry.make_bzrdir('default'):
587
332
target_repo = target_repo._real_repository
588
333
self.assertEqual(target_repo._format, source_branch.repository._format)
590
def test_revert_inventory(self):
591
tree = self.make_branch_and_tree('source')
592
self.build_tree(['source/foo'])
594
tree.commit('revision 1')
596
target = dir.clone(self.get_url('target'))
597
self.skipIfNoWorkingTree(target)
598
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
599
['./.bzr/stat-cache',
600
'./.bzr/checkout/dirstate',
601
'./.bzr/checkout/stat-cache',
602
'./.bzr/checkout/merge-hashes',
603
'./.bzr/merge-hashes',
606
self.assertRepositoryHasSameItems(tree.branch.repository,
607
target.open_repository())
609
target.open_workingtree().revert()
610
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
611
['./.bzr/stat-cache',
612
'./.bzr/checkout/dirstate',
613
'./.bzr/checkout/stat-cache',
614
'./.bzr/checkout/merge-hashes',
615
'./.bzr/merge-hashes',
618
self.assertRepositoryHasSameItems(tree.branch.repository,
619
target.open_repository())
621
def test_clone_bzrdir_tree_branch_reference(self):
622
# a tree with a branch reference (aka a checkout)
623
# should stay a checkout on clone.
624
referenced_branch = self.make_branch('referencced')
625
dir = self.make_bzrdir('source')
627
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
628
target_branch=referenced_branch)
629
except errors.IncompatibleFormat:
630
# this is ok too, not all formats have to support references.
632
self.createWorkingTreeOrSkip(dir)
633
target = dir.clone(self.get_url('target'))
634
self.skipIfNoWorkingTree(target)
635
self.assertNotEqual(dir.transport.base, target.transport.base)
636
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
637
['./.bzr/stat-cache',
638
'./.bzr/checkout/stat-cache',
639
'./.bzr/checkout/merge-hashes',
640
'./.bzr/merge-hashes',
641
'./.bzr/repository/inventory.knit',
644
335
def test_clone_bzrdir_tree_revision(self):
645
336
# test for revision limiting, [smoke test, not corner case checks].
646
337
# make a tree with a revision with a last-revision
749
439
target.open_branch()
750
440
self.openWorkingTreeIfLocal(target)
752
def test_sprout_bzrdir_repository(self):
753
tree = self.make_branch_and_tree('commit_tree')
754
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
756
tree.commit('revision 1', rev_id='1')
757
dir = self.make_bzrdir('source')
758
repo = dir.create_repository()
759
repo.fetch(tree.branch.repository)
760
self.assertTrue(repo.has_revision('1'))
763
_mod_revision.is_null(_mod_revision.ensure_null(
764
dir.open_branch().last_revision())))
765
except errors.NotBranchError:
767
target = dir.sprout(self.get_url('target'))
768
self.assertNotEqual(dir.transport.base, target.transport.base)
769
# testing inventory isn't reasonable for repositories
770
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
776
'./.bzr/repository/inventory.knit',
779
local_inventory = dir.transport.local_abspath('inventory')
780
except errors.NotLocalUrl:
783
# If we happen to have a tree, we'll guarantee everything
784
# except for the tree root is the same.
785
inventory_f = file(local_inventory, 'rb')
786
self.addCleanup(inventory_f.close)
787
self.assertContainsRe(inventory_f.read(),
788
'<inventory format="5">\n</inventory>\n')
790
if e.errno != errno.ENOENT:
793
442
def test_sprout_bzrdir_with_repository_to_shared(self):
794
443
tree = self.make_branch_and_tree('commit_tree')
795
444
self.build_tree(['commit_tree/foo'])
894
543
target = self.sproutOrSkip(dir, self.get_url('target'), revision_id='2')
895
544
raise TestSkipped('revision limiting not strict yet')
897
def test_sprout_bzrdir_branch_and_repo(self):
898
tree = self.make_branch_and_tree('commit_tree')
899
self.build_tree(['commit_tree/foo'])
901
tree.commit('revision 1')
902
source = self.make_branch('source')
903
tree.branch.repository.copy_content_into(source.repository)
904
tree.bzrdir.open_branch().copy_content_into(source)
906
target = dir.sprout(self.get_url('target'))
907
self.assertNotEqual(dir.transport.base, target.transport.base)
908
target_repo = target.open_repository()
909
self.assertRepositoryHasSameItems(source.repository, target_repo)
910
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
912
'./.bzr/basis-inventory-cache',
913
'./.bzr/branch/branch.conf',
914
'./.bzr/branch/parent',
916
'./.bzr/checkout/inventory',
917
'./.bzr/checkout/stat-cache',
925
546
def test_sprout_bzrdir_branch_and_repo_shared(self):
926
547
# sprouting a branch with a repo into a shared repo uses the shared
1046
667
target = dir.sprout(self.get_url('target'), revision_id='1')
1047
668
self.assertEqual('1', target.open_branch().last_revision())
1049
def test_sprout_bzrdir_tree_branch_repo(self):
1050
tree = self.make_branch_and_tree('source')
1051
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
1053
tree.commit('revision 1')
1055
target = self.sproutOrSkip(dir, self.get_url('target'))
1056
self.assertNotEqual(dir.transport.base, target.transport.base)
1057
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
1059
'./.bzr/branch/branch.conf',
1060
'./.bzr/branch/parent',
1061
'./.bzr/checkout/dirstate',
1062
'./.bzr/checkout/stat-cache',
1063
'./.bzr/checkout/inventory',
1066
'./.bzr/repository',
1067
'./.bzr/stat-cache',
1069
self.assertRepositoryHasSameItems(
1070
tree.branch.repository, target.open_repository())
1072
670
def test_sprout_bzrdir_tree_branch_reference(self):
1073
671
# sprouting should create a repository if needed and a sprouted branch.
1074
672
# the tree state should not be copied.
1208
806
t = transport.get_transport(self.get_url())
1209
807
readonly_t = transport.get_transport(self.get_readonly_url())
1210
808
made_control = self.bzrdir_format.initialize(t.base)
1211
self.failUnless(isinstance(made_control, bzrdir.BzrDir))
809
self.failUnless(isinstance(made_control, controldir.ControlDir))
1212
810
self.assertEqual(self.bzrdir_format,
1213
bzrdir.BzrDirFormat.find_format(readonly_t))
811
controldir.ControlDirFormat.find_format(readonly_t))
1214
812
direct_opened_dir = self.bzrdir_format.open(readonly_t)
1215
813
opened_dir = bzrdir.BzrDir.open(t.base)
1216
814
self.assertEqual(made_control._format,
1217
815
opened_dir._format)
1218
816
self.assertEqual(direct_opened_dir._format,
1219
817
opened_dir._format)
1220
self.failUnless(isinstance(opened_dir, bzrdir.BzrDir))
818
self.failUnless(isinstance(opened_dir, controldir.ControlDir))
1222
820
def test_format_initialize_on_transport_ex(self):
1223
821
t = self.get_transport('dir')
1405
1003
network_name = format.network_name()
1406
1004
self.assertEqual(real_dir._format.network_name(), network_name)
1408
registry = bzrdir.network_format_registry
1006
registry = controldir.network_format_registry
1409
1007
network_name = format.network_name()
1410
1008
looked_up_format = registry.get(network_name)
1411
self.assertEqual(format.__class__, looked_up_format.__class__)
1010
issubclass(format.__class__, looked_up_format.__class__))
1412
1011
# The network name must be a byte string.
1413
1012
self.assertIsInstance(network_name, str)
1415
1014
def test_open_not_bzrdir(self):
1416
1015
# test the formats specific behaviour for no-content or similar dirs.
1417
self.assertRaises(NotBranchError,
1016
self.assertRaises(errors.NotBranchError,
1418
1017
self.bzrdir_format.open,
1419
1018
transport.get_transport(self.get_readonly_url()))
1814
1413
text = dir._format.get_format_description()
1815
1414
self.failUnless(len(text))
1817
def test_retire_bzrdir(self):
1818
bd = self.make_bzrdir('.')
1819
transport = bd.root_transport
1820
# must not overwrite existing directories
1821
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
1822
transport=transport)
1823
self.failUnless(transport.has('.bzr'))
1825
self.failIf(transport.has('.bzr'))
1826
self.failUnless(transport.has('.bzr.retired.1'))
1828
def test_retire_bzrdir_limited(self):
1829
bd = self.make_bzrdir('.')
1830
transport = bd.root_transport
1831
# must not overwrite existing directories
1832
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
1833
transport=transport)
1834
self.failUnless(transport.has('.bzr'))
1835
self.assertRaises((errors.FileExists, errors.DirectoryNotEmpty),
1836
bd.retire_bzrdir, limit=0)
1839
class TestBreakLock(TestCaseWithBzrDir):
1417
class TestBreakLock(TestCaseWithControlDir):
1841
1419
def test_break_lock_empty(self):
1842
1420
# break lock on an empty bzrdir should work silently.