1
# Copyright (C) 2006-2010 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
17
"""Tests for bzrdir implementations - tests a bzrdir format."""
20
from stat import S_ISDIR
25
revision as _mod_revision,
28
from bzrlib.tests import (
31
from bzrlib.tests.per_bzrdir import TestCaseWithBzrDir
32
from bzrlib.transport.local import (
37
class TestBzrDir(TestCaseWithBzrDir):
39
# Many of these tests test for disk equality rather than checking
40
# for semantic equivalence. This works well for some tests but
41
# is not good at handling changes in representation or the addition
42
# or removal of control data. It would be nice to for instance:
43
# sprout a new branch, check that the nickname has been reset by hand
44
# and then set the nickname to match the source branch, at which point
45
# a semantic equivalence should pass
47
def assertDirectoriesEqual(self, source, target, ignore_list=[]):
48
"""Assert that the content of source and target are identical.
50
paths in ignore list will be completely ignored.
52
We ignore paths that represent data which is allowed to change during
53
a clone or sprout: for instance, inventory.knit contains gzip fragements
54
which have timestamps in them, and as we have read the inventory from
55
the source knit, the already-read data is recompressed rather than
56
reading it again, which leads to changed timestamps. This is ok though,
57
because the inventory.kndx file is not ignored, and the integrity of
58
knit joins is tested by test_knit and test_versionedfile.
60
:seealso: Additionally, assertRepositoryHasSameItems provides value
61
rather than representation checking of repositories for
67
dir = directories.pop()
68
for path in set(source.list_dir(dir) + target.list_dir(dir)):
69
path = dir + '/' + path
70
if path in ignore_list:
73
stat = source.stat(path)
74
except errors.NoSuchFile:
75
self.fail('%s not in source' % path)
76
if S_ISDIR(stat.st_mode):
77
self.assertTrue(S_ISDIR(target.stat(path).st_mode))
78
directories.append(path)
80
self.assertEqualDiff(source.get(path).read(),
81
target.get(path).read(),
82
"text for file %r differs:\n" % path)
84
def assertRepositoryHasSameItems(self, left_repo, right_repo):
85
"""require left_repo and right_repo to contain the same data."""
86
# XXX: TODO: Doesn't work yet, because we need to be able to compare
87
# local repositories to remote ones... but this is an as-yet unsolved
88
# aspect of format management and the Remote protocols...
89
# self.assertEqual(left_repo._format.__class__,
90
# right_repo._format.__class__)
93
right_repo.lock_read()
96
all_revs = left_repo.all_revision_ids()
97
self.assertEqual(left_repo.all_revision_ids(),
98
right_repo.all_revision_ids())
99
for rev_id in left_repo.all_revision_ids():
100
self.assertEqual(left_repo.get_revision(rev_id),
101
right_repo.get_revision(rev_id))
102
# Assert the revision trees (and thus the inventories) are equal
103
sort_key = lambda rev_tree: rev_tree.get_revision_id()
104
rev_trees_a = sorted(
105
left_repo.revision_trees(all_revs), key=sort_key)
106
rev_trees_b = sorted(
107
right_repo.revision_trees(all_revs), key=sort_key)
108
for tree_a, tree_b in zip(rev_trees_a, rev_trees_b):
109
self.assertEqual([], list(tree_a.iter_changes(tree_b)))
111
text_index = left_repo._generate_text_key_index()
112
self.assertEqual(text_index,
113
right_repo._generate_text_key_index())
115
for file_id, revision_id in text_index.iterkeys():
116
desired_files.append(
117
(file_id, revision_id, (file_id, revision_id)))
118
left_texts = list(left_repo.iter_files_bytes(desired_files))
119
right_texts = list(right_repo.iter_files_bytes(desired_files))
122
self.assertEqual(left_texts, right_texts)
124
for rev_id in all_revs:
126
left_text = left_repo.get_signature_text(rev_id)
127
except errors.NoSuchRevision:
129
right_text = right_repo.get_signature_text(rev_id)
130
self.assertEqual(left_text, right_text)
136
def sproutOrSkip(self, from_bzrdir, to_url, revision_id=None,
137
force_new_repo=False, accelerator_tree=None,
138
create_tree_if_local=True):
139
"""Sprout from_bzrdir into to_url, or raise TestSkipped.
141
A simple wrapper for from_bzrdir.sprout that translates NotLocalUrl into
142
TestSkipped. Returns the newly sprouted bzrdir.
144
to_transport = transport.get_transport(to_url)
145
if not isinstance(to_transport, LocalTransport):
146
raise TestSkipped('Cannot sprout to remote bzrdirs.')
147
target = from_bzrdir.sprout(to_url, revision_id=revision_id,
148
force_new_repo=force_new_repo,
149
possible_transports=[to_transport],
150
accelerator_tree=accelerator_tree,
151
create_tree_if_local=create_tree_if_local)
154
def skipIfNoWorkingTree(self, a_bzrdir):
155
"""Raises TestSkipped if a_bzrdir doesn't have a working tree.
157
If the bzrdir does have a workingtree, this is a no-op.
160
a_bzrdir.open_workingtree()
161
except (errors.NotLocalUrl, errors.NoWorkingTree):
162
raise TestSkipped("bzrdir on transport %r has no working tree"
163
% a_bzrdir.transport)
165
def createWorkingTreeOrSkip(self, a_bzrdir):
166
"""Create a working tree on a_bzrdir, or raise TestSkipped.
168
A simple wrapper for create_workingtree that translates NotLocalUrl into
169
TestSkipped. Returns the newly created working tree.
172
return a_bzrdir.create_workingtree()
173
except errors.NotLocalUrl:
174
raise TestSkipped("cannot make working tree with transport %r"
175
% a_bzrdir.transport)
177
def test_clone_bzrdir_repository_under_shared_force_new_repo(self):
178
tree = self.make_branch_and_tree('commit_tree')
179
self.build_tree(['commit_tree/foo'])
181
tree.commit('revision 1', rev_id='1')
182
dir = self.make_bzrdir('source')
183
repo = dir.create_repository()
184
repo.fetch(tree.branch.repository)
185
self.assertTrue(repo.has_revision('1'))
187
self.make_repository('target', shared=True)
188
except errors.IncompatibleFormat:
190
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
191
self.assertNotEqual(dir.transport.base, target.transport.base)
192
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
193
['./.bzr/repository',
195
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
197
def test_clone_bzrdir_branch_and_repo(self):
198
tree = self.make_branch_and_tree('commit_tree')
199
self.build_tree(['commit_tree/foo'])
201
tree.commit('revision 1')
202
source = self.make_branch('source')
203
tree.branch.repository.copy_content_into(source.repository)
204
tree.branch.copy_content_into(source)
206
target = dir.clone(self.get_url('target'))
207
self.assertNotEqual(dir.transport.base, target.transport.base)
208
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
210
'./.bzr/basis-inventory-cache',
211
'./.bzr/checkout/stat-cache',
212
'./.bzr/merge-hashes',
216
self.assertRepositoryHasSameItems(
217
tree.branch.repository, target.open_repository())
219
def test_clone_on_transport(self):
220
a_dir = self.make_bzrdir('source')
221
target_transport = a_dir.root_transport.clone('..').clone('target')
222
target = a_dir.clone_on_transport(target_transport)
223
self.assertNotEqual(a_dir.transport.base, target.transport.base)
224
self.assertDirectoriesEqual(a_dir.root_transport, target.root_transport,
225
['./.bzr/merge-hashes'])
227
def test_clone_bzrdir_empty(self):
228
dir = self.make_bzrdir('source')
229
target = dir.clone(self.get_url('target'))
230
self.assertNotEqual(dir.transport.base, target.transport.base)
231
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
232
['./.bzr/merge-hashes'])
234
def test_clone_bzrdir_empty_force_new_ignored(self):
235
# the force_new_repo parameter should have no effect on an empty
236
# bzrdir's clone logic
237
dir = self.make_bzrdir('source')
238
target = dir.clone(self.get_url('target'), force_new_repo=True)
239
self.assertNotEqual(dir.transport.base, target.transport.base)
240
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
241
['./.bzr/merge-hashes'])
243
def test_clone_bzrdir_repository(self):
244
tree = self.make_branch_and_tree('commit_tree')
245
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
247
tree.commit('revision 1', rev_id='1')
248
dir = self.make_bzrdir('source')
249
repo = dir.create_repository()
250
repo.fetch(tree.branch.repository)
251
self.assertTrue(repo.has_revision('1'))
252
target = dir.clone(self.get_url('target'))
253
self.assertNotEqual(dir.transport.base, target.transport.base)
254
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
256
'./.bzr/merge-hashes',
259
self.assertRepositoryHasSameItems(tree.branch.repository,
260
target.open_repository())
262
def test_clone_bzrdir_tree_branch_repo(self):
263
tree = self.make_branch_and_tree('source')
264
self.build_tree(['source/foo'])
266
tree.commit('revision 1')
268
target = dir.clone(self.get_url('target'))
269
self.skipIfNoWorkingTree(target)
270
self.assertNotEqual(dir.transport.base, target.transport.base)
271
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
272
['./.bzr/stat-cache',
273
'./.bzr/checkout/dirstate',
274
'./.bzr/checkout/stat-cache',
275
'./.bzr/checkout/merge-hashes',
276
'./.bzr/merge-hashes',
279
self.assertRepositoryHasSameItems(tree.branch.repository,
280
target.open_repository())
281
target.open_workingtree().revert()
283
def test_revert_inventory(self):
284
tree = self.make_branch_and_tree('source')
285
self.build_tree(['source/foo'])
287
tree.commit('revision 1')
289
target = dir.clone(self.get_url('target'))
290
self.skipIfNoWorkingTree(target)
291
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
292
['./.bzr/stat-cache',
293
'./.bzr/checkout/dirstate',
294
'./.bzr/checkout/stat-cache',
295
'./.bzr/checkout/merge-hashes',
296
'./.bzr/merge-hashes',
299
self.assertRepositoryHasSameItems(tree.branch.repository,
300
target.open_repository())
302
target.open_workingtree().revert()
303
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
304
['./.bzr/stat-cache',
305
'./.bzr/checkout/dirstate',
306
'./.bzr/checkout/stat-cache',
307
'./.bzr/checkout/merge-hashes',
308
'./.bzr/merge-hashes',
311
self.assertRepositoryHasSameItems(tree.branch.repository,
312
target.open_repository())
314
def test_clone_bzrdir_tree_branch_reference(self):
315
# a tree with a branch reference (aka a checkout)
316
# should stay a checkout on clone.
317
referenced_branch = self.make_branch('referencced')
318
dir = self.make_bzrdir('source')
320
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
321
target_branch=referenced_branch)
322
except errors.IncompatibleFormat:
323
# this is ok too, not all formats have to support references.
325
self.createWorkingTreeOrSkip(dir)
326
target = dir.clone(self.get_url('target'))
327
self.skipIfNoWorkingTree(target)
328
self.assertNotEqual(dir.transport.base, target.transport.base)
329
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
330
['./.bzr/stat-cache',
331
'./.bzr/checkout/stat-cache',
332
'./.bzr/checkout/merge-hashes',
333
'./.bzr/merge-hashes',
334
'./.bzr/repository/inventory.knit',
337
def test_clone_bzrdir_branch_and_repo_into_shared_repo_force_new_repo(self):
338
# by default cloning into a shared repo uses the shared repo.
339
tree = self.make_branch_and_tree('commit_tree')
340
self.build_tree(['commit_tree/foo'])
342
tree.commit('revision 1')
343
source = self.make_branch('source')
344
tree.branch.repository.copy_content_into(source.repository)
345
tree.branch.copy_content_into(source)
347
self.make_repository('target', shared=True)
348
except errors.IncompatibleFormat:
351
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
352
self.assertNotEqual(dir.transport.base, target.transport.base)
353
repo = target.open_repository()
354
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
355
['./.bzr/repository',
357
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
359
def test_clone_bzrdir_branch_reference(self):
360
# cloning should preserve the reference status of the branch in a bzrdir
361
referenced_branch = self.make_branch('referencced')
362
dir = self.make_bzrdir('source')
364
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
365
target_branch=referenced_branch)
366
except errors.IncompatibleFormat:
367
# this is ok too, not all formats have to support references.
369
target = dir.clone(self.get_url('target'))
370
self.assertNotEqual(dir.transport.base, target.transport.base)
371
self.assertDirectoriesEqual(dir.root_transport, target.root_transport)
373
def test_sprout_bzrdir_repository(self):
374
tree = self.make_branch_and_tree('commit_tree')
375
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
377
tree.commit('revision 1', rev_id='1')
378
dir = self.make_bzrdir('source')
379
repo = dir.create_repository()
380
repo.fetch(tree.branch.repository)
381
self.assertTrue(repo.has_revision('1'))
384
_mod_revision.is_null(_mod_revision.ensure_null(
385
dir.open_branch().last_revision())))
386
except errors.NotBranchError:
388
target = dir.sprout(self.get_url('target'))
389
self.assertNotEqual(dir.transport.base, target.transport.base)
390
# testing inventory isn't reasonable for repositories
391
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
397
'./.bzr/repository/inventory.knit',
400
local_inventory = dir.transport.local_abspath('inventory')
401
except errors.NotLocalUrl:
404
# If we happen to have a tree, we'll guarantee everything
405
# except for the tree root is the same.
406
inventory_f = file(local_inventory, 'rb')
407
self.addCleanup(inventory_f.close)
408
self.assertContainsRe(inventory_f.read(),
409
'<inventory format="5">\n</inventory>\n')
411
if e.errno != errno.ENOENT:
414
def test_sprout_bzrdir_branch_and_repo(self):
415
tree = self.make_branch_and_tree('commit_tree')
416
self.build_tree(['commit_tree/foo'])
418
tree.commit('revision 1')
419
source = self.make_branch('source')
420
tree.branch.repository.copy_content_into(source.repository)
421
tree.bzrdir.open_branch().copy_content_into(source)
423
target = dir.sprout(self.get_url('target'))
424
self.assertNotEqual(dir.transport.base, target.transport.base)
425
target_repo = target.open_repository()
426
self.assertRepositoryHasSameItems(source.repository, target_repo)
427
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
429
'./.bzr/basis-inventory-cache',
430
'./.bzr/branch/branch.conf',
431
'./.bzr/branch/parent',
433
'./.bzr/checkout/inventory',
434
'./.bzr/checkout/stat-cache',
442
def test_sprout_bzrdir_tree_branch_repo(self):
443
tree = self.make_branch_and_tree('source')
444
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
446
tree.commit('revision 1')
448
target = self.sproutOrSkip(dir, self.get_url('target'))
449
self.assertNotEqual(dir.transport.base, target.transport.base)
450
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
452
'./.bzr/branch/branch.conf',
453
'./.bzr/branch/parent',
454
'./.bzr/checkout/dirstate',
455
'./.bzr/checkout/stat-cache',
456
'./.bzr/checkout/inventory',
462
self.assertRepositoryHasSameItems(
463
tree.branch.repository, target.open_repository())
466
def test_retire_bzrdir(self):
467
bd = self.make_bzrdir('.')
468
transport = bd.root_transport
469
# must not overwrite existing directories
470
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
472
self.failUnless(transport.has('.bzr'))
474
self.failIf(transport.has('.bzr'))
475
self.failUnless(transport.has('.bzr.retired.1'))
477
def test_retire_bzrdir_limited(self):
478
bd = self.make_bzrdir('.')
479
transport = bd.root_transport
480
# must not overwrite existing directories
481
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
483
self.failUnless(transport.has('.bzr'))
484
self.assertRaises((errors.FileExists, errors.DirectoryNotEmpty),
485
bd.retire_bzrdir, limit=0)