85
TransformRenameFailed,
89
class TestTreeTransform(tests.TestCaseWithTransport):
92
super(TestTreeTransform, self).setUp()
93
self.wt = self.make_branch_and_tree('.', format='development-subtree')
96
def get_transform(self):
97
transform = TreeTransform(self.wt)
98
self.addCleanup(transform.finalize)
99
return transform, transform.root
101
def get_transform_for_sha1_test(self):
102
trans, root = self.get_transform()
103
self.wt.lock_tree_write()
104
self.addCleanup(self.wt.unlock)
105
contents = [b'just some content\n']
106
sha1 = osutils.sha_strings(contents)
107
# Roll back the clock
108
trans._creation_mtime = time.time() - 20.0
109
return trans, root, contents, sha1
111
def test_existing_limbo(self):
112
transform, root = self.get_transform()
113
limbo_name = transform._limbodir
114
deletion_path = transform._deletiondir
115
os.mkdir(pathjoin(limbo_name, 'hehe'))
116
self.assertRaises(ImmortalLimbo, transform.apply)
117
self.assertRaises(LockError, self.wt.unlock)
118
self.assertRaises(ExistingLimbo, self.get_transform)
119
self.assertRaises(LockError, self.wt.unlock)
120
os.rmdir(pathjoin(limbo_name, 'hehe'))
122
os.rmdir(deletion_path)
123
transform, root = self.get_transform()
126
def test_existing_pending_deletion(self):
127
transform, root = self.get_transform()
128
deletion_path = self._limbodir = urlutils.local_path_from_url(
129
transform._tree._transport.abspath('pending-deletion'))
130
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
131
self.assertRaises(ImmortalPendingDeletion, transform.apply)
132
self.assertRaises(LockError, self.wt.unlock)
133
self.assertRaises(ExistingPendingDeletion, self.get_transform)
135
def test_build(self):
136
transform, root = self.get_transform()
137
self.wt.lock_tree_write()
138
self.addCleanup(self.wt.unlock)
139
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
140
imaginary_id = transform.trans_id_tree_path('imaginary')
141
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
142
self.assertEqual(imaginary_id, imaginary_id2)
143
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
144
self.assertEqual('directory', transform.final_kind(root))
145
self.assertEqual(self.wt.path2id(''), transform.final_file_id(root))
146
trans_id = transform.create_path('name', root)
147
self.assertIs(transform.final_file_id(trans_id), None)
148
self.assertIs(None, transform.final_kind(trans_id))
149
transform.create_file([b'contents'], trans_id)
150
transform.set_executability(True, trans_id)
151
transform.version_file(b'my_pretties', trans_id)
152
self.assertRaises(DuplicateKey, transform.version_file,
153
b'my_pretties', trans_id)
154
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
155
self.assertEqual(transform.final_parent(trans_id), root)
156
self.assertIs(transform.final_parent(root), ROOT_PARENT)
157
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
158
oz_id = transform.create_path('oz', root)
159
transform.create_directory(oz_id)
160
transform.version_file(b'ozzie', oz_id)
161
trans_id2 = transform.create_path('name2', root)
162
transform.create_file([b'contents'], trans_id2)
163
transform.set_executability(False, trans_id2)
164
transform.version_file(b'my_pretties2', trans_id2)
165
modified_paths = transform.apply().modified_paths
166
with self.wt.get_file('name') as f:
167
self.assertEqual(b'contents', f.read())
168
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
169
self.assertIs(self.wt.is_executable('name'), True)
170
self.assertIs(self.wt.is_executable('name2'), False)
171
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
172
self.assertEqual(len(modified_paths), 3)
173
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
174
(b'ozzie', b'my_pretties', b'my_pretties2')]
175
self.assertSubset(tree_mod_paths, modified_paths)
176
# is it safe to finalize repeatedly?
180
def test_apply_informs_tree_of_observed_sha1(self):
181
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
182
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
185
orig = self.wt._observed_sha1
187
def _observed_sha1(*args):
190
self.wt._observed_sha1 = _observed_sha1
192
self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
195
def test_create_file_caches_sha1(self):
196
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
197
trans_id = trans.create_path('file1', root)
198
trans.create_file(contents, trans_id, sha1=sha1)
199
st_val = osutils.lstat(trans._limbo_name(trans_id))
200
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
201
self.assertEqual(o_sha1, sha1)
202
self.assertEqualStat(o_st_val, st_val)
204
def test__apply_insertions_updates_sha1(self):
205
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
206
trans_id = trans.create_path('file1', root)
207
trans.create_file(contents, trans_id, sha1=sha1)
208
st_val = osutils.lstat(trans._limbo_name(trans_id))
209
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
210
self.assertEqual(o_sha1, sha1)
211
self.assertEqualStat(o_st_val, st_val)
212
creation_mtime = trans._creation_mtime + 10.0
213
# We fake a time difference from when the file was created until now it
214
# is being renamed by using os.utime. Note that the change we actually
215
# want to see is the real ctime change from 'os.rename()', but as long
216
# as we observe a new stat value, we should be fine.
217
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
219
new_st_val = osutils.lstat(self.wt.abspath('file1'))
220
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
221
self.assertEqual(o_sha1, sha1)
222
self.assertEqualStat(o_st_val, new_st_val)
223
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
225
def test_new_file_caches_sha1(self):
226
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
227
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
229
st_val = osutils.lstat(trans._limbo_name(trans_id))
230
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
231
self.assertEqual(o_sha1, sha1)
232
self.assertEqualStat(o_st_val, st_val)
234
def test_cancel_creation_removes_observed_sha1(self):
235
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
236
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
238
self.assertTrue(trans_id in trans._observed_sha1s)
239
trans.cancel_creation(trans_id)
240
self.assertFalse(trans_id in trans._observed_sha1s)
242
def test_create_files_same_timestamp(self):
243
transform, root = self.get_transform()
244
self.wt.lock_tree_write()
245
self.addCleanup(self.wt.unlock)
246
# Roll back the clock, so that we know everything is being set to the
248
transform._creation_mtime = creation_mtime = time.time() - 20.0
249
transform.create_file([b'content-one'],
250
transform.create_path('one', root))
251
time.sleep(1) # *ugly*
252
transform.create_file([b'content-two'],
253
transform.create_path('two', root))
255
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
257
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
259
# We only guarantee 2s resolution
261
abs(creation_mtime - st1.st_mtime) < 2.0,
262
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
263
# But if we have more than that, all files should get the same result
264
self.assertEqual(st1.st_mtime, st2.st_mtime)
266
def test_change_root_id(self):
267
transform, root = self.get_transform()
268
self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
269
transform.new_directory('', ROOT_PARENT, b'new-root-id')
270
transform.delete_contents(root)
271
transform.unversion_file(root)
272
transform.fixup_new_roots()
274
self.assertEqual(b'new-root-id', self.wt.path2id(''))
276
def test_change_root_id_add_files(self):
277
transform, root = self.get_transform()
278
self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
279
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
280
transform.new_file('file', new_trans_id, [b'new-contents\n'],
282
transform.delete_contents(root)
283
transform.unversion_file(root)
284
transform.fixup_new_roots()
286
self.assertEqual(b'new-root-id', self.wt.path2id(''))
287
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
288
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
290
def test_add_two_roots(self):
291
transform, root = self.get_transform()
292
transform.new_directory('', ROOT_PARENT, b'new-root-id')
293
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
294
self.assertRaises(ValueError, transform.fixup_new_roots)
296
def test_retain_existing_root(self):
297
tt, root = self.get_transform()
299
tt.new_directory('', ROOT_PARENT, b'new-root-id')
301
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
303
def test_retain_existing_root_added_file(self):
304
tt, root = self.get_transform()
305
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
306
child = tt.new_directory('child', new_trans_id, b'child-id')
308
self.assertEqual(tt.root, tt.final_parent(child))
310
def test_add_unversioned_root(self):
311
transform, root = self.get_transform()
312
transform.new_directory('', ROOT_PARENT, None)
313
transform.delete_contents(transform.root)
314
transform.fixup_new_roots()
315
self.assertNotIn(transform.root, transform._new_id)
317
def test_remove_root_fixup(self):
318
transform, root = self.get_transform()
319
old_root_id = self.wt.path2id('')
320
self.assertNotEqual(b'new-root-id', old_root_id)
321
transform.delete_contents(root)
322
transform.unversion_file(root)
323
transform.fixup_new_roots()
325
self.assertEqual(old_root_id, self.wt.path2id(''))
327
transform, root = self.get_transform()
328
transform.new_directory('', ROOT_PARENT, b'new-root-id')
329
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
330
self.assertRaises(ValueError, transform.fixup_new_roots)
332
def test_fixup_new_roots_permits_empty_tree(self):
333
transform, root = self.get_transform()
334
transform.delete_contents(root)
335
transform.unversion_file(root)
336
transform.fixup_new_roots()
337
self.assertIs(None, transform.final_kind(root))
338
self.assertIs(None, transform.final_file_id(root))
340
def test_apply_retains_root_directory(self):
341
# Do not attempt to delete the physical root directory, because that
343
transform, root = self.get_transform()
345
transform.delete_contents(root)
346
e = self.assertRaises(AssertionError, self.assertRaises,
347
errors.TransformRenameFailed,
349
self.assertContainsRe('TransformRenameFailed not raised', str(e))
351
def test_apply_retains_file_id(self):
352
transform, root = self.get_transform()
353
old_root_id = transform.tree_file_id(root)
354
transform.unversion_file(root)
356
self.assertEqual(old_root_id, self.wt.path2id(''))
358
def test_hardlink(self):
359
self.requireFeature(HardlinkFeature)
360
transform, root = self.get_transform()
361
transform.new_file('file1', root, [b'contents'])
363
target = self.make_branch_and_tree('target')
364
target_transform = TreeTransform(target)
365
trans_id = target_transform.create_path('file1', target_transform.root)
366
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
367
target_transform.apply()
368
self.assertPathExists('target/file1')
369
source_stat = os.stat(self.wt.abspath('file1'))
370
target_stat = os.stat('target/file1')
371
self.assertEqual(source_stat, target_stat)
373
def test_convenience(self):
374
transform, root = self.get_transform()
375
self.wt.lock_tree_write()
376
self.addCleanup(self.wt.unlock)
377
transform.new_file('name', root, [b'contents'], b'my_pretties', True)
378
oz = transform.new_directory('oz', root, b'oz-id')
379
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
380
transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
383
self.assertEqual(len(transform.find_conflicts()), 0)
385
self.assertRaises(ReusingTransform, transform.find_conflicts)
386
with open(self.wt.abspath('name'), 'r') as f:
387
self.assertEqual('contents', f.read())
388
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
389
self.assertIs(self.wt.is_executable('name'), True)
390
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
391
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
392
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
394
self.assertEqual(b'toto-contents',
395
self.wt.get_file('oz/dorothy/toto').read())
396
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
398
def test_tree_reference(self):
399
transform, root = self.get_transform()
400
tree = transform._tree
401
trans_id = transform.new_directory('reference', root, b'subtree-id')
402
transform.set_tree_reference(b'subtree-revision', trans_id)
405
self.addCleanup(tree.unlock)
408
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
410
def test_conflicts(self):
411
transform, root = self.get_transform()
412
trans_id = transform.new_file('name', root, [b'contents'],
414
self.assertEqual(len(transform.find_conflicts()), 0)
415
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
416
self.assertEqual(transform.find_conflicts(),
417
[('duplicate', trans_id, trans_id2, 'name')])
418
self.assertRaises(MalformedTransform, transform.apply)
419
transform.adjust_path('name', trans_id, trans_id2)
420
self.assertEqual(transform.find_conflicts(),
421
[('non-directory parent', trans_id)])
422
tinman_id = transform.trans_id_tree_path('tinman')
423
transform.adjust_path('name', tinman_id, trans_id2)
424
self.assertEqual(transform.find_conflicts(),
425
[('unversioned parent', tinman_id),
426
('missing parent', tinman_id)])
427
lion_id = transform.create_path('lion', root)
428
self.assertEqual(transform.find_conflicts(),
429
[('unversioned parent', tinman_id),
430
('missing parent', tinman_id)])
431
transform.adjust_path('name', lion_id, trans_id2)
432
self.assertEqual(transform.find_conflicts(),
433
[('unversioned parent', lion_id),
434
('missing parent', lion_id)])
435
transform.version_file(b"Courage", lion_id)
436
self.assertEqual(transform.find_conflicts(),
437
[('missing parent', lion_id),
438
('versioning no contents', lion_id)])
439
transform.adjust_path('name2', root, trans_id2)
440
self.assertEqual(transform.find_conflicts(),
441
[('versioning no contents', lion_id)])
442
transform.create_file([b'Contents, okay?'], lion_id)
443
transform.adjust_path('name2', trans_id2, trans_id2)
444
self.assertEqual(transform.find_conflicts(),
445
[('parent loop', trans_id2),
446
('non-directory parent', trans_id2)])
447
transform.adjust_path('name2', root, trans_id2)
448
oz_id = transform.new_directory('oz', root)
449
transform.set_executability(True, oz_id)
450
self.assertEqual(transform.find_conflicts(),
451
[('unversioned executability', oz_id)])
452
transform.version_file(b'oz-id', oz_id)
453
self.assertEqual(transform.find_conflicts(),
454
[('non-file executability', oz_id)])
455
transform.set_executability(None, oz_id)
456
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
458
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
459
with open(self.wt.abspath('name'), 'rb') as f:
460
self.assertEqual(b'contents', f.read())
461
transform2, root = self.get_transform()
462
oz_id = transform2.trans_id_tree_path('oz')
463
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
464
result = transform2.find_conflicts()
465
fp = FinalPaths(transform2)
466
self.assertTrue('oz/tip' in transform2._tree_path_ids)
467
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
468
self.assertEqual(len(result), 2)
469
self.assertEqual((result[0][0], result[0][1]),
470
('duplicate', newtip))
471
self.assertEqual((result[1][0], result[1][2]),
472
('duplicate id', newtip))
473
transform2.finalize()
474
transform3 = TreeTransform(self.wt)
475
self.addCleanup(transform3.finalize)
476
oz_id = transform3.trans_id_tree_path('oz')
477
transform3.delete_contents(oz_id)
478
self.assertEqual(transform3.find_conflicts(),
479
[('missing parent', oz_id)])
480
root_id = transform3.root
481
tip_id = transform3.trans_id_tree_path('oz/tip')
482
transform3.adjust_path('tip', root_id, tip_id)
485
def test_conflict_on_case_insensitive(self):
486
tree = self.make_branch_and_tree('tree')
487
# Don't try this at home, kids!
488
# Force the tree to report that it is case sensitive, for conflict
490
tree.case_sensitive = True
491
transform = TreeTransform(tree)
492
self.addCleanup(transform.finalize)
493
transform.new_file('file', transform.root, [b'content'])
494
transform.new_file('FiLe', transform.root, [b'content'])
495
result = transform.find_conflicts()
496
self.assertEqual([], result)
498
# Force the tree to report that it is case insensitive, for conflict
500
tree.case_sensitive = False
501
transform = TreeTransform(tree)
502
self.addCleanup(transform.finalize)
503
transform.new_file('file', transform.root, [b'content'])
504
transform.new_file('FiLe', transform.root, [b'content'])
505
result = transform.find_conflicts()
506
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
508
def test_conflict_on_case_insensitive_existing(self):
509
tree = self.make_branch_and_tree('tree')
510
self.build_tree(['tree/FiLe'])
511
# Don't try this at home, kids!
512
# Force the tree to report that it is case sensitive, for conflict
514
tree.case_sensitive = True
515
transform = TreeTransform(tree)
516
self.addCleanup(transform.finalize)
517
transform.new_file('file', transform.root, [b'content'])
518
result = transform.find_conflicts()
519
self.assertEqual([], result)
521
# Force the tree to report that it is case insensitive, for conflict
523
tree.case_sensitive = False
524
transform = TreeTransform(tree)
525
self.addCleanup(transform.finalize)
526
transform.new_file('file', transform.root, [b'content'])
527
result = transform.find_conflicts()
528
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
530
def test_resolve_case_insensitive_conflict(self):
531
tree = self.make_branch_and_tree('tree')
532
# Don't try this at home, kids!
533
# Force the tree to report that it is case insensitive, for conflict
535
tree.case_sensitive = False
536
transform = TreeTransform(tree)
537
self.addCleanup(transform.finalize)
538
transform.new_file('file', transform.root, [b'content'])
539
transform.new_file('FiLe', transform.root, [b'content'])
540
resolve_conflicts(transform)
542
self.assertPathExists('tree/file')
543
self.assertPathExists('tree/FiLe.moved')
545
def test_resolve_checkout_case_conflict(self):
546
tree = self.make_branch_and_tree('tree')
547
# Don't try this at home, kids!
548
# Force the tree to report that it is case insensitive, for conflict
550
tree.case_sensitive = False
551
transform = TreeTransform(tree)
552
self.addCleanup(transform.finalize)
553
transform.new_file('file', transform.root, [b'content'])
554
transform.new_file('FiLe', transform.root, [b'content'])
555
resolve_conflicts(transform,
556
pass_func=lambda t, c: resolve_checkout(t, c, []))
558
self.assertPathExists('tree/file')
559
self.assertPathExists('tree/FiLe.moved')
561
def test_apply_case_conflict(self):
562
"""Ensure that a transform with case conflicts can always be applied"""
563
tree = self.make_branch_and_tree('tree')
564
transform = TreeTransform(tree)
565
self.addCleanup(transform.finalize)
566
transform.new_file('file', transform.root, [b'content'])
567
transform.new_file('FiLe', transform.root, [b'content'])
568
dir = transform.new_directory('dir', transform.root)
569
transform.new_file('dirfile', dir, [b'content'])
570
transform.new_file('dirFiLe', dir, [b'content'])
571
resolve_conflicts(transform)
573
self.assertPathExists('tree/file')
574
if not os.path.exists('tree/FiLe.moved'):
575
self.assertPathExists('tree/FiLe')
576
self.assertPathExists('tree/dir/dirfile')
577
if not os.path.exists('tree/dir/dirFiLe.moved'):
578
self.assertPathExists('tree/dir/dirFiLe')
580
def test_case_insensitive_limbo(self):
581
tree = self.make_branch_and_tree('tree')
582
# Don't try this at home, kids!
583
# Force the tree to report that it is case insensitive
584
tree.case_sensitive = False
585
transform = TreeTransform(tree)
586
self.addCleanup(transform.finalize)
587
dir = transform.new_directory('dir', transform.root)
588
first = transform.new_file('file', dir, [b'content'])
589
second = transform.new_file('FiLe', dir, [b'content'])
590
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
591
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
593
def test_adjust_path_updates_child_limbo_names(self):
594
tree = self.make_branch_and_tree('tree')
595
transform = TreeTransform(tree)
596
self.addCleanup(transform.finalize)
597
foo_id = transform.new_directory('foo', transform.root)
598
bar_id = transform.new_directory('bar', foo_id)
599
baz_id = transform.new_directory('baz', bar_id)
600
qux_id = transform.new_directory('qux', baz_id)
601
transform.adjust_path('quxx', foo_id, bar_id)
602
self.assertStartsWith(transform._limbo_name(qux_id),
603
transform._limbo_name(bar_id))
605
def test_add_del(self):
606
start, root = self.get_transform()
607
start.new_directory('a', root, b'a')
609
transform, root = self.get_transform()
610
transform.delete_versioned(transform.trans_id_tree_path('a'))
611
transform.new_directory('a', root, b'a')
614
def test_unversioning(self):
615
create_tree, root = self.get_transform()
616
parent_id = create_tree.new_directory('parent', root, b'parent-id')
617
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
619
unversion = TreeTransform(self.wt)
620
self.addCleanup(unversion.finalize)
621
parent = unversion.trans_id_tree_path('parent')
622
unversion.unversion_file(parent)
623
self.assertEqual(unversion.find_conflicts(),
624
[('unversioned parent', parent_id)])
625
file_id = unversion.trans_id_tree_path('parent/child')
626
unversion.unversion_file(file_id)
629
def test_name_invariants(self):
630
create_tree, root = self.get_transform()
632
root = create_tree.root
633
create_tree.new_file('name1', root, [b'hello1'], b'name1')
634
create_tree.new_file('name2', root, [b'hello2'], b'name2')
635
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
636
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
637
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
638
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
641
mangle_tree, root = self.get_transform()
642
root = mangle_tree.root
644
name1 = mangle_tree.trans_id_tree_path('name1')
645
name2 = mangle_tree.trans_id_tree_path('name2')
646
mangle_tree.adjust_path('name2', root, name1)
647
mangle_tree.adjust_path('name1', root, name2)
649
# tests for deleting parent directories
650
ddir = mangle_tree.trans_id_tree_path('dying_directory')
651
mangle_tree.delete_contents(ddir)
652
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
653
mangle_tree.delete_versioned(dfile)
654
mangle_tree.unversion_file(dfile)
655
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
656
mangle_tree.adjust_path('mfile', root, mfile)
658
# tests for adding parent directories
659
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
660
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
661
mangle_tree.adjust_path('mfile2', newdir, mfile2)
662
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
663
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
664
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
665
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
667
with open(self.wt.abspath('name1'), 'r') as f:
668
self.assertEqual(f.read(), 'hello2')
669
with open(self.wt.abspath('name2'), 'r') as f:
670
self.assertEqual(f.read(), 'hello1')
671
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
672
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
673
with open(mfile2_path, 'r') as f:
674
self.assertEqual(f.read(), 'later2')
675
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
676
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
677
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
678
with open(newfile_path, 'r') as f:
679
self.assertEqual(f.read(), 'hello3')
680
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
681
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
682
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
684
def test_both_rename(self):
685
create_tree, root = self.get_transform()
686
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
687
create_tree.new_file('blackbox.py', newdir, [
688
b'hello1'], b'blackbox-id')
690
mangle_tree, root = self.get_transform()
691
selftest = mangle_tree.trans_id_tree_path('selftest')
692
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
693
mangle_tree.adjust_path('test', root, selftest)
694
mangle_tree.adjust_path('test_too_much', root, selftest)
695
mangle_tree.set_executability(True, blackbox)
698
def test_both_rename2(self):
699
create_tree, root = self.get_transform()
700
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
701
tests = create_tree.new_directory('tests', breezy, b'tests-id')
702
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
703
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
706
mangle_tree, root = self.get_transform()
707
breezy = mangle_tree.trans_id_tree_path('breezy')
708
tests = mangle_tree.trans_id_tree_path('breezy/tests')
709
test_too_much = mangle_tree.trans_id_tree_path(
710
'breezy/tests/blackbox/test_too_much.py')
711
mangle_tree.adjust_path('selftest', breezy, tests)
712
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
713
mangle_tree.set_executability(True, test_too_much)
716
def test_both_rename3(self):
717
create_tree, root = self.get_transform()
718
tests = create_tree.new_directory('tests', root, b'tests-id')
719
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
722
mangle_tree, root = self.get_transform()
723
tests = mangle_tree.trans_id_tree_path('tests')
724
test_too_much = mangle_tree.trans_id_tree_path(
725
'tests/test_too_much.py')
726
mangle_tree.adjust_path('selftest', root, tests)
727
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
728
mangle_tree.set_executability(True, test_too_much)
731
def test_move_dangling_ie(self):
732
create_tree, root = self.get_transform()
734
root = create_tree.root
735
create_tree.new_file('name1', root, [b'hello1'], b'name1')
737
delete_contents, root = self.get_transform()
738
file = delete_contents.trans_id_tree_path('name1')
739
delete_contents.delete_contents(file)
740
delete_contents.apply()
741
move_id, root = self.get_transform()
742
name1 = move_id.trans_id_tree_path('name1')
743
newdir = move_id.new_directory('dir', root, b'newdir')
744
move_id.adjust_path('name2', newdir, name1)
747
def test_replace_dangling_ie(self):
748
create_tree, root = self.get_transform()
750
root = create_tree.root
751
create_tree.new_file('name1', root, [b'hello1'], b'name1')
753
delete_contents = TreeTransform(self.wt)
754
self.addCleanup(delete_contents.finalize)
755
file = delete_contents.trans_id_tree_path('name1')
756
delete_contents.delete_contents(file)
757
delete_contents.apply()
758
delete_contents.finalize()
759
replace = TreeTransform(self.wt)
760
self.addCleanup(replace.finalize)
761
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
762
conflicts = replace.find_conflicts()
763
name1 = replace.trans_id_tree_path('name1')
764
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
765
resolve_conflicts(replace)
768
def _test_symlinks(self, link_name1, link_target1,
769
link_name2, link_target2):
774
self.requireFeature(SymlinkFeature)
775
transform, root = self.get_transform()
776
oz_id = transform.new_directory('oz', root, b'oz-id')
777
transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
778
wiz_id = transform.create_path(link_name2, oz_id)
779
transform.create_symlink(link_target2, wiz_id)
780
transform.version_file(b'wiz-id2', wiz_id)
781
transform.set_executability(True, wiz_id)
782
self.assertEqual(transform.find_conflicts(),
783
[('non-file executability', wiz_id)])
784
transform.set_executability(None, wiz_id)
786
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
787
self.assertEqual('symlink',
788
file_kind(self.wt.abspath(ozpath(link_name1))))
789
self.assertEqual(link_target2,
790
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
791
self.assertEqual(link_target1,
792
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
794
def test_symlinks(self):
795
self._test_symlinks('wizard', 'wizard-target',
796
'wizard2', 'behind_curtain')
798
def test_symlinks_unicode(self):
799
self.requireFeature(features.UnicodeFilenameFeature)
800
self._test_symlinks(u'\N{Euro Sign}wizard',
801
u'wizard-targ\N{Euro Sign}t',
802
u'\N{Euro Sign}wizard2',
803
u'b\N{Euro Sign}hind_curtain')
805
def test_unsupported_symlink_no_conflict(self):
807
wt = self.make_branch_and_tree('.')
808
tt = TreeTransform(wt)
809
self.addCleanup(tt.finalize)
810
tt.new_symlink('foo', tt.root, 'bar')
811
result = tt.find_conflicts()
812
self.assertEqual([], result)
813
os_symlink = getattr(os, 'symlink', None)
819
os.symlink = os_symlink
821
def get_conflicted(self):
822
create, root = self.get_transform()
823
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
824
oz = create.new_directory('oz', root, b'oz-id')
825
create.new_directory('emeraldcity', oz, b'emerald-id')
827
conflicts, root = self.get_transform()
828
# set up duplicate entry, duplicate id
829
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
831
old_dorothy = conflicts.trans_id_tree_path('dorothy')
832
oz = conflicts.trans_id_tree_path('oz')
833
# set up DeletedParent parent conflict
834
conflicts.delete_versioned(oz)
835
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
836
# set up MissingParent conflict
837
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
838
conflicts.adjust_path('munchkincity', root, munchkincity)
839
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
841
conflicts.adjust_path('emeraldcity', emerald, emerald)
842
return conflicts, emerald, oz, old_dorothy, new_dorothy
844
def test_conflict_resolution(self):
845
conflicts, emerald, oz, old_dorothy, new_dorothy =\
846
self.get_conflicted()
847
resolve_conflicts(conflicts)
848
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
849
self.assertIs(conflicts.final_file_id(old_dorothy), None)
850
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
851
self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
852
self.assertEqual(conflicts.final_parent(emerald), oz)
855
def test_cook_conflicts(self):
856
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
857
raw_conflicts = resolve_conflicts(tt)
858
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
859
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
860
'dorothy', None, b'dorothy-id')
861
self.assertEqual(cooked_conflicts[0], duplicate)
862
duplicate_id = DuplicateID('Unversioned existing file',
863
'dorothy.moved', 'dorothy', None,
865
self.assertEqual(cooked_conflicts[1], duplicate_id)
866
missing_parent = MissingParent('Created directory', 'munchkincity',
868
deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
869
self.assertEqual(cooked_conflicts[2], missing_parent)
870
unversioned_parent = UnversionedParent('Versioned directory',
873
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
875
self.assertEqual(cooked_conflicts[3], unversioned_parent)
876
parent_loop = ParentLoop(
877
'Cancelled move', 'oz/emeraldcity',
878
'oz/emeraldcity', b'emerald-id', b'emerald-id')
879
self.assertEqual(cooked_conflicts[4], deleted_parent)
880
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
881
self.assertEqual(cooked_conflicts[6], parent_loop)
882
self.assertEqual(len(cooked_conflicts), 7)
885
def test_string_conflicts(self):
886
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
887
raw_conflicts = resolve_conflicts(tt)
888
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
890
conflicts_s = [str(c) for c in cooked_conflicts]
891
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
892
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
893
'Moved existing file to '
895
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
896
'Unversioned existing file '
898
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
899
' munchkincity. Created directory.')
900
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
901
' versioned, but has versioned'
902
' children. Versioned directory.')
903
self.assertEqualDiff(
904
conflicts_s[4], "Conflict: can't delete oz because it"
905
" is not empty. Not deleting.")
906
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
907
' versioned, but has versioned'
908
' children. Versioned directory.')
909
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
910
' oz/emeraldcity. Cancelled move.')
912
def prepare_wrong_parent_kind(self):
913
tt, root = self.get_transform()
914
tt.new_file('parent', root, [b'contents'], b'parent-id')
916
tt, root = self.get_transform()
917
parent_id = tt.trans_id_file_id(b'parent-id')
918
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
921
def test_find_conflicts_wrong_parent_kind(self):
922
tt = self.prepare_wrong_parent_kind()
925
def test_resolve_conflicts_wrong_existing_parent_kind(self):
926
tt = self.prepare_wrong_parent_kind()
927
raw_conflicts = resolve_conflicts(tt)
928
self.assertEqual({('non-directory parent', 'Created directory',
929
'new-3')}, raw_conflicts)
930
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
931
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
932
b'parent-id')], cooked_conflicts)
934
self.assertFalse(self.wt.is_versioned('parent'))
935
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
937
def test_resolve_conflicts_wrong_new_parent_kind(self):
938
tt, root = self.get_transform()
939
parent_id = tt.new_directory('parent', root, b'parent-id')
940
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
942
tt, root = self.get_transform()
943
parent_id = tt.trans_id_file_id(b'parent-id')
944
tt.delete_contents(parent_id)
945
tt.create_file([b'contents'], parent_id)
946
raw_conflicts = resolve_conflicts(tt)
947
self.assertEqual({('non-directory parent', 'Created directory',
948
'new-3')}, raw_conflicts)
950
self.assertFalse(self.wt.is_versioned('parent'))
951
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
953
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
954
tt, root = self.get_transform()
955
parent_id = tt.new_directory('parent', root)
956
tt.new_file('child,', parent_id, [b'contents2'])
958
tt, root = self.get_transform()
959
parent_id = tt.trans_id_tree_path('parent')
960
tt.delete_contents(parent_id)
961
tt.create_file([b'contents'], parent_id)
962
resolve_conflicts(tt)
964
self.assertFalse(self.wt.is_versioned('parent'))
965
self.assertFalse(self.wt.is_versioned('parent.new'))
967
def test_resolve_conflicts_missing_parent(self):
968
wt = self.make_branch_and_tree('.')
969
tt = TreeTransform(wt)
970
self.addCleanup(tt.finalize)
971
parent = tt.trans_id_file_id(b'parent-id')
972
tt.new_file('file', parent, [b'Contents'])
973
raw_conflicts = resolve_conflicts(tt)
974
# Since the directory doesn't exist it's seen as 'missing'. So
975
# 'resolve_conflicts' create a conflict asking for it to be created.
976
self.assertLength(1, raw_conflicts)
977
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
979
# apply fail since the missing directory doesn't exist
980
self.assertRaises(errors.NoFinalPath, tt.apply)
982
def test_moving_versioned_directories(self):
983
create, root = self.get_transform()
984
kansas = create.new_directory('kansas', root, b'kansas-id')
985
create.new_directory('house', kansas, b'house-id')
986
create.new_directory('oz', root, b'oz-id')
988
cyclone, root = self.get_transform()
989
oz = cyclone.trans_id_tree_path('oz')
990
house = cyclone.trans_id_tree_path('house')
991
cyclone.adjust_path('house', oz, house)
994
def test_moving_root(self):
995
create, root = self.get_transform()
996
fun = create.new_directory('fun', root, b'fun-id')
997
create.new_directory('sun', root, b'sun-id')
998
create.new_directory('moon', root, b'moon')
1000
transform, root = self.get_transform()
1001
transform.adjust_root_path('oldroot', fun)
1002
new_root = transform.trans_id_tree_path('')
1003
transform.version_file(b'new-root', new_root)
1006
def test_renames(self):
1007
create, root = self.get_transform()
1008
old = create.new_directory('old-parent', root, b'old-id')
1009
intermediate = create.new_directory('intermediate', old, b'im-id')
1010
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1013
rename, root = self.get_transform()
1014
old = rename.trans_id_file_id(b'old-id')
1015
rename.adjust_path('new', root, old)
1016
myfile = rename.trans_id_file_id(b'myfile-id')
1017
rename.set_executability(True, myfile)
1020
def test_rename_fails(self):
1021
self.requireFeature(features.not_running_as_root)
1022
# see https://bugs.launchpad.net/bzr/+bug/491763
1023
create, root_id = self.get_transform()
1024
create.new_directory('first-dir', root_id, b'first-id')
1025
create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1027
if os.name == "posix" and sys.platform != "cygwin":
1028
# posix filesystems fail on renaming if the readonly bit is set
1029
osutils.make_readonly(self.wt.abspath('first-dir'))
1030
elif os.name == "nt":
1031
# windows filesystems fail on renaming open files
1032
self.addCleanup(open(self.wt.abspath('myfile')).close)
1034
self.skipTest("Can't force a permissions error on rename")
1035
# now transform to rename
1036
rename_transform, root_id = self.get_transform()
1037
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1038
dir_id = rename_transform.trans_id_file_id(b'first-id')
1039
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1040
e = self.assertRaises(errors.TransformRenameFailed,
1041
rename_transform.apply)
1042
# On nix looks like:
1043
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1044
# to .../first-dir/newname: [Errno 13] Permission denied"
1045
# On windows looks like:
1046
# "Failed to rename .../work/myfile to
1047
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1048
# This test isn't concerned with exactly what the error looks like,
1049
# and the strerror will vary across OS and locales, but the assert
1050
# that the exeception attributes are what we expect
1051
self.assertEqual(e.errno, errno.EACCES)
1052
if os.name == "posix":
1053
self.assertEndsWith(e.to_path, "/first-dir/newname")
1055
self.assertEqual(os.path.basename(e.from_path), "myfile")
1057
def test_set_executability_order(self):
1058
"""Ensure that executability behaves the same, no matter what order.
1060
- create file and set executability simultaneously
1061
- create file and set executability afterward
1062
- unsetting the executability of a file whose executability has not
1064
declared should throw an exception (this may happen when a
1065
merge attempts to create a file with a duplicate ID)
1067
transform, root = self.get_transform()
1068
wt = transform._tree
1070
self.addCleanup(wt.unlock)
1071
transform.new_file('set_on_creation', root, [b'Set on creation'],
1073
sac = transform.new_file('set_after_creation', root,
1074
[b'Set after creation'], b'sac')
1075
transform.set_executability(True, sac)
1076
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1078
self.assertRaises(KeyError, transform.set_executability, None, uws)
1080
self.assertTrue(wt.is_executable('set_on_creation'))
1081
self.assertTrue(wt.is_executable('set_after_creation'))
1083
def test_preserve_mode(self):
1084
"""File mode is preserved when replacing content"""
1085
if sys.platform == 'win32':
1086
raise TestSkipped('chmod has no effect on win32')
1087
transform, root = self.get_transform()
1088
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1090
self.wt.lock_write()
1091
self.addCleanup(self.wt.unlock)
1092
self.assertTrue(self.wt.is_executable('file1'))
1093
transform, root = self.get_transform()
1094
file1_id = transform.trans_id_tree_path('file1')
1095
transform.delete_contents(file1_id)
1096
transform.create_file([b'contents2'], file1_id)
1098
self.assertTrue(self.wt.is_executable('file1'))
1100
def test__set_mode_stats_correctly(self):
1101
"""_set_mode stats to determine file mode."""
1102
if sys.platform == 'win32':
1103
raise TestSkipped('chmod has no effect on win32')
1108
def instrumented_stat(path):
1109
stat_paths.append(path)
1110
return real_stat(path)
1112
transform, root = self.get_transform()
1114
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1115
file_id=b'bar-id-1', executable=False)
1118
transform, root = self.get_transform()
1119
bar1_id = transform.trans_id_tree_path('bar')
1120
bar2_id = transform.trans_id_tree_path('bar2')
1122
os.stat = instrumented_stat
1123
transform.create_file([b'bar2 contents\n'],
1124
bar2_id, mode_id=bar1_id)
1127
transform.finalize()
1129
bar1_abspath = self.wt.abspath('bar')
1130
self.assertEqual([bar1_abspath], stat_paths)
1132
def test_iter_changes(self):
1133
self.wt.set_root_id(b'eert_toor')
1134
transform, root = self.get_transform()
1135
transform.new_file('old', root, [b'blah'], b'id-1', True)
1137
transform, root = self.get_transform()
1139
self.assertEqual([], list(transform.iter_changes()))
1140
old = transform.trans_id_tree_path('old')
1141
transform.unversion_file(old)
1142
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1143
(b'eert_toor', b'eert_toor'),
1144
('old', 'old'), ('file', 'file'),
1145
(True, True), False)],
1146
list(transform.iter_changes()))
1147
transform.new_directory('new', root, b'id-1')
1148
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1149
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1150
('file', 'directory'),
1151
(True, False), False)],
1152
list(transform.iter_changes()))
1154
transform.finalize()
1156
def test_iter_changes_new(self):
1157
self.wt.set_root_id(b'eert_toor')
1158
transform, root = self.get_transform()
1159
transform.new_file('old', root, [b'blah'])
1161
transform, root = self.get_transform()
1163
old = transform.trans_id_tree_path('old')
1164
transform.version_file(b'id-1', old)
1165
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1166
(b'eert_toor', b'eert_toor'),
1167
('old', 'old'), ('file', 'file'),
1168
(False, False), False)],
1169
list(transform.iter_changes()))
1171
transform.finalize()
1173
def test_iter_changes_modifications(self):
1174
self.wt.set_root_id(b'eert_toor')
1175
transform, root = self.get_transform()
1176
transform.new_file('old', root, [b'blah'], b'id-1')
1177
transform.new_file('new', root, [b'blah'])
1178
transform.new_directory('subdir', root, b'subdir-id')
1180
transform, root = self.get_transform()
1182
old = transform.trans_id_tree_path('old')
1183
subdir = transform.trans_id_tree_path('subdir')
1184
new = transform.trans_id_tree_path('new')
1185
self.assertEqual([], list(transform.iter_changes()))
1188
transform.delete_contents(old)
1189
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1190
(b'eert_toor', b'eert_toor'),
1191
('old', 'old'), ('file', None),
1192
(False, False), False)],
1193
list(transform.iter_changes()))
1196
transform.create_file([b'blah'], old)
1197
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1198
(b'eert_toor', b'eert_toor'),
1199
('old', 'old'), ('file', 'file'),
1200
(False, False), False)],
1201
list(transform.iter_changes()))
1202
transform.cancel_deletion(old)
1203
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1204
(b'eert_toor', b'eert_toor'),
1205
('old', 'old'), ('file', 'file'),
1206
(False, False), False)],
1207
list(transform.iter_changes()))
1208
transform.cancel_creation(old)
1210
# move file_id to a different file
1211
self.assertEqual([], list(transform.iter_changes()))
1212
transform.unversion_file(old)
1213
transform.version_file(b'id-1', new)
1214
transform.adjust_path('old', root, new)
1215
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1216
(b'eert_toor', b'eert_toor'),
1217
('old', 'old'), ('file', 'file'),
1218
(False, False), False)],
1219
list(transform.iter_changes()))
1220
transform.cancel_versioning(new)
1221
transform._removed_id = set()
1224
self.assertEqual([], list(transform.iter_changes()))
1225
transform.set_executability(True, old)
1226
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1227
(b'eert_toor', b'eert_toor'),
1228
('old', 'old'), ('file', 'file'),
1229
(False, True), False)],
1230
list(transform.iter_changes()))
1231
transform.set_executability(None, old)
1234
self.assertEqual([], list(transform.iter_changes()))
1235
transform.adjust_path('new', root, old)
1236
transform._new_parent = {}
1237
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1238
(b'eert_toor', b'eert_toor'),
1239
('old', 'new'), ('file', 'file'),
1240
(False, False), False)],
1241
list(transform.iter_changes()))
1242
transform._new_name = {}
1245
self.assertEqual([], list(transform.iter_changes()))
1246
transform.adjust_path('new', subdir, old)
1247
transform._new_name = {}
1248
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1249
(True, True), (b'eert_toor',
1250
b'subdir-id'), ('old', 'old'),
1251
('file', 'file'), (False, False), False)],
1252
list(transform.iter_changes()))
1253
transform._new_path = {}
1256
transform.finalize()
1258
def test_iter_changes_modified_bleed(self):
1259
self.wt.set_root_id(b'eert_toor')
1260
"""Modified flag should not bleed from one change to another"""
1261
# unfortunately, we have no guarantee that file1 (which is modified)
1262
# will be applied before file2. And if it's applied after file2, it
1263
# obviously can't bleed into file2's change output. But for now, it
1265
transform, root = self.get_transform()
1266
transform.new_file('file1', root, [b'blah'], b'id-1')
1267
transform.new_file('file2', root, [b'blah'], b'id-2')
1269
transform, root = self.get_transform()
1271
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1272
transform.set_executability(True,
1273
transform.trans_id_file_id(b'id-2'))
1275
[(b'id-1', (u'file1', u'file1'), True, (True, True),
1276
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1277
('file', None), (False, False), False),
1278
(b'id-2', (u'file2', u'file2'), False, (True, True),
1279
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1280
('file', 'file'), (False, True), False)],
1281
list(transform.iter_changes()))
1283
transform.finalize()
1285
def test_iter_changes_move_missing(self):
1286
"""Test moving ids with no files around"""
1287
self.wt.set_root_id(b'toor_eert')
1288
# Need two steps because versioning a non-existant file is a conflict.
1289
transform, root = self.get_transform()
1290
transform.new_directory('floater', root, b'floater-id')
1292
transform, root = self.get_transform()
1293
transform.delete_contents(transform.trans_id_tree_path('floater'))
1295
transform, root = self.get_transform()
1296
floater = transform.trans_id_tree_path('floater')
1298
transform.adjust_path('flitter', root, floater)
1299
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1301
(b'toor_eert', b'toor_eert'),
1302
('floater', 'flitter'),
1303
(None, None), (False, False), False)],
1304
list(transform.iter_changes()))
1306
transform.finalize()
1308
def test_iter_changes_pointless(self):
1309
"""Ensure that no-ops are not treated as modifications"""
1310
self.wt.set_root_id(b'eert_toor')
1311
transform, root = self.get_transform()
1312
transform.new_file('old', root, [b'blah'], b'id-1')
1313
transform.new_directory('subdir', root, b'subdir-id')
1315
transform, root = self.get_transform()
1317
old = transform.trans_id_tree_path('old')
1318
subdir = transform.trans_id_tree_path('subdir')
1319
self.assertEqual([], list(transform.iter_changes()))
1320
transform.delete_contents(subdir)
1321
transform.create_directory(subdir)
1322
transform.set_executability(False, old)
1323
transform.unversion_file(old)
1324
transform.version_file(b'id-1', old)
1325
transform.adjust_path('old', root, old)
1326
self.assertEqual([], list(transform.iter_changes()))
1328
transform.finalize()
1330
def test_rename_count(self):
1331
transform, root = self.get_transform()
1332
transform.new_file('name1', root, [b'contents'])
1333
self.assertEqual(transform.rename_count, 0)
1335
self.assertEqual(transform.rename_count, 1)
1336
transform2, root = self.get_transform()
1337
transform2.adjust_path('name2', root,
1338
transform2.trans_id_tree_path('name1'))
1339
self.assertEqual(transform2.rename_count, 0)
1341
self.assertEqual(transform2.rename_count, 2)
1343
def test_change_parent(self):
1344
"""Ensure that after we change a parent, the results are still right.
1346
Renames and parent changes on pending transforms can happen as part
1347
of conflict resolution, and are explicitly permitted by the
1350
This test ensures they work correctly with the rename-avoidance
1353
transform, root = self.get_transform()
1354
parent1 = transform.new_directory('parent1', root)
1355
child1 = transform.new_file('child1', parent1, [b'contents'])
1356
parent2 = transform.new_directory('parent2', root)
1357
transform.adjust_path('child1', parent2, child1)
1359
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1360
self.assertPathExists(self.wt.abspath('parent2/child1'))
1361
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1362
# no rename for child1 (counting only renames during apply)
1363
self.assertEqual(2, transform.rename_count)
1365
def test_cancel_parent(self):
1366
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1368
This is like the test_change_parent, except that we cancel the parent
1369
before adjusting the path. The transform must detect that the
1370
directory is non-empty, and move children to safe locations.
1372
transform, root = self.get_transform()
1373
parent1 = transform.new_directory('parent1', root)
1374
child1 = transform.new_file('child1', parent1, [b'contents'])
1375
child2 = transform.new_file('child2', parent1, [b'contents'])
1377
transform.cancel_creation(parent1)
1379
self.fail('Failed to move child1 before deleting parent1')
1380
transform.cancel_creation(child2)
1381
transform.create_directory(parent1)
1383
transform.cancel_creation(parent1)
1384
# If the transform incorrectly believes that child2 is still in
1385
# parent1's limbo directory, it will try to rename it and fail
1386
# because was already moved by the first cancel_creation.
1388
self.fail('Transform still thinks child2 is a child of parent1')
1389
parent2 = transform.new_directory('parent2', root)
1390
transform.adjust_path('child1', parent2, child1)
1392
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1393
self.assertPathExists(self.wt.abspath('parent2/child1'))
1394
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1395
self.assertEqual(2, transform.rename_count)
1397
def test_adjust_and_cancel(self):
1398
"""Make sure adjust_path keeps track of limbo children properly"""
1399
transform, root = self.get_transform()
1400
parent1 = transform.new_directory('parent1', root)
1401
child1 = transform.new_file('child1', parent1, [b'contents'])
1402
parent2 = transform.new_directory('parent2', root)
1403
transform.adjust_path('child1', parent2, child1)
1404
transform.cancel_creation(child1)
1406
transform.cancel_creation(parent1)
1407
# if the transform thinks child1 is still in parent1's limbo
1408
# directory, it will attempt to move it and fail.
1410
self.fail('Transform still thinks child1 is a child of parent1')
1411
transform.finalize()
1413
def test_noname_contents(self):
1414
"""TreeTransform should permit deferring naming files."""
1415
transform, root = self.get_transform()
1416
parent = transform.trans_id_file_id(b'parent-id')
1418
transform.create_directory(parent)
1420
self.fail("Can't handle contents with no name")
1421
transform.finalize()
1423
def test_noname_contents_nested(self):
1424
"""TreeTransform should permit deferring naming files."""
1425
transform, root = self.get_transform()
1426
parent = transform.trans_id_file_id(b'parent-id')
1428
transform.create_directory(parent)
1430
self.fail("Can't handle contents with no name")
1431
transform.new_directory('child', parent)
1432
transform.adjust_path('parent', root, parent)
1434
self.assertPathExists(self.wt.abspath('parent/child'))
1435
self.assertEqual(1, transform.rename_count)
1437
def test_reuse_name(self):
1438
"""Avoid reusing the same limbo name for different files"""
1439
transform, root = self.get_transform()
1440
parent = transform.new_directory('parent', root)
1441
transform.new_directory('child', parent)
1443
child2 = transform.new_directory('child', parent)
1445
self.fail('Tranform tried to use the same limbo name twice')
1446
transform.adjust_path('child2', parent, child2)
1448
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1449
# child2 is put into top-level limbo because child1 has already
1450
# claimed the direct limbo path when child2 is created. There is no
1451
# advantage in renaming files once they're in top-level limbo, except
1453
self.assertEqual(2, transform.rename_count)
1455
def test_reuse_when_first_moved(self):
1456
"""Don't avoid direct paths when it is safe to use them"""
1457
transform, root = self.get_transform()
1458
parent = transform.new_directory('parent', root)
1459
child1 = transform.new_directory('child', parent)
1460
transform.adjust_path('child1', parent, child1)
1461
transform.new_directory('child', parent)
1463
# limbo/new-1 => parent
1464
self.assertEqual(1, transform.rename_count)
1466
def test_reuse_after_cancel(self):
1467
"""Don't avoid direct paths when it is safe to use them"""
1468
transform, root = self.get_transform()
1469
parent2 = transform.new_directory('parent2', root)
1470
child1 = transform.new_directory('child1', parent2)
1471
transform.cancel_creation(parent2)
1472
transform.create_directory(parent2)
1473
transform.new_directory('child1', parent2)
1474
transform.adjust_path('child2', parent2, child1)
1476
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1477
self.assertEqual(2, transform.rename_count)
1479
def test_finalize_order(self):
1480
"""Finalize must be done in child-to-parent order"""
1481
transform, root = self.get_transform()
1482
parent = transform.new_directory('parent', root)
1483
transform.new_directory('child', parent)
1485
transform.finalize()
1487
self.fail('Tried to remove parent before child1')
1489
def test_cancel_with_cancelled_child_should_succeed(self):
1490
transform, root = self.get_transform()
1491
parent = transform.new_directory('parent', root)
1492
child = transform.new_directory('child', parent)
1493
transform.cancel_creation(child)
1494
transform.cancel_creation(parent)
1495
transform.finalize()
1497
def test_rollback_on_directory_clash(self):
1499
wt = self.make_branch_and_tree('.')
1500
tt = TreeTransform(wt) # TreeTransform obtains write lock
1502
foo = tt.new_directory('foo', tt.root)
1503
tt.new_file('bar', foo, [b'foobar'])
1504
baz = tt.new_directory('baz', tt.root)
1505
tt.new_file('qux', baz, [b'quux'])
1506
# Ask for a rename 'foo' -> 'baz'
1507
tt.adjust_path('baz', tt.root, foo)
1508
# Lie to tt that we've already resolved all conflicts.
1509
tt.apply(no_conflicts=True)
1510
except BaseException:
1513
# The rename will fail because the target directory is not empty (but
1514
# raises FileExists anyway).
1515
err = self.assertRaises(errors.FileExists, tt_helper)
1516
self.assertEndsWith(err.path, "/baz")
1518
def test_two_directories_clash(self):
1520
wt = self.make_branch_and_tree('.')
1521
tt = TreeTransform(wt) # TreeTransform obtains write lock
1523
foo_1 = tt.new_directory('foo', tt.root)
1524
tt.new_directory('bar', foo_1)
1525
# Adding the same directory with a different content
1526
foo_2 = tt.new_directory('foo', tt.root)
1527
tt.new_directory('baz', foo_2)
1528
# Lie to tt that we've already resolved all conflicts.
1529
tt.apply(no_conflicts=True)
1530
except BaseException:
1533
err = self.assertRaises(errors.FileExists, tt_helper)
1534
self.assertEndsWith(err.path, "/foo")
1536
def test_two_directories_clash_finalize(self):
1538
wt = self.make_branch_and_tree('.')
1539
tt = TreeTransform(wt) # TreeTransform obtains write lock
1541
foo_1 = tt.new_directory('foo', tt.root)
1542
tt.new_directory('bar', foo_1)
1543
# Adding the same directory with a different content
1544
foo_2 = tt.new_directory('foo', tt.root)
1545
tt.new_directory('baz', foo_2)
1546
# Lie to tt that we've already resolved all conflicts.
1547
tt.apply(no_conflicts=True)
1548
except BaseException:
1551
err = self.assertRaises(errors.FileExists, tt_helper)
1552
self.assertEndsWith(err.path, "/foo")
1554
def test_file_to_directory(self):
1555
wt = self.make_branch_and_tree('.')
1556
self.build_tree(['foo'])
1559
tt = TreeTransform(wt)
1560
self.addCleanup(tt.finalize)
1561
foo_trans_id = tt.trans_id_tree_path("foo")
1562
tt.delete_contents(foo_trans_id)
1563
tt.create_directory(foo_trans_id)
1564
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1565
tt.create_file([b"aa\n"], bar_trans_id)
1566
tt.version_file(b"bar-1", bar_trans_id)
1568
self.assertPathExists("foo/bar")
1571
self.assertEqual(wt.kind("foo"), "directory")
1575
changes = wt.changes_from(wt.basis_tree())
1576
self.assertFalse(changes.has_changed(), changes)
1578
def test_file_to_symlink(self):
1579
self.requireFeature(SymlinkFeature)
1580
wt = self.make_branch_and_tree('.')
1581
self.build_tree(['foo'])
1584
tt = TreeTransform(wt)
1585
self.addCleanup(tt.finalize)
1586
foo_trans_id = tt.trans_id_tree_path("foo")
1587
tt.delete_contents(foo_trans_id)
1588
tt.create_symlink("bar", foo_trans_id)
1590
self.assertPathExists("foo")
1592
self.addCleanup(wt.unlock)
1593
self.assertEqual(wt.kind("foo"), "symlink")
1595
def test_file_to_symlink_unsupported(self):
1596
wt = self.make_branch_and_tree('.')
1597
self.build_tree(['foo'])
1600
self.overrideAttr(osutils, 'supports_symlinks', lambda p: False)
1601
tt = TreeTransform(wt)
1602
self.addCleanup(tt.finalize)
1603
foo_trans_id = tt.trans_id_tree_path("foo")
1604
tt.delete_contents(foo_trans_id)
1606
trace.push_log_file(log)
1607
tt.create_symlink("bar", foo_trans_id)
1609
self.assertContainsRe(
1611
b'Unable to create symlink "foo" on this filesystem')
1613
def test_dir_to_file(self):
1614
wt = self.make_branch_and_tree('.')
1615
self.build_tree(['foo/', 'foo/bar'])
1616
wt.add(['foo', 'foo/bar'])
1618
tt = TreeTransform(wt)
1619
self.addCleanup(tt.finalize)
1620
foo_trans_id = tt.trans_id_tree_path("foo")
1621
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1622
tt.delete_contents(foo_trans_id)
1623
tt.delete_versioned(bar_trans_id)
1624
tt.create_file([b"aa\n"], foo_trans_id)
1626
self.assertPathExists("foo")
1628
self.addCleanup(wt.unlock)
1629
self.assertEqual(wt.kind("foo"), "file")
1631
def test_dir_to_hardlink(self):
1632
self.requireFeature(HardlinkFeature)
1633
wt = self.make_branch_and_tree('.')
1634
self.build_tree(['foo/', 'foo/bar'])
1635
wt.add(['foo', 'foo/bar'])
1637
tt = TreeTransform(wt)
1638
self.addCleanup(tt.finalize)
1639
foo_trans_id = tt.trans_id_tree_path("foo")
1640
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1641
tt.delete_contents(foo_trans_id)
1642
tt.delete_versioned(bar_trans_id)
1643
self.build_tree(['baz'])
1644
tt.create_hardlink("baz", foo_trans_id)
1646
self.assertPathExists("foo")
1647
self.assertPathExists("baz")
1649
self.addCleanup(wt.unlock)
1650
self.assertEqual(wt.kind("foo"), "file")
1652
def test_no_final_path(self):
1653
transform, root = self.get_transform()
1654
trans_id = transform.trans_id_file_id(b'foo')
1655
transform.create_file([b'bar'], trans_id)
1656
transform.cancel_creation(trans_id)
1659
def test_create_from_tree(self):
1660
tree1 = self.make_branch_and_tree('tree1')
1661
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1662
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1663
tree2 = self.make_branch_and_tree('tree2')
1664
tt = TreeTransform(tree2)
1665
foo_trans_id = tt.create_path('foo', tt.root)
1666
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1667
bar_trans_id = tt.create_path('bar', tt.root)
1668
create_from_tree(tt, bar_trans_id, tree1, 'bar')
1670
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1671
self.assertFileEqual(b'baz', 'tree2/bar')
1673
def test_create_from_tree_bytes(self):
1674
"""Provided lines are used instead of tree content."""
1675
tree1 = self.make_branch_and_tree('tree1')
1676
self.build_tree_contents([('tree1/foo', b'bar'), ])
1677
tree1.add('foo', b'foo-id')
1678
tree2 = self.make_branch_and_tree('tree2')
1679
tt = TreeTransform(tree2)
1680
foo_trans_id = tt.create_path('foo', tt.root)
1681
create_from_tree(tt, foo_trans_id, tree1, 'foo', chunks=[b'qux'])
1683
self.assertFileEqual(b'qux', 'tree2/foo')
1685
def test_create_from_tree_symlink(self):
1686
self.requireFeature(SymlinkFeature)
1687
tree1 = self.make_branch_and_tree('tree1')
1688
os.symlink('bar', 'tree1/foo')
1689
tree1.add('foo', b'foo-id')
1690
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1691
foo_trans_id = tt.create_path('foo', tt.root)
1692
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1694
self.assertEqual('bar', os.readlink('tree2/foo'))
1697
89
class TransformGroup(object):
1699
91
def __init__(self, dirname, root_id):
2758
1118
conflicts.pop())
2761
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2762
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2763
(False, False), False)
2764
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2765
('', ''), ('directory', 'directory'), (False, False), False)
2768
class TestTransformPreview(tests.TestCaseWithTransport):
2770
def create_tree(self):
2771
tree = self.make_branch_and_tree('.')
2772
self.build_tree_contents([('a', b'content 1')])
2773
tree.set_root_id(b'TREE_ROOT')
2774
tree.add('a', b'a-id')
2775
tree.commit('rev1', rev_id=b'rev1')
2776
return tree.branch.repository.revision_tree(b'rev1')
2778
def get_empty_preview(self):
2779
repository = self.make_repository('repo')
2780
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2781
preview = TransformPreview(tree)
2782
self.addCleanup(preview.finalize)
2785
def test_transform_preview(self):
2786
revision_tree = self.create_tree()
2787
preview = TransformPreview(revision_tree)
2788
self.addCleanup(preview.finalize)
2790
def test_transform_preview_tree(self):
2791
revision_tree = self.create_tree()
2792
preview = TransformPreview(revision_tree)
2793
self.addCleanup(preview.finalize)
2794
preview.get_preview_tree()
2796
def test_transform_new_file(self):
2797
revision_tree = self.create_tree()
2798
preview = TransformPreview(revision_tree)
2799
self.addCleanup(preview.finalize)
2800
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2801
preview_tree = preview.get_preview_tree()
2802
self.assertEqual(preview_tree.kind('file2'), 'file')
2803
with preview_tree.get_file('file2') as f:
2804
self.assertEqual(f.read(), b'content B\n')
2806
def test_diff_preview_tree(self):
2807
revision_tree = self.create_tree()
2808
preview = TransformPreview(revision_tree)
2809
self.addCleanup(preview.finalize)
2810
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2811
preview_tree = preview.get_preview_tree()
2813
show_diff_trees(revision_tree, preview_tree, out)
2814
lines = out.getvalue().splitlines()
2815
self.assertEqual(lines[0], b"=== added file 'file2'")
2816
# 3 lines of diff administrivia
2817
self.assertEqual(lines[4], b"+content B")
2819
def test_unsupported_symlink_diff(self):
2820
self.requireFeature(SymlinkFeature)
2821
tree = self.make_branch_and_tree('.')
2822
self.build_tree_contents([('a', 'content 1')])
2823
tree.set_root_id(b'TREE_ROOT')
2824
tree.add('a', b'a-id')
2825
os.symlink('a', 'foo')
2826
tree.add('foo', b'foo-id')
2827
tree.commit('rev1', rev_id=b'rev1')
2828
revision_tree = tree.branch.repository.revision_tree(b'rev1')
2829
preview = TransformPreview(revision_tree)
2830
self.addCleanup(preview.finalize)
2831
preview.delete_versioned(preview.trans_id_tree_path('foo'))
2832
preview_tree = preview.get_preview_tree()
2835
trace.push_log_file(log)
2836
os_symlink = getattr(os, 'symlink', None)
2839
show_diff_trees(revision_tree, preview_tree, out)
2840
lines = out.getvalue().splitlines()
2842
os.symlink = os_symlink
2843
self.assertContainsRe(
2845
b'Ignoring "foo" as symlinks are not supported on this filesystem')
2847
def test_transform_conflicts(self):
2848
revision_tree = self.create_tree()
2849
preview = TransformPreview(revision_tree)
2850
self.addCleanup(preview.finalize)
2851
preview.new_file('a', preview.root, [b'content 2'])
2852
resolve_conflicts(preview)
2853
trans_id = preview.trans_id_file_id(b'a-id')
2854
self.assertEqual('a.moved', preview.final_name(trans_id))
2856
def get_tree_and_preview_tree(self):
2857
revision_tree = self.create_tree()
2858
preview = TransformPreview(revision_tree)
2859
self.addCleanup(preview.finalize)
2860
a_trans_id = preview.trans_id_file_id(b'a-id')
2861
preview.delete_contents(a_trans_id)
2862
preview.create_file([b'b content'], a_trans_id)
2863
preview_tree = preview.get_preview_tree()
2864
return revision_tree, preview_tree
2866
def test_iter_changes(self):
2867
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2868
root = revision_tree.path2id('')
2869
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2870
(root, root), ('a', 'a'), ('file', 'file'),
2871
(False, False), False)],
2872
list(preview_tree.iter_changes(revision_tree)))
2874
def test_include_unchanged_succeeds(self):
2875
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2876
changes = preview_tree.iter_changes(revision_tree,
2877
include_unchanged=True)
2878
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2880
def test_specific_files(self):
2881
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2882
changes = preview_tree.iter_changes(revision_tree,
2883
specific_files=[''])
2884
self.assertEqual([A_ENTRY], list(changes))
2886
def test_want_unversioned(self):
2887
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2888
changes = preview_tree.iter_changes(revision_tree,
2889
want_unversioned=True)
2890
self.assertEqual([A_ENTRY], list(changes))
2892
def test_ignore_extra_trees_no_specific_files(self):
2893
# extra_trees is harmless without specific_files, so we'll silently
2894
# accept it, even though we won't use it.
2895
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2896
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2898
def test_ignore_require_versioned_no_specific_files(self):
2899
# require_versioned is meaningless without specific_files.
2900
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2901
preview_tree.iter_changes(revision_tree, require_versioned=False)
2903
def test_ignore_pb(self):
2904
# pb could be supported, but TT.iter_changes doesn't support it.
2905
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2906
preview_tree.iter_changes(revision_tree)
2908
def test_kind(self):
2909
revision_tree = self.create_tree()
2910
preview = TransformPreview(revision_tree)
2911
self.addCleanup(preview.finalize)
2912
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2913
preview.new_directory('directory', preview.root, b'dir-id')
2914
preview_tree = preview.get_preview_tree()
2915
self.assertEqual('file', preview_tree.kind('file'))
2916
self.assertEqual('directory', preview_tree.kind('directory'))
2918
def test_get_file_mtime(self):
2919
preview = self.get_empty_preview()
2920
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2922
limbo_path = preview._limbo_name(file_trans_id)
2923
preview_tree = preview.get_preview_tree()
2924
self.assertEqual(os.stat(limbo_path).st_mtime,
2925
preview_tree.get_file_mtime('file'))
2927
def test_get_file_mtime_renamed(self):
2928
work_tree = self.make_branch_and_tree('tree')
2929
self.build_tree(['tree/file'])
2930
work_tree.add('file', b'file-id')
2931
preview = TransformPreview(work_tree)
2932
self.addCleanup(preview.finalize)
2933
file_trans_id = preview.trans_id_tree_path('file')
2934
preview.adjust_path('renamed', preview.root, file_trans_id)
2935
preview_tree = preview.get_preview_tree()
2936
preview_mtime = preview_tree.get_file_mtime('renamed')
2937
work_mtime = work_tree.get_file_mtime('file')
2939
def test_get_file_size(self):
2940
work_tree = self.make_branch_and_tree('tree')
2941
self.build_tree_contents([('tree/old', b'old')])
2942
work_tree.add('old', b'old-id')
2943
preview = TransformPreview(work_tree)
2944
self.addCleanup(preview.finalize)
2945
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2947
tree = preview.get_preview_tree()
2948
self.assertEqual(len('old'), tree.get_file_size('old'))
2949
self.assertEqual(len('contents'), tree.get_file_size('name'))
2951
def test_get_file(self):
2952
preview = self.get_empty_preview()
2953
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2954
preview_tree = preview.get_preview_tree()
2955
with preview_tree.get_file('file') as tree_file:
2956
self.assertEqual(b'contents', tree_file.read())
2958
def test_get_symlink_target(self):
2959
self.requireFeature(SymlinkFeature)
2960
preview = self.get_empty_preview()
2961
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2962
preview_tree = preview.get_preview_tree()
2963
self.assertEqual('target',
2964
preview_tree.get_symlink_target('symlink'))
2966
def test_all_file_ids(self):
2967
tree = self.make_branch_and_tree('tree')
2968
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2969
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2970
preview = TransformPreview(tree)
2971
self.addCleanup(preview.finalize)
2972
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2973
c_trans_id = preview.trans_id_file_id(b'c-id')
2974
preview.unversion_file(c_trans_id)
2975
preview.version_file(b'c-id', c_trans_id)
2976
preview_tree = preview.get_preview_tree()
2977
self.assertEqual({b'a-id', b'c-id', tree.path2id('')},
2978
preview_tree.all_file_ids())
2980
def test_path2id_deleted_unchanged(self):
2981
tree = self.make_branch_and_tree('tree')
2982
self.build_tree(['tree/unchanged', 'tree/deleted'])
2983
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2984
preview = TransformPreview(tree)
2985
self.addCleanup(preview.finalize)
2986
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2987
preview_tree = preview.get_preview_tree()
2988
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2989
self.assertFalse(preview_tree.is_versioned('deleted'))
2991
def test_path2id_created(self):
2992
tree = self.make_branch_and_tree('tree')
2993
self.build_tree(['tree/unchanged'])
2994
tree.add(['unchanged'], [b'unchanged-id'])
2995
preview = TransformPreview(tree)
2996
self.addCleanup(preview.finalize)
2997
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2998
[b'contents'], b'new-id')
2999
preview_tree = preview.get_preview_tree()
3000
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3002
def test_path2id_moved(self):
3003
tree = self.make_branch_and_tree('tree')
3004
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3005
tree.add(['old_parent', 'old_parent/child'],
3006
[b'old_parent-id', b'child-id'])
3007
preview = TransformPreview(tree)
3008
self.addCleanup(preview.finalize)
3009
new_parent = preview.new_directory('new_parent', preview.root,
3011
preview.adjust_path('child', new_parent,
3012
preview.trans_id_file_id(b'child-id'))
3013
preview_tree = preview.get_preview_tree()
3014
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3015
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3017
def test_path2id_renamed_parent(self):
3018
tree = self.make_branch_and_tree('tree')
3019
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3020
tree.add(['old_name', 'old_name/child'],
3021
[b'parent-id', b'child-id'])
3022
preview = TransformPreview(tree)
3023
self.addCleanup(preview.finalize)
3024
preview.adjust_path('new_name', preview.root,
3025
preview.trans_id_file_id(b'parent-id'))
3026
preview_tree = preview.get_preview_tree()
3027
self.assertFalse(preview_tree.is_versioned('old_name/child'))
3028
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3030
def assertMatchingIterEntries(self, tt, specific_files=None):
3031
preview_tree = tt.get_preview_tree()
3032
preview_result = list(preview_tree.iter_entries_by_dir(
3033
specific_files=specific_files))
3036
actual_result = list(tree.iter_entries_by_dir(
3037
specific_files=specific_files))
3038
self.assertEqual(actual_result, preview_result)
3040
def test_iter_entries_by_dir_new(self):
3041
tree = self.make_branch_and_tree('tree')
3042
tt = TreeTransform(tree)
3043
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3044
self.assertMatchingIterEntries(tt)
3046
def test_iter_entries_by_dir_deleted(self):
3047
tree = self.make_branch_and_tree('tree')
3048
self.build_tree(['tree/deleted'])
3049
tree.add('deleted', b'deleted-id')
3050
tt = TreeTransform(tree)
3051
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3052
self.assertMatchingIterEntries(tt)
3054
def test_iter_entries_by_dir_unversioned(self):
3055
tree = self.make_branch_and_tree('tree')
3056
self.build_tree(['tree/removed'])
3057
tree.add('removed', b'removed-id')
3058
tt = TreeTransform(tree)
3059
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3060
self.assertMatchingIterEntries(tt)
3062
def test_iter_entries_by_dir_moved(self):
3063
tree = self.make_branch_and_tree('tree')
3064
self.build_tree(['tree/moved', 'tree/new_parent/'])
3065
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3066
tt = TreeTransform(tree)
3067
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3068
tt.trans_id_file_id(b'moved-id'))
3069
self.assertMatchingIterEntries(tt)
3071
def test_iter_entries_by_dir_specific_files(self):
3072
tree = self.make_branch_and_tree('tree')
3073
tree.set_root_id(b'tree-root-id')
3074
self.build_tree(['tree/parent/', 'tree/parent/child'])
3075
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3076
tt = TreeTransform(tree)
3077
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3079
def test_symlink_content_summary(self):
3080
self.requireFeature(SymlinkFeature)
3081
preview = self.get_empty_preview()
3082
preview.new_symlink('path', preview.root, 'target', b'path-id')
3083
summary = preview.get_preview_tree().path_content_summary('path')
3084
self.assertEqual(('symlink', None, None, 'target'), summary)
3086
def test_missing_content_summary(self):
3087
preview = self.get_empty_preview()
3088
summary = preview.get_preview_tree().path_content_summary('path')
3089
self.assertEqual(('missing', None, None, None), summary)
3091
def test_deleted_content_summary(self):
3092
tree = self.make_branch_and_tree('tree')
3093
self.build_tree(['tree/path/'])
3095
preview = TransformPreview(tree)
3096
self.addCleanup(preview.finalize)
3097
preview.delete_contents(preview.trans_id_tree_path('path'))
3098
summary = preview.get_preview_tree().path_content_summary('path')
3099
self.assertEqual(('missing', None, None, None), summary)
3101
def test_file_content_summary_executable(self):
3102
preview = self.get_empty_preview()
3103
path_id = preview.new_file('path', preview.root, [
3104
b'contents'], b'path-id')
3105
preview.set_executability(True, path_id)
3106
summary = preview.get_preview_tree().path_content_summary('path')
3107
self.assertEqual(4, len(summary))
3108
self.assertEqual('file', summary[0])
3109
# size must be known
3110
self.assertEqual(len('contents'), summary[1])
3112
self.assertEqual(True, summary[2])
3113
# will not have hash (not cheap to determine)
3114
self.assertIs(None, summary[3])
3116
def test_change_executability(self):
3117
tree = self.make_branch_and_tree('tree')
3118
self.build_tree(['tree/path'])
3120
preview = TransformPreview(tree)
3121
self.addCleanup(preview.finalize)
3122
path_id = preview.trans_id_tree_path('path')
3123
preview.set_executability(True, path_id)
3124
summary = preview.get_preview_tree().path_content_summary('path')
3125
self.assertEqual(True, summary[2])
3127
def test_file_content_summary_non_exec(self):
3128
preview = self.get_empty_preview()
3129
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3130
summary = preview.get_preview_tree().path_content_summary('path')
3131
self.assertEqual(4, len(summary))
3132
self.assertEqual('file', summary[0])
3133
# size must be known
3134
self.assertEqual(len('contents'), summary[1])
3136
self.assertEqual(False, summary[2])
3137
# will not have hash (not cheap to determine)
3138
self.assertIs(None, summary[3])
3140
def test_dir_content_summary(self):
3141
preview = self.get_empty_preview()
3142
preview.new_directory('path', preview.root, b'path-id')
3143
summary = preview.get_preview_tree().path_content_summary('path')
3144
self.assertEqual(('directory', None, None, None), summary)
3146
def test_tree_content_summary(self):
3147
preview = self.get_empty_preview()
3148
path = preview.new_directory('path', preview.root, b'path-id')
3149
preview.set_tree_reference(b'rev-1', path)
3150
summary = preview.get_preview_tree().path_content_summary('path')
3151
self.assertEqual(4, len(summary))
3152
self.assertEqual('tree-reference', summary[0])
3154
def test_annotate(self):
3155
tree = self.make_branch_and_tree('tree')
3156
self.build_tree_contents([('tree/file', b'a\n')])
3157
tree.add('file', b'file-id')
3158
tree.commit('a', rev_id=b'one')
3159
self.build_tree_contents([('tree/file', b'a\nb\n')])
3160
preview = TransformPreview(tree)
3161
self.addCleanup(preview.finalize)
3162
file_trans_id = preview.trans_id_file_id(b'file-id')
3163
preview.delete_contents(file_trans_id)
3164
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3165
preview_tree = preview.get_preview_tree()
3171
annotation = preview_tree.annotate_iter(
3172
'file', default_revision=b'me:')
3173
self.assertEqual(expected, annotation)
3175
def test_annotate_missing(self):
3176
preview = self.get_empty_preview()
3177
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3178
preview_tree = preview.get_preview_tree()
3184
annotation = preview_tree.annotate_iter(
3185
'file', default_revision=b'me:')
3186
self.assertEqual(expected, annotation)
3188
def test_annotate_rename(self):
3189
tree = self.make_branch_and_tree('tree')
3190
self.build_tree_contents([('tree/file', b'a\n')])
3191
tree.add('file', b'file-id')
3192
tree.commit('a', rev_id=b'one')
3193
preview = TransformPreview(tree)
3194
self.addCleanup(preview.finalize)
3195
file_trans_id = preview.trans_id_file_id(b'file-id')
3196
preview.adjust_path('newname', preview.root, file_trans_id)
3197
preview_tree = preview.get_preview_tree()
3201
annotation = preview_tree.annotate_iter(
3202
'file', default_revision=b'me:')
3203
self.assertEqual(expected, annotation)
3205
def test_annotate_deleted(self):
3206
tree = self.make_branch_and_tree('tree')
3207
self.build_tree_contents([('tree/file', b'a\n')])
3208
tree.add('file', b'file-id')
3209
tree.commit('a', rev_id=b'one')
3210
self.build_tree_contents([('tree/file', b'a\nb\n')])
3211
preview = TransformPreview(tree)
3212
self.addCleanup(preview.finalize)
3213
file_trans_id = preview.trans_id_file_id(b'file-id')
3214
preview.delete_contents(file_trans_id)
3215
preview_tree = preview.get_preview_tree()
3216
annotation = preview_tree.annotate_iter(
3217
'file', default_revision=b'me:')
3218
self.assertIs(None, annotation)
3220
def test_stored_kind(self):
3221
preview = self.get_empty_preview()
3222
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3223
preview_tree = preview.get_preview_tree()
3224
self.assertEqual('file', preview_tree.stored_kind('file'))
3226
def test_is_executable(self):
3227
preview = self.get_empty_preview()
3228
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3229
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3230
preview_tree = preview.get_preview_tree()
3231
self.assertEqual(True, preview_tree.is_executable('file'))
3233
def test_get_set_parent_ids(self):
3234
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3235
self.assertEqual([], preview_tree.get_parent_ids())
3236
preview_tree.set_parent_ids([b'rev-1'])
3237
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3239
def test_plan_file_merge(self):
3240
work_a = self.make_branch_and_tree('wta')
3241
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3242
work_a.add('file', b'file-id')
3243
base_id = work_a.commit('base version')
3244
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3245
preview = TransformPreview(work_a)
3246
self.addCleanup(preview.finalize)
3247
trans_id = preview.trans_id_file_id(b'file-id')
3248
preview.delete_contents(trans_id)
3249
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3250
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3251
tree_a = preview.get_preview_tree()
3252
tree_a.set_parent_ids([base_id])
3254
('killed-a', b'a\n'),
3255
('killed-b', b'b\n'),
3256
('unchanged', b'c\n'),
3257
('unchanged', b'd\n'),
3260
], list(tree_a.plan_file_merge('file', tree_b)))
3262
def test_plan_file_merge_revision_tree(self):
3263
work_a = self.make_branch_and_tree('wta')
3264
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3265
work_a.add('file', b'file-id')
3266
base_id = work_a.commit('base version')
3267
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3268
preview = TransformPreview(work_a.basis_tree())
3269
self.addCleanup(preview.finalize)
3270
trans_id = preview.trans_id_file_id(b'file-id')
3271
preview.delete_contents(trans_id)
3272
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3273
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3274
tree_a = preview.get_preview_tree()
3275
tree_a.set_parent_ids([base_id])
3277
('killed-a', b'a\n'),
3278
('killed-b', b'b\n'),
3279
('unchanged', b'c\n'),
3280
('unchanged', b'd\n'),
3283
], list(tree_a.plan_file_merge('file', tree_b)))
3285
def test_walkdirs(self):
3286
preview = self.get_empty_preview()
3287
preview.new_directory('', ROOT_PARENT, b'tree-root')
3288
# FIXME: new_directory should mark root.
3289
preview.fixup_new_roots()
3290
preview_tree = preview.get_preview_tree()
3291
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3292
expected = [(('', b'tree-root'),
3293
[('a', 'a', 'file', None, b'a-id', 'file')])]
3294
self.assertEqual(expected, list(preview_tree.walkdirs()))
3296
def test_extras(self):
3297
work_tree = self.make_branch_and_tree('tree')
3298
self.build_tree(['tree/removed-file', 'tree/existing-file',
3299
'tree/not-removed-file'])
3300
work_tree.add(['removed-file', 'not-removed-file'])
3301
preview = TransformPreview(work_tree)
3302
self.addCleanup(preview.finalize)
3303
preview.new_file('new-file', preview.root, [b'contents'])
3304
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3305
b'new-versioned-id')
3306
tree = preview.get_preview_tree()
3307
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3308
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3311
def test_merge_into_preview(self):
3312
work_tree = self.make_branch_and_tree('tree')
3313
self.build_tree_contents([('tree/file', b'b\n')])
3314
work_tree.add('file', b'file-id')
3315
work_tree.commit('first commit')
3316
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3317
self.build_tree_contents([('child/file', b'b\nc\n')])
3318
child_tree.commit('child commit')
3319
child_tree.lock_write()
3320
self.addCleanup(child_tree.unlock)
3321
work_tree.lock_write()
3322
self.addCleanup(work_tree.unlock)
3323
preview = TransformPreview(work_tree)
3324
self.addCleanup(preview.finalize)
3325
file_trans_id = preview.trans_id_file_id(b'file-id')
3326
preview.delete_contents(file_trans_id)
3327
preview.create_file([b'a\nb\n'], file_trans_id)
3328
preview_tree = preview.get_preview_tree()
3329
merger = Merger.from_revision_ids(preview_tree,
3330
child_tree.branch.last_revision(),
3331
other_branch=child_tree.branch,
3332
tree_branch=work_tree.branch)
3333
merger.merge_type = Merge3Merger
3334
tt = merger.make_merger().make_preview_transform()
3335
self.addCleanup(tt.finalize)
3336
final_tree = tt.get_preview_tree()
3339
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3341
def test_merge_preview_into_workingtree(self):
3342
tree = self.make_branch_and_tree('tree')
3343
tree.set_root_id(b'TREE_ROOT')
3344
tt = TransformPreview(tree)
3345
self.addCleanup(tt.finalize)
3346
tt.new_file('name', tt.root, [b'content'], b'file-id')
3347
tree2 = self.make_branch_and_tree('tree2')
3348
tree2.set_root_id(b'TREE_ROOT')
3349
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3351
merger.merge_type = Merge3Merger
3354
def test_merge_preview_into_workingtree_handles_conflicts(self):
3355
tree = self.make_branch_and_tree('tree')
3356
self.build_tree_contents([('tree/foo', b'bar')])
3357
tree.add('foo', b'foo-id')
3359
tt = TransformPreview(tree)
3360
self.addCleanup(tt.finalize)
3361
trans_id = tt.trans_id_file_id(b'foo-id')
3362
tt.delete_contents(trans_id)
3363
tt.create_file([b'baz'], trans_id)
3364
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3365
self.build_tree_contents([('tree2/foo', b'qux')])
3366
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3368
merger.merge_type = Merge3Merger
3371
def test_has_filename(self):
3372
wt = self.make_branch_and_tree('tree')
3373
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3374
tt = TransformPreview(wt)
3375
removed_id = tt.trans_id_tree_path('removed')
3376
tt.delete_contents(removed_id)
3377
tt.new_file('new', tt.root, [b'contents'])
3378
modified_id = tt.trans_id_tree_path('modified')
3379
tt.delete_contents(modified_id)
3380
tt.create_file([b'modified-contents'], modified_id)
3381
self.addCleanup(tt.finalize)
3382
tree = tt.get_preview_tree()
3383
self.assertTrue(tree.has_filename('unmodified'))
3384
self.assertFalse(tree.has_filename('not-present'))
3385
self.assertFalse(tree.has_filename('removed'))
3386
self.assertTrue(tree.has_filename('new'))
3387
self.assertTrue(tree.has_filename('modified'))
3389
def test_is_executable(self):
3390
tree = self.make_branch_and_tree('tree')
3391
preview = TransformPreview(tree)
3392
self.addCleanup(preview.finalize)
3393
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3394
preview_tree = preview.get_preview_tree()
3395
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3397
def test_commit_preview_tree(self):
3398
tree = self.make_branch_and_tree('tree')
3399
rev_id = tree.commit('rev1')
3400
tree.branch.lock_write()
3401
self.addCleanup(tree.branch.unlock)
3402
tt = TransformPreview(tree)
3403
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3404
self.addCleanup(tt.finalize)
3405
preview = tt.get_preview_tree()
3406
preview.set_parent_ids([rev_id])
3407
builder = tree.branch.get_commit_builder([rev_id])
3408
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3409
builder.finish_inventory()
3410
rev2_id = builder.commit('rev2')
3411
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3412
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3414
def test_ascii_limbo_paths(self):
3415
self.requireFeature(features.UnicodeFilenameFeature)
3416
branch = self.make_branch('any')
3417
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3418
tt = TransformPreview(tree)
3419
self.addCleanup(tt.finalize)
3420
foo_id = tt.new_directory('', ROOT_PARENT)
3421
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3422
limbo_path = tt._limbo_name(bar_id)
3423
self.assertEqual(limbo_path, limbo_path)
3426
1121
class FakeSerializer(object):
3427
1122
"""Serializer implementation that simply returns the input.