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.per_bzrdir import TestCaseWithBzrDir
31
class TestBzrDir(TestCaseWithBzrDir):
33
# Many of these tests test for disk equality rather than checking
34
# for semantic equivalence. This works well for some tests but
35
# is not good at handling changes in representation or the addition
36
# or removal of control data. It would be nice to for instance:
37
# sprout a new branch, check that the nickname has been reset by hand
38
# and then set the nickname to match the source branch, at which point
39
# a semantic equivalence should pass
41
def assertDirectoriesEqual(self, source, target, ignore_list=[]):
42
"""Assert that the content of source and target are identical.
44
paths in ignore list will be completely ignored.
46
We ignore paths that represent data which is allowed to change during
47
a clone or sprout: for instance, inventory.knit contains gzip fragements
48
which have timestamps in them, and as we have read the inventory from
49
the source knit, the already-read data is recompressed rather than
50
reading it again, which leads to changed timestamps. This is ok though,
51
because the inventory.kndx file is not ignored, and the integrity of
52
knit joins is tested by test_knit and test_versionedfile.
54
:seealso: Additionally, assertRepositoryHasSameItems provides value
55
rather than representation checking of repositories for
61
dir = directories.pop()
62
for path in set(source.list_dir(dir) + target.list_dir(dir)):
63
path = dir + '/' + path
64
if path in ignore_list:
67
stat = source.stat(path)
68
except errors.NoSuchFile:
69
self.fail('%s not in source' % path)
70
if S_ISDIR(stat.st_mode):
71
self.assertTrue(S_ISDIR(target.stat(path).st_mode))
72
directories.append(path)
74
self.assertEqualDiff(source.get(path).read(),
75
target.get(path).read(),
76
"text for file %r differs:\n" % path)
78
def assertRepositoryHasSameItems(self, left_repo, right_repo):
79
"""require left_repo and right_repo to contain the same data."""
80
# XXX: TODO: Doesn't work yet, because we need to be able to compare
81
# local repositories to remote ones... but this is an as-yet unsolved
82
# aspect of format management and the Remote protocols...
83
# self.assertEqual(left_repo._format.__class__,
84
# right_repo._format.__class__)
87
right_repo.lock_read()
90
all_revs = left_repo.all_revision_ids()
91
self.assertEqual(left_repo.all_revision_ids(),
92
right_repo.all_revision_ids())
93
for rev_id in left_repo.all_revision_ids():
94
self.assertEqual(left_repo.get_revision(rev_id),
95
right_repo.get_revision(rev_id))
96
# Assert the revision trees (and thus the inventories) are equal
97
sort_key = lambda rev_tree: rev_tree.get_revision_id()
99
left_repo.revision_trees(all_revs), key=sort_key)
100
rev_trees_b = sorted(
101
right_repo.revision_trees(all_revs), key=sort_key)
102
for tree_a, tree_b in zip(rev_trees_a, rev_trees_b):
103
self.assertEqual([], list(tree_a.iter_changes(tree_b)))
105
text_index = left_repo._generate_text_key_index()
106
self.assertEqual(text_index,
107
right_repo._generate_text_key_index())
109
for file_id, revision_id in text_index.iterkeys():
110
desired_files.append(
111
(file_id, revision_id, (file_id, revision_id)))
112
left_texts = list(left_repo.iter_files_bytes(desired_files))
113
right_texts = list(right_repo.iter_files_bytes(desired_files))
116
self.assertEqual(left_texts, right_texts)
118
for rev_id in all_revs:
120
left_text = left_repo.get_signature_text(rev_id)
121
except errors.NoSuchRevision:
123
right_text = right_repo.get_signature_text(rev_id)
124
self.assertEqual(left_text, right_text)
130
def test_clone_bzrdir_repository_under_shared_force_new_repo(self):
131
tree = self.make_branch_and_tree('commit_tree')
132
self.build_tree(['commit_tree/foo'])
134
tree.commit('revision 1', rev_id='1')
135
dir = self.make_bzrdir('source')
136
repo = dir.create_repository()
137
repo.fetch(tree.branch.repository)
138
self.assertTrue(repo.has_revision('1'))
140
self.make_repository('target', shared=True)
141
except errors.IncompatibleFormat:
143
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
144
self.assertNotEqual(dir.transport.base, target.transport.base)
145
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
146
['./.bzr/repository',
148
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
150
def test_clone_bzrdir_branch_and_repo(self):
151
tree = self.make_branch_and_tree('commit_tree')
152
self.build_tree(['commit_tree/foo'])
154
tree.commit('revision 1')
155
source = self.make_branch('source')
156
tree.branch.repository.copy_content_into(source.repository)
157
tree.branch.copy_content_into(source)
159
target = dir.clone(self.get_url('target'))
160
self.assertNotEqual(dir.transport.base, target.transport.base)
161
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
163
'./.bzr/basis-inventory-cache',
164
'./.bzr/checkout/stat-cache',
165
'./.bzr/merge-hashes',
169
self.assertRepositoryHasSameItems(
170
tree.branch.repository, target.open_repository())
172
def test_clone_on_transport(self):
173
a_dir = self.make_bzrdir('source')
174
target_transport = a_dir.root_transport.clone('..').clone('target')
175
target = a_dir.clone_on_transport(target_transport)
176
self.assertNotEqual(a_dir.transport.base, target.transport.base)
177
self.assertDirectoriesEqual(a_dir.root_transport, target.root_transport,
178
['./.bzr/merge-hashes'])
180
def test_clone_bzrdir_empty(self):
181
dir = self.make_bzrdir('source')
182
target = dir.clone(self.get_url('target'))
183
self.assertNotEqual(dir.transport.base, target.transport.base)
184
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
185
['./.bzr/merge-hashes'])
187
def test_clone_bzrdir_empty_force_new_ignored(self):
188
# the force_new_repo parameter should have no effect on an empty
189
# bzrdir's clone logic
190
dir = self.make_bzrdir('source')
191
target = dir.clone(self.get_url('target'), force_new_repo=True)
192
self.assertNotEqual(dir.transport.base, target.transport.base)
193
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
194
['./.bzr/merge-hashes'])
196
def test_clone_bzrdir_repository(self):
197
tree = self.make_branch_and_tree('commit_tree')
198
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
200
tree.commit('revision 1', rev_id='1')
201
dir = self.make_bzrdir('source')
202
repo = dir.create_repository()
203
repo.fetch(tree.branch.repository)
204
self.assertTrue(repo.has_revision('1'))
205
target = dir.clone(self.get_url('target'))
206
self.assertNotEqual(dir.transport.base, target.transport.base)
207
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
209
'./.bzr/merge-hashes',
212
self.assertRepositoryHasSameItems(tree.branch.repository,
213
target.open_repository())
215
def test_clone_bzrdir_tree_branch_repo(self):
216
tree = self.make_branch_and_tree('source')
217
self.build_tree(['source/foo'])
219
tree.commit('revision 1')
221
target = dir.clone(self.get_url('target'))
222
self.skipIfNoWorkingTree(target)
223
self.assertNotEqual(dir.transport.base, target.transport.base)
224
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
225
['./.bzr/stat-cache',
226
'./.bzr/checkout/dirstate',
227
'./.bzr/checkout/stat-cache',
228
'./.bzr/checkout/merge-hashes',
229
'./.bzr/merge-hashes',
232
self.assertRepositoryHasSameItems(tree.branch.repository,
233
target.open_repository())
234
target.open_workingtree().revert()
236
def test_revert_inventory(self):
237
tree = self.make_branch_and_tree('source')
238
self.build_tree(['source/foo'])
240
tree.commit('revision 1')
242
target = dir.clone(self.get_url('target'))
243
self.skipIfNoWorkingTree(target)
244
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
245
['./.bzr/stat-cache',
246
'./.bzr/checkout/dirstate',
247
'./.bzr/checkout/stat-cache',
248
'./.bzr/checkout/merge-hashes',
249
'./.bzr/merge-hashes',
252
self.assertRepositoryHasSameItems(tree.branch.repository,
253
target.open_repository())
255
target.open_workingtree().revert()
256
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
257
['./.bzr/stat-cache',
258
'./.bzr/checkout/dirstate',
259
'./.bzr/checkout/stat-cache',
260
'./.bzr/checkout/merge-hashes',
261
'./.bzr/merge-hashes',
264
self.assertRepositoryHasSameItems(tree.branch.repository,
265
target.open_repository())
267
def test_clone_bzrdir_tree_branch_reference(self):
268
# a tree with a branch reference (aka a checkout)
269
# should stay a checkout on clone.
270
referenced_branch = self.make_branch('referencced')
271
dir = self.make_bzrdir('source')
273
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
274
target_branch=referenced_branch)
275
except errors.IncompatibleFormat:
276
# this is ok too, not all formats have to support references.
278
self.createWorkingTreeOrSkip(dir)
279
target = dir.clone(self.get_url('target'))
280
self.skipIfNoWorkingTree(target)
281
self.assertNotEqual(dir.transport.base, target.transport.base)
282
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
283
['./.bzr/stat-cache',
284
'./.bzr/checkout/stat-cache',
285
'./.bzr/checkout/merge-hashes',
286
'./.bzr/merge-hashes',
287
'./.bzr/repository/inventory.knit',
290
def test_clone_bzrdir_branch_and_repo_into_shared_repo_force_new_repo(self):
291
# by default cloning into a shared repo uses the shared repo.
292
tree = self.make_branch_and_tree('commit_tree')
293
self.build_tree(['commit_tree/foo'])
295
tree.commit('revision 1')
296
source = self.make_branch('source')
297
tree.branch.repository.copy_content_into(source.repository)
298
tree.branch.copy_content_into(source)
300
self.make_repository('target', shared=True)
301
except errors.IncompatibleFormat:
304
target = dir.clone(self.get_url('target/child'), force_new_repo=True)
305
self.assertNotEqual(dir.transport.base, target.transport.base)
306
repo = target.open_repository()
307
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
308
['./.bzr/repository',
310
self.assertRepositoryHasSameItems(tree.branch.repository, repo)
312
def test_clone_bzrdir_branch_reference(self):
313
# cloning should preserve the reference status of the branch in a bzrdir
314
referenced_branch = self.make_branch('referencced')
315
dir = self.make_bzrdir('source')
317
reference = bzrlib.branch.BranchReferenceFormat().initialize(dir,
318
target_branch=referenced_branch)
319
except errors.IncompatibleFormat:
320
# this is ok too, not all formats have to support references.
322
target = dir.clone(self.get_url('target'))
323
self.assertNotEqual(dir.transport.base, target.transport.base)
324
self.assertDirectoriesEqual(dir.root_transport, target.root_transport)
326
def test_sprout_bzrdir_repository(self):
327
tree = self.make_branch_and_tree('commit_tree')
328
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
330
tree.commit('revision 1', rev_id='1')
331
dir = self.make_bzrdir('source')
332
repo = dir.create_repository()
333
repo.fetch(tree.branch.repository)
334
self.assertTrue(repo.has_revision('1'))
337
_mod_revision.is_null(_mod_revision.ensure_null(
338
dir.open_branch().last_revision())))
339
except errors.NotBranchError:
341
target = dir.sprout(self.get_url('target'))
342
self.assertNotEqual(dir.transport.base, target.transport.base)
343
# testing inventory isn't reasonable for repositories
344
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
350
'./.bzr/repository/inventory.knit',
353
local_inventory = dir.transport.local_abspath('inventory')
354
except errors.NotLocalUrl:
357
# If we happen to have a tree, we'll guarantee everything
358
# except for the tree root is the same.
359
inventory_f = file(local_inventory, 'rb')
360
self.addCleanup(inventory_f.close)
361
self.assertContainsRe(inventory_f.read(),
362
'<inventory format="5">\n</inventory>\n')
364
if e.errno != errno.ENOENT:
367
def test_sprout_bzrdir_branch_and_repo(self):
368
tree = self.make_branch_and_tree('commit_tree')
369
self.build_tree(['commit_tree/foo'])
371
tree.commit('revision 1')
372
source = self.make_branch('source')
373
tree.branch.repository.copy_content_into(source.repository)
374
tree.bzrdir.open_branch().copy_content_into(source)
376
target = dir.sprout(self.get_url('target'))
377
self.assertNotEqual(dir.transport.base, target.transport.base)
378
target_repo = target.open_repository()
379
self.assertRepositoryHasSameItems(source.repository, target_repo)
380
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
382
'./.bzr/basis-inventory-cache',
383
'./.bzr/branch/branch.conf',
384
'./.bzr/branch/parent',
386
'./.bzr/checkout/inventory',
387
'./.bzr/checkout/stat-cache',
395
def test_sprout_bzrdir_tree_branch_repo(self):
396
tree = self.make_branch_and_tree('source')
397
self.build_tree(['foo'], transport=tree.bzrdir.transport.clone('..'))
399
tree.commit('revision 1')
401
target = self.sproutOrSkip(dir, self.get_url('target'))
402
self.assertNotEqual(dir.transport.base, target.transport.base)
403
self.assertDirectoriesEqual(dir.root_transport, target.root_transport,
405
'./.bzr/branch/branch.conf',
406
'./.bzr/branch/parent',
407
'./.bzr/checkout/dirstate',
408
'./.bzr/checkout/stat-cache',
409
'./.bzr/checkout/inventory',
415
self.assertRepositoryHasSameItems(
416
tree.branch.repository, target.open_repository())
419
def test_retire_bzrdir(self):
420
bd = self.make_bzrdir('.')
421
transport = bd.root_transport
422
# must not overwrite existing directories
423
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
425
self.failUnless(transport.has('.bzr'))
427
self.failIf(transport.has('.bzr'))
428
self.failUnless(transport.has('.bzr.retired.1'))
430
def test_retire_bzrdir_limited(self):
431
bd = self.make_bzrdir('.')
432
transport = bd.root_transport
433
# must not overwrite existing directories
434
self.build_tree(['.bzr.retired.0/', '.bzr.retired.0/junk',],
436
self.failUnless(transport.has('.bzr'))
437
self.assertRaises((errors.FileExists, errors.DirectoryNotEmpty),
438
bd.retire_bzrdir, limit=0)