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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests of the parent related functions of WorkingTrees."""
19
from cStringIO import StringIO
19
from errno import EEXIST
22
22
from bzrlib import (
24
25
revision as _mod_revision,
26
28
from bzrlib.inventory import (
29
31
InventoryDirectory,
32
from bzrlib.revisiontree import InventoryRevisionTree
33
from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
34
from bzrlib.tests import (
34
from bzrlib.revision import Revision
35
from bzrlib.tests import SymlinkFeature, TestNotApplicable
36
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
37
37
from bzrlib.uncommit import uncommit
227
227
(rev3, rev_tree3)])
228
228
self.assertConsistentParents([rev2, rev3], t)
230
def test_unicode_symlink(self):
231
# this tests bug #272444
232
self.requireFeature(features.SymlinkFeature)
233
self.requireFeature(features.UnicodeFilenameFeature)
235
tree = self.make_branch_and_tree('tree1')
237
# The link points to a file whose name is an omega
238
# U+03A9 GREEK CAPITAL LETTER OMEGA
239
# UTF-8: ce a9 UTF-16BE: 03a9 Decimal: Ω
241
link_name = u'\N{Euro Sign}link'
242
os.symlink(target, 'tree1/' + link_name)
243
tree.add([link_name], ['link-id'])
245
revision1 = tree.commit('added a link to a Unicode target')
246
revision2 = tree.commit('this revision will be discarded')
247
tree.set_parent_ids([revision1])
249
self.addCleanup(tree.unlock)
250
# Check that the symlink target is safely round-tripped in the trees.
251
self.assertEqual(target, tree.get_symlink_target('link-id'))
252
basis = tree.basis_tree()
253
self.assertEqual(target, basis.get_symlink_target('link-id'))
256
231
class TestAddParent(TestParents):
262
237
uncommit(tree.branch, tree=tree)
263
238
tree.add_parent_tree_id(first_revision)
264
239
self.assertConsistentParents([first_revision], tree)
266
241
def test_add_first_parent_id_ghost_rejects(self):
267
242
"""Test adding the first parent id - as a ghost"""
268
243
tree = self.make_branch_and_tree('.')
269
244
self.assertRaises(errors.GhostRevisionUnusableHere,
270
245
tree.add_parent_tree_id, 'first-revision')
272
247
def test_add_first_parent_id_ghost_force(self):
273
248
"""Test adding the first parent id - as a ghost"""
274
249
tree = self.make_branch_and_tree('.')
281
256
tree.add_parent_tree_id('first-revision', allow_leftmost_as_ghost=True)
282
257
tree.add_parent_tree_id('second')
283
258
self.assertConsistentParents(['first-revision', 'second'], tree)
285
260
def test_add_second_parent_id(self):
286
261
"""Test adding the second parent id"""
287
262
tree = self.make_branch_and_tree('.')
290
265
second_revision = tree.commit('second post')
291
266
tree.add_parent_tree_id(first_revision)
292
267
self.assertConsistentParents([second_revision, first_revision], tree)
294
269
def test_add_second_parent_id_ghost(self):
295
270
"""Test adding the second parent id - as a ghost"""
296
271
tree = self.make_branch_and_tree('.')
297
272
first_revision = tree.commit('first post')
298
273
tree.add_parent_tree_id('second')
299
274
self.assertConsistentParents([first_revision, 'second'], tree)
301
276
def test_add_first_parent_tree(self):
302
277
"""Test adding the first parent id"""
303
278
tree = self.make_branch_and_tree('.')
306
281
tree.add_parent_tree((first_revision,
307
282
tree.branch.repository.revision_tree(first_revision)))
308
283
self.assertConsistentParents([first_revision], tree)
310
285
def test_add_first_parent_tree_ghost_rejects(self):
311
286
"""Test adding the first parent id - as a ghost"""
312
287
tree = self.make_branch_and_tree('.')
313
288
self.assertRaises(errors.GhostRevisionUnusableHere,
314
289
tree.add_parent_tree, ('first-revision', None))
316
291
def test_add_first_parent_tree_ghost_force(self):
317
292
"""Test adding the first parent id - as a ghost"""
318
293
tree = self.make_branch_and_tree('.')
319
294
tree.add_parent_tree(('first-revision', None),
320
295
allow_leftmost_as_ghost=True)
321
296
self.assertConsistentParents(['first-revision'], tree)
323
298
def test_add_second_parent_tree(self):
324
299
"""Test adding the second parent id"""
325
300
tree = self.make_branch_and_tree('.')
329
304
tree.add_parent_tree((first_revision,
330
305
tree.branch.repository.revision_tree(first_revision)))
331
306
self.assertConsistentParents([second_revision, first_revision], tree)
333
308
def test_add_second_parent_tree_ghost(self):
334
309
"""Test adding the second parent id - as a ghost"""
335
310
tree = self.make_branch_and_tree('.')
341
316
class UpdateToOneParentViaDeltaTests(TestCaseWithWorkingTree):
342
317
"""Tests for the update_basis_by_delta call.
344
319
This is intuitively defined as 'apply an inventory delta to the basis and
345
320
discard other parents', but for trees that have an inventory that is not
346
321
managed as a tree-by-id, the implementation requires roughly duplicated
363
338
result_basis = tree.basis_tree()
364
339
result_basis.lock_read()
366
self.assertEqual(expected_inventory, result_basis.root_inventory)
368
result_basis.unlock()
340
self.addCleanup(result_basis.unlock)
341
self.assertEqual(expected_inventory, result_basis.inventory)
370
343
def make_inv_delta(self, old, new):
371
344
"""Make an inventory delta from two inventories."""
388
361
def fake_up_revision(self, tree, revid, shape):
390
class ShapeTree(InventoryRevisionTree):
392
def __init__(self, shape):
393
self._repository = tree.branch.repository
394
self._inventory = shape
396
def get_file_text(self, file_id, path=None):
397
ie = self.root_inventory[file_id]
398
if ie.kind != "file":
400
return 'a' * ie.text_size
402
def get_file(self, file_id, path=None):
403
return StringIO(self.get_file_text(file_id))
405
362
tree.lock_write()
407
if shape.root.revision is None:
408
shape.root.revision = revid
409
builder = tree.branch.get_commit_builder(
413
committer="Foo Bar <foo@example.com>",
415
shape_tree = ShapeTree(shape)
416
base_tree = tree.branch.repository.revision_tree(
417
_mod_revision.NULL_REVISION)
418
changes = shape_tree.iter_changes(
420
list(builder.record_iter_changes(shape_tree,
421
base_tree.get_revision_id(), changes))
422
builder.finish_inventory()
423
builder.commit("Message")
364
tree.branch.repository.start_write_group()
366
if shape.root.revision is None:
367
shape.root.revision = revid
368
sha1 = tree.branch.repository.add_inventory(revid, shape, [])
369
rev = Revision(timestamp=0,
371
committer="Foo Bar <foo@example.com>",
375
tree.branch.repository.add_revision(revid, rev)
377
tree.branch.repository.abort_write_group()
380
tree.branch.repository.commit_write_group()
450
407
self.add_dir(new_shape, new_revid, 'root-id', None, '')
452
409
def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
453
new_shape, new_revid, extra_parent=None, set_current_inventory=True):
410
new_shape, new_revid, extra_parent=None):
454
411
# set the inventory revision ids.
455
412
basis_shape.revision_id = basis_revid
456
413
new_shape.revision_id = new_revid
465
422
parents.append(extra_parent)
466
423
tree.set_parent_ids(parents)
467
424
self.fake_up_revision(tree, new_revid, new_shape)
468
if set_current_inventory:
469
# give tree an inventory of new_shape
470
tree._write_inventory(new_shape)
425
# give tree an inventory of new_shape
426
tree._write_inventory(new_shape)
471
427
self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
472
428
delta, new_shape)
473
429
# The tree should be internally consistent; while this is a moderately
474
430
# large hammer, this is a particularly sensitive area of code, so the
475
431
# extra assurance is well worth it.
477
# If tree.branch is remote
478
if tree.user_url != tree.branch.user_url:
479
# We have a lightweight checkout, delete both locations
480
tree.branch.bzrdir.root_transport.delete_tree('.')
481
tree.bzrdir.root_transport.delete_tree('.')
433
osutils.rmtree('tree')
483
435
def test_no_parents_just_root(self):
484
436
"""Test doing an empty commit - no parent, set a root only."""
485
basis_shape = Inventory(root_id=None) # empty tree
486
new_shape = Inventory() # tree with a root
437
basis_shape = Inventory(root_id=None) # empty tree
438
new_shape = Inventory() # tree with a root
487
439
self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
534
486
def do_file(inv, revid):
535
487
self.add_file(inv, revid, 'path-id', 'root-id', 'path', '1' * 32,
538
489
def do_link(inv, revid):
539
490
self.add_link(inv, revid, 'path-id', 'root-id', 'path', 'target')
541
491
def do_dir(inv, revid):
542
492
self.add_dir(inv, revid, 'path-id', 'root-id', 'path')
544
493
for old_factory in (do_file, do_link, do_dir):
545
494
for new_factory in (do_file, do_link, do_dir):
546
495
if old_factory == new_factory:
765
714
self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
766
715
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
767
716
new_shape, new_revid)
769
def test_add_files_to_empty_directory(self):
770
old_revid = 'old-parent'
771
basis_shape = Inventory(root_id=None)
772
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
773
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
774
new_revid = 'new-parent'
775
new_shape = Inventory(root_id=None)
776
self.add_new_root(new_shape, old_revid, new_revid)
777
self.add_dir(new_shape, old_revid, 'dir-id-A', 'root-id', 'A')
778
self.add_file(new_shape, new_revid, 'file-id-B', 'dir-id-A', 'B',
780
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
781
new_shape, new_revid, set_current_inventory=False)