85
TransformRenameFailed,
94
class TestTreeTransform(tests.TestCaseWithTransport):
97
super(TestTreeTransform, self).setUp()
98
self.wt = self.make_branch_and_tree('.', format='development-subtree')
101
def get_transform(self):
102
transform = TreeTransform(self.wt)
103
self.addCleanup(transform.finalize)
104
return transform, transform.root
106
def get_transform_for_sha1_test(self):
107
trans, root = self.get_transform()
108
self.wt.lock_tree_write()
109
self.addCleanup(self.wt.unlock)
110
contents = [b'just some content\n']
111
sha1 = osutils.sha_strings(contents)
112
# Roll back the clock
113
trans._creation_mtime = time.time() - 20.0
114
return trans, root, contents, sha1
116
def test_existing_limbo(self):
117
transform, root = self.get_transform()
118
limbo_name = transform._limbodir
119
deletion_path = transform._deletiondir
120
os.mkdir(pathjoin(limbo_name, 'hehe'))
121
self.assertRaises(ImmortalLimbo, transform.apply)
122
self.assertRaises(LockError, self.wt.unlock)
123
self.assertRaises(ExistingLimbo, self.get_transform)
124
self.assertRaises(LockError, self.wt.unlock)
125
os.rmdir(pathjoin(limbo_name, 'hehe'))
127
os.rmdir(deletion_path)
128
transform, root = self.get_transform()
131
def test_existing_pending_deletion(self):
132
transform, root = self.get_transform()
133
deletion_path = self._limbodir = urlutils.local_path_from_url(
134
transform._tree._transport.abspath('pending-deletion'))
135
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
136
self.assertRaises(ImmortalPendingDeletion, transform.apply)
137
self.assertRaises(LockError, self.wt.unlock)
138
self.assertRaises(ExistingPendingDeletion, self.get_transform)
140
def test_build(self):
141
transform, root = self.get_transform()
142
self.wt.lock_tree_write()
143
self.addCleanup(self.wt.unlock)
144
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
145
imaginary_id = transform.trans_id_tree_path('imaginary')
146
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
147
self.assertEqual(imaginary_id, imaginary_id2)
148
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
149
self.assertEqual('directory', transform.final_kind(root))
150
self.assertEqual(self.wt.path2id(''), transform.final_file_id(root))
151
trans_id = transform.create_path('name', root)
152
self.assertIs(transform.final_file_id(trans_id), None)
153
self.assertIs(None, transform.final_kind(trans_id))
154
transform.create_file([b'contents'], trans_id)
155
transform.set_executability(True, trans_id)
156
transform.version_file(b'my_pretties', trans_id)
157
self.assertRaises(DuplicateKey, transform.version_file,
158
b'my_pretties', trans_id)
159
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
160
self.assertEqual(transform.final_parent(trans_id), root)
161
self.assertIs(transform.final_parent(root), ROOT_PARENT)
162
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
163
oz_id = transform.create_path('oz', root)
164
transform.create_directory(oz_id)
165
transform.version_file(b'ozzie', oz_id)
166
trans_id2 = transform.create_path('name2', root)
167
transform.create_file([b'contents'], trans_id2)
168
transform.set_executability(False, trans_id2)
169
transform.version_file(b'my_pretties2', trans_id2)
170
modified_paths = transform.apply().modified_paths
171
with self.wt.get_file('name') as f:
172
self.assertEqual(b'contents', f.read())
173
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
174
self.assertIs(self.wt.is_executable('name'), True)
175
self.assertIs(self.wt.is_executable('name2'), False)
176
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
177
self.assertEqual(len(modified_paths), 3)
178
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
179
(b'ozzie', b'my_pretties', b'my_pretties2')]
180
self.assertSubset(tree_mod_paths, modified_paths)
181
# is it safe to finalize repeatedly?
185
def test_apply_informs_tree_of_observed_sha1(self):
186
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
187
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
190
orig = self.wt._observed_sha1
192
def _observed_sha1(*args):
195
self.wt._observed_sha1 = _observed_sha1
197
self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
200
def test_create_file_caches_sha1(self):
201
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
202
trans_id = trans.create_path('file1', root)
203
trans.create_file(contents, trans_id, sha1=sha1)
204
st_val = osutils.lstat(trans._limbo_name(trans_id))
205
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
206
self.assertEqual(o_sha1, sha1)
207
self.assertEqualStat(o_st_val, st_val)
209
def test__apply_insertions_updates_sha1(self):
210
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
211
trans_id = trans.create_path('file1', root)
212
trans.create_file(contents, trans_id, sha1=sha1)
213
st_val = osutils.lstat(trans._limbo_name(trans_id))
214
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
215
self.assertEqual(o_sha1, sha1)
216
self.assertEqualStat(o_st_val, st_val)
217
creation_mtime = trans._creation_mtime + 10.0
218
# We fake a time difference from when the file was created until now it
219
# is being renamed by using os.utime. Note that the change we actually
220
# want to see is the real ctime change from 'os.rename()', but as long
221
# as we observe a new stat value, we should be fine.
222
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
224
new_st_val = osutils.lstat(self.wt.abspath('file1'))
225
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
226
self.assertEqual(o_sha1, sha1)
227
self.assertEqualStat(o_st_val, new_st_val)
228
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
230
def test_new_file_caches_sha1(self):
231
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
232
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
234
st_val = osutils.lstat(trans._limbo_name(trans_id))
235
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
236
self.assertEqual(o_sha1, sha1)
237
self.assertEqualStat(o_st_val, st_val)
239
def test_cancel_creation_removes_observed_sha1(self):
240
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
241
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
243
self.assertTrue(trans_id in trans._observed_sha1s)
244
trans.cancel_creation(trans_id)
245
self.assertFalse(trans_id in trans._observed_sha1s)
247
def test_create_files_same_timestamp(self):
248
transform, root = self.get_transform()
249
self.wt.lock_tree_write()
250
self.addCleanup(self.wt.unlock)
251
# Roll back the clock, so that we know everything is being set to the
253
transform._creation_mtime = creation_mtime = time.time() - 20.0
254
transform.create_file([b'content-one'],
255
transform.create_path('one', root))
256
time.sleep(1) # *ugly*
257
transform.create_file([b'content-two'],
258
transform.create_path('two', root))
260
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
262
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
264
# We only guarantee 2s resolution
266
abs(creation_mtime - st1.st_mtime) < 2.0,
267
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
268
# But if we have more than that, all files should get the same result
269
self.assertEqual(st1.st_mtime, st2.st_mtime)
271
def test_change_root_id(self):
272
transform, root = self.get_transform()
273
self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
274
transform.new_directory('', ROOT_PARENT, b'new-root-id')
275
transform.delete_contents(root)
276
transform.unversion_file(root)
277
transform.fixup_new_roots()
279
self.assertEqual(b'new-root-id', self.wt.path2id(''))
281
def test_change_root_id_add_files(self):
282
transform, root = self.get_transform()
283
self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
284
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
285
transform.new_file('file', new_trans_id, [b'new-contents\n'],
287
transform.delete_contents(root)
288
transform.unversion_file(root)
289
transform.fixup_new_roots()
291
self.assertEqual(b'new-root-id', self.wt.path2id(''))
292
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
293
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
295
def test_add_two_roots(self):
296
transform, root = self.get_transform()
297
transform.new_directory('', ROOT_PARENT, b'new-root-id')
298
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
299
self.assertRaises(ValueError, transform.fixup_new_roots)
301
def test_retain_existing_root(self):
302
tt, root = self.get_transform()
304
tt.new_directory('', ROOT_PARENT, b'new-root-id')
306
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
308
def test_retain_existing_root_added_file(self):
309
tt, root = self.get_transform()
310
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
311
child = tt.new_directory('child', new_trans_id, b'child-id')
313
self.assertEqual(tt.root, tt.final_parent(child))
315
def test_add_unversioned_root(self):
316
transform, root = self.get_transform()
317
transform.new_directory('', ROOT_PARENT, None)
318
transform.delete_contents(transform.root)
319
transform.fixup_new_roots()
320
self.assertNotIn(transform.root, transform._new_id)
322
def test_remove_root_fixup(self):
323
transform, root = self.get_transform()
324
old_root_id = self.wt.path2id('')
325
self.assertNotEqual(b'new-root-id', old_root_id)
326
transform.delete_contents(root)
327
transform.unversion_file(root)
328
transform.fixup_new_roots()
330
self.assertEqual(old_root_id, self.wt.path2id(''))
332
transform, root = self.get_transform()
333
transform.new_directory('', ROOT_PARENT, b'new-root-id')
334
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
335
self.assertRaises(ValueError, transform.fixup_new_roots)
337
def test_fixup_new_roots_permits_empty_tree(self):
338
transform, root = self.get_transform()
339
transform.delete_contents(root)
340
transform.unversion_file(root)
341
transform.fixup_new_roots()
342
self.assertIs(None, transform.final_kind(root))
343
self.assertIs(None, transform.final_file_id(root))
345
def test_apply_retains_root_directory(self):
346
# Do not attempt to delete the physical root directory, because that
348
transform, root = self.get_transform()
350
transform.delete_contents(root)
351
e = self.assertRaises(AssertionError, self.assertRaises,
352
errors.TransformRenameFailed,
354
self.assertContainsRe('TransformRenameFailed not raised', str(e))
356
def test_apply_retains_file_id(self):
357
transform, root = self.get_transform()
358
old_root_id = transform.tree_file_id(root)
359
transform.unversion_file(root)
361
self.assertEqual(old_root_id, self.wt.path2id(''))
363
def test_hardlink(self):
364
self.requireFeature(HardlinkFeature)
365
transform, root = self.get_transform()
366
transform.new_file('file1', root, [b'contents'])
368
target = self.make_branch_and_tree('target')
369
target_transform = TreeTransform(target)
370
trans_id = target_transform.create_path('file1', target_transform.root)
371
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
372
target_transform.apply()
373
self.assertPathExists('target/file1')
374
source_stat = os.stat(self.wt.abspath('file1'))
375
target_stat = os.stat('target/file1')
376
self.assertEqual(source_stat, target_stat)
378
def test_convenience(self):
379
transform, root = self.get_transform()
380
self.wt.lock_tree_write()
381
self.addCleanup(self.wt.unlock)
382
transform.new_file('name', root, [b'contents'], b'my_pretties', True)
383
oz = transform.new_directory('oz', root, b'oz-id')
384
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
385
transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
388
self.assertEqual(len(transform.find_conflicts()), 0)
390
self.assertRaises(ReusingTransform, transform.find_conflicts)
391
with open(self.wt.abspath('name'), 'r') as f:
392
self.assertEqual('contents', f.read())
393
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
394
self.assertIs(self.wt.is_executable('name'), True)
395
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
396
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
397
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
399
self.assertEqual(b'toto-contents',
400
self.wt.get_file('oz/dorothy/toto').read())
401
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
403
def test_tree_reference(self):
404
transform, root = self.get_transform()
405
tree = transform._tree
406
trans_id = transform.new_directory('reference', root, b'subtree-id')
407
transform.set_tree_reference(b'subtree-revision', trans_id)
410
self.addCleanup(tree.unlock)
413
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
415
def test_conflicts(self):
416
transform, root = self.get_transform()
417
trans_id = transform.new_file('name', root, [b'contents'],
419
self.assertEqual(len(transform.find_conflicts()), 0)
420
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
421
self.assertEqual(transform.find_conflicts(),
422
[('duplicate', trans_id, trans_id2, 'name')])
423
self.assertRaises(MalformedTransform, transform.apply)
424
transform.adjust_path('name', trans_id, trans_id2)
425
self.assertEqual(transform.find_conflicts(),
426
[('non-directory parent', trans_id)])
427
tinman_id = transform.trans_id_tree_path('tinman')
428
transform.adjust_path('name', tinman_id, trans_id2)
429
self.assertEqual(transform.find_conflicts(),
430
[('unversioned parent', tinman_id),
431
('missing parent', tinman_id)])
432
lion_id = transform.create_path('lion', root)
433
self.assertEqual(transform.find_conflicts(),
434
[('unversioned parent', tinman_id),
435
('missing parent', tinman_id)])
436
transform.adjust_path('name', lion_id, trans_id2)
437
self.assertEqual(transform.find_conflicts(),
438
[('unversioned parent', lion_id),
439
('missing parent', lion_id)])
440
transform.version_file(b"Courage", lion_id)
441
self.assertEqual(transform.find_conflicts(),
442
[('missing parent', lion_id),
443
('versioning no contents', lion_id)])
444
transform.adjust_path('name2', root, trans_id2)
445
self.assertEqual(transform.find_conflicts(),
446
[('versioning no contents', lion_id)])
447
transform.create_file([b'Contents, okay?'], lion_id)
448
transform.adjust_path('name2', trans_id2, trans_id2)
449
self.assertEqual(transform.find_conflicts(),
450
[('parent loop', trans_id2),
451
('non-directory parent', trans_id2)])
452
transform.adjust_path('name2', root, trans_id2)
453
oz_id = transform.new_directory('oz', root)
454
transform.set_executability(True, oz_id)
455
self.assertEqual(transform.find_conflicts(),
456
[('unversioned executability', oz_id)])
457
transform.version_file(b'oz-id', oz_id)
458
self.assertEqual(transform.find_conflicts(),
459
[('non-file executability', oz_id)])
460
transform.set_executability(None, oz_id)
461
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
463
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
464
with open(self.wt.abspath('name'), 'rb') as f:
465
self.assertEqual(b'contents', f.read())
466
transform2, root = self.get_transform()
467
oz_id = transform2.trans_id_tree_path('oz')
468
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
469
result = transform2.find_conflicts()
470
fp = FinalPaths(transform2)
471
self.assertTrue('oz/tip' in transform2._tree_path_ids)
472
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
473
self.assertEqual(len(result), 2)
474
self.assertEqual((result[0][0], result[0][1]),
475
('duplicate', newtip))
476
self.assertEqual((result[1][0], result[1][2]),
477
('duplicate id', newtip))
478
transform2.finalize()
479
transform3 = TreeTransform(self.wt)
480
self.addCleanup(transform3.finalize)
481
oz_id = transform3.trans_id_tree_path('oz')
482
transform3.delete_contents(oz_id)
483
self.assertEqual(transform3.find_conflicts(),
484
[('missing parent', oz_id)])
485
root_id = transform3.root
486
tip_id = transform3.trans_id_tree_path('oz/tip')
487
transform3.adjust_path('tip', root_id, tip_id)
490
def test_conflict_on_case_insensitive(self):
491
tree = self.make_branch_and_tree('tree')
492
# Don't try this at home, kids!
493
# Force the tree to report that it is case sensitive, for conflict
495
tree.case_sensitive = True
496
transform = TreeTransform(tree)
497
self.addCleanup(transform.finalize)
498
transform.new_file('file', transform.root, [b'content'])
499
transform.new_file('FiLe', transform.root, [b'content'])
500
result = transform.find_conflicts()
501
self.assertEqual([], result)
503
# Force the tree to report that it is case insensitive, for conflict
505
tree.case_sensitive = False
506
transform = TreeTransform(tree)
507
self.addCleanup(transform.finalize)
508
transform.new_file('file', transform.root, [b'content'])
509
transform.new_file('FiLe', transform.root, [b'content'])
510
result = transform.find_conflicts()
511
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
513
def test_conflict_on_case_insensitive_existing(self):
514
tree = self.make_branch_and_tree('tree')
515
self.build_tree(['tree/FiLe'])
516
# Don't try this at home, kids!
517
# Force the tree to report that it is case sensitive, for conflict
519
tree.case_sensitive = True
520
transform = TreeTransform(tree)
521
self.addCleanup(transform.finalize)
522
transform.new_file('file', transform.root, [b'content'])
523
result = transform.find_conflicts()
524
self.assertEqual([], result)
526
# Force the tree to report that it is case insensitive, for conflict
528
tree.case_sensitive = False
529
transform = TreeTransform(tree)
530
self.addCleanup(transform.finalize)
531
transform.new_file('file', transform.root, [b'content'])
532
result = transform.find_conflicts()
533
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
535
def test_resolve_case_insensitive_conflict(self):
536
tree = self.make_branch_and_tree('tree')
537
# Don't try this at home, kids!
538
# Force the tree to report that it is case insensitive, for conflict
540
tree.case_sensitive = False
541
transform = TreeTransform(tree)
542
self.addCleanup(transform.finalize)
543
transform.new_file('file', transform.root, [b'content'])
544
transform.new_file('FiLe', transform.root, [b'content'])
545
resolve_conflicts(transform)
547
self.assertPathExists('tree/file')
548
self.assertPathExists('tree/FiLe.moved')
550
def test_resolve_checkout_case_conflict(self):
551
tree = self.make_branch_and_tree('tree')
552
# Don't try this at home, kids!
553
# Force the tree to report that it is case insensitive, for conflict
555
tree.case_sensitive = False
556
transform = TreeTransform(tree)
557
self.addCleanup(transform.finalize)
558
transform.new_file('file', transform.root, [b'content'])
559
transform.new_file('FiLe', transform.root, [b'content'])
560
resolve_conflicts(transform,
561
pass_func=lambda t, c: resolve_checkout(t, c, []))
563
self.assertPathExists('tree/file')
564
self.assertPathExists('tree/FiLe.moved')
566
def test_apply_case_conflict(self):
567
"""Ensure that a transform with case conflicts can always be applied"""
568
tree = self.make_branch_and_tree('tree')
569
transform = TreeTransform(tree)
570
self.addCleanup(transform.finalize)
571
transform.new_file('file', transform.root, [b'content'])
572
transform.new_file('FiLe', transform.root, [b'content'])
573
dir = transform.new_directory('dir', transform.root)
574
transform.new_file('dirfile', dir, [b'content'])
575
transform.new_file('dirFiLe', dir, [b'content'])
576
resolve_conflicts(transform)
578
self.assertPathExists('tree/file')
579
if not os.path.exists('tree/FiLe.moved'):
580
self.assertPathExists('tree/FiLe')
581
self.assertPathExists('tree/dir/dirfile')
582
if not os.path.exists('tree/dir/dirFiLe.moved'):
583
self.assertPathExists('tree/dir/dirFiLe')
585
def test_case_insensitive_limbo(self):
586
tree = self.make_branch_and_tree('tree')
587
# Don't try this at home, kids!
588
# Force the tree to report that it is case insensitive
589
tree.case_sensitive = False
590
transform = TreeTransform(tree)
591
self.addCleanup(transform.finalize)
592
dir = transform.new_directory('dir', transform.root)
593
first = transform.new_file('file', dir, [b'content'])
594
second = transform.new_file('FiLe', dir, [b'content'])
595
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
596
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
598
def test_adjust_path_updates_child_limbo_names(self):
599
tree = self.make_branch_and_tree('tree')
600
transform = TreeTransform(tree)
601
self.addCleanup(transform.finalize)
602
foo_id = transform.new_directory('foo', transform.root)
603
bar_id = transform.new_directory('bar', foo_id)
604
baz_id = transform.new_directory('baz', bar_id)
605
qux_id = transform.new_directory('qux', baz_id)
606
transform.adjust_path('quxx', foo_id, bar_id)
607
self.assertStartsWith(transform._limbo_name(qux_id),
608
transform._limbo_name(bar_id))
610
def test_add_del(self):
611
start, root = self.get_transform()
612
start.new_directory('a', root, b'a')
614
transform, root = self.get_transform()
615
transform.delete_versioned(transform.trans_id_tree_path('a'))
616
transform.new_directory('a', root, b'a')
619
def test_unversioning(self):
620
create_tree, root = self.get_transform()
621
parent_id = create_tree.new_directory('parent', root, b'parent-id')
622
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
624
unversion = TreeTransform(self.wt)
625
self.addCleanup(unversion.finalize)
626
parent = unversion.trans_id_tree_path('parent')
627
unversion.unversion_file(parent)
628
self.assertEqual(unversion.find_conflicts(),
629
[('unversioned parent', parent_id)])
630
file_id = unversion.trans_id_tree_path('parent/child')
631
unversion.unversion_file(file_id)
634
def test_name_invariants(self):
635
create_tree, root = self.get_transform()
637
root = create_tree.root
638
create_tree.new_file('name1', root, [b'hello1'], b'name1')
639
create_tree.new_file('name2', root, [b'hello2'], b'name2')
640
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
641
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
642
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
643
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
646
mangle_tree, root = self.get_transform()
647
root = mangle_tree.root
649
name1 = mangle_tree.trans_id_tree_path('name1')
650
name2 = mangle_tree.trans_id_tree_path('name2')
651
mangle_tree.adjust_path('name2', root, name1)
652
mangle_tree.adjust_path('name1', root, name2)
654
# tests for deleting parent directories
655
ddir = mangle_tree.trans_id_tree_path('dying_directory')
656
mangle_tree.delete_contents(ddir)
657
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
658
mangle_tree.delete_versioned(dfile)
659
mangle_tree.unversion_file(dfile)
660
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
661
mangle_tree.adjust_path('mfile', root, mfile)
663
# tests for adding parent directories
664
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
665
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
666
mangle_tree.adjust_path('mfile2', newdir, mfile2)
667
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
668
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
669
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
670
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
672
with open(self.wt.abspath('name1'), 'r') as f:
673
self.assertEqual(f.read(), 'hello2')
674
with open(self.wt.abspath('name2'), 'r') as f:
675
self.assertEqual(f.read(), 'hello1')
676
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
677
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
678
with open(mfile2_path, 'r') as f:
679
self.assertEqual(f.read(), 'later2')
680
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
681
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
682
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
683
with open(newfile_path, 'r') as f:
684
self.assertEqual(f.read(), 'hello3')
685
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
686
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
687
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
689
def test_both_rename(self):
690
create_tree, root = self.get_transform()
691
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
692
create_tree.new_file('blackbox.py', newdir, [
693
b'hello1'], b'blackbox-id')
695
mangle_tree, root = self.get_transform()
696
selftest = mangle_tree.trans_id_tree_path('selftest')
697
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
698
mangle_tree.adjust_path('test', root, selftest)
699
mangle_tree.adjust_path('test_too_much', root, selftest)
700
mangle_tree.set_executability(True, blackbox)
703
def test_both_rename2(self):
704
create_tree, root = self.get_transform()
705
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
706
tests = create_tree.new_directory('tests', breezy, b'tests-id')
707
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
708
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
711
mangle_tree, root = self.get_transform()
712
breezy = mangle_tree.trans_id_tree_path('breezy')
713
tests = mangle_tree.trans_id_tree_path('breezy/tests')
714
test_too_much = mangle_tree.trans_id_tree_path(
715
'breezy/tests/blackbox/test_too_much.py')
716
mangle_tree.adjust_path('selftest', breezy, tests)
717
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
718
mangle_tree.set_executability(True, test_too_much)
721
def test_both_rename3(self):
722
create_tree, root = self.get_transform()
723
tests = create_tree.new_directory('tests', root, b'tests-id')
724
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
727
mangle_tree, root = self.get_transform()
728
tests = mangle_tree.trans_id_tree_path('tests')
729
test_too_much = mangle_tree.trans_id_tree_path(
730
'tests/test_too_much.py')
731
mangle_tree.adjust_path('selftest', root, tests)
732
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
733
mangle_tree.set_executability(True, test_too_much)
736
def test_move_dangling_ie(self):
737
create_tree, root = self.get_transform()
739
root = create_tree.root
740
create_tree.new_file('name1', root, [b'hello1'], b'name1')
742
delete_contents, root = self.get_transform()
743
file = delete_contents.trans_id_tree_path('name1')
744
delete_contents.delete_contents(file)
745
delete_contents.apply()
746
move_id, root = self.get_transform()
747
name1 = move_id.trans_id_tree_path('name1')
748
newdir = move_id.new_directory('dir', root, b'newdir')
749
move_id.adjust_path('name2', newdir, name1)
752
def test_replace_dangling_ie(self):
753
create_tree, root = self.get_transform()
755
root = create_tree.root
756
create_tree.new_file('name1', root, [b'hello1'], b'name1')
758
delete_contents = TreeTransform(self.wt)
759
self.addCleanup(delete_contents.finalize)
760
file = delete_contents.trans_id_tree_path('name1')
761
delete_contents.delete_contents(file)
762
delete_contents.apply()
763
delete_contents.finalize()
764
replace = TreeTransform(self.wt)
765
self.addCleanup(replace.finalize)
766
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
767
conflicts = replace.find_conflicts()
768
name1 = replace.trans_id_tree_path('name1')
769
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
770
resolve_conflicts(replace)
773
def _test_symlinks(self, link_name1, link_target1,
774
link_name2, link_target2):
779
self.requireFeature(SymlinkFeature)
780
transform, root = self.get_transform()
781
oz_id = transform.new_directory('oz', root, b'oz-id')
782
transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
783
wiz_id = transform.create_path(link_name2, oz_id)
784
transform.create_symlink(link_target2, wiz_id)
785
transform.version_file(b'wiz-id2', wiz_id)
786
transform.set_executability(True, wiz_id)
787
self.assertEqual(transform.find_conflicts(),
788
[('non-file executability', wiz_id)])
789
transform.set_executability(None, wiz_id)
791
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
792
self.assertEqual('symlink',
793
file_kind(self.wt.abspath(ozpath(link_name1))))
794
self.assertEqual(link_target2,
795
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
796
self.assertEqual(link_target1,
797
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
799
def test_symlinks(self):
800
self._test_symlinks('wizard', 'wizard-target',
801
'wizard2', 'behind_curtain')
803
def test_symlinks_unicode(self):
804
self.requireFeature(features.UnicodeFilenameFeature)
805
self._test_symlinks(u'\N{Euro Sign}wizard',
806
u'wizard-targ\N{Euro Sign}t',
807
u'\N{Euro Sign}wizard2',
808
u'b\N{Euro Sign}hind_curtain')
810
def test_unsupported_symlink_no_conflict(self):
812
wt = self.make_branch_and_tree('.')
813
tt = TreeTransform(wt)
814
self.addCleanup(tt.finalize)
815
tt.new_symlink('foo', tt.root, 'bar')
816
result = tt.find_conflicts()
817
self.assertEqual([], result)
818
os_symlink = getattr(os, 'symlink', None)
824
os.symlink = os_symlink
826
def get_conflicted(self):
827
create, root = self.get_transform()
828
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
829
oz = create.new_directory('oz', root, b'oz-id')
830
create.new_directory('emeraldcity', oz, b'emerald-id')
832
conflicts, root = self.get_transform()
833
# set up duplicate entry, duplicate id
834
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
836
old_dorothy = conflicts.trans_id_tree_path('dorothy')
837
oz = conflicts.trans_id_tree_path('oz')
838
# set up DeletedParent parent conflict
839
conflicts.delete_versioned(oz)
840
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
841
# set up MissingParent conflict
842
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
843
conflicts.adjust_path('munchkincity', root, munchkincity)
844
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
846
conflicts.adjust_path('emeraldcity', emerald, emerald)
847
return conflicts, emerald, oz, old_dorothy, new_dorothy
849
def test_conflict_resolution(self):
850
conflicts, emerald, oz, old_dorothy, new_dorothy =\
851
self.get_conflicted()
852
resolve_conflicts(conflicts)
853
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
854
self.assertIs(conflicts.final_file_id(old_dorothy), None)
855
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
856
self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
857
self.assertEqual(conflicts.final_parent(emerald), oz)
860
def test_cook_conflicts(self):
861
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
862
raw_conflicts = resolve_conflicts(tt)
863
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
864
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
865
'dorothy', None, b'dorothy-id')
866
self.assertEqual(cooked_conflicts[0], duplicate)
867
duplicate_id = DuplicateID('Unversioned existing file',
868
'dorothy.moved', 'dorothy', None,
870
self.assertEqual(cooked_conflicts[1], duplicate_id)
871
missing_parent = MissingParent('Created directory', 'munchkincity',
873
deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
874
self.assertEqual(cooked_conflicts[2], missing_parent)
875
unversioned_parent = UnversionedParent('Versioned directory',
878
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
880
self.assertEqual(cooked_conflicts[3], unversioned_parent)
881
parent_loop = ParentLoop(
882
'Cancelled move', 'oz/emeraldcity',
883
'oz/emeraldcity', b'emerald-id', b'emerald-id')
884
self.assertEqual(cooked_conflicts[4], deleted_parent)
885
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
886
self.assertEqual(cooked_conflicts[6], parent_loop)
887
self.assertEqual(len(cooked_conflicts), 7)
890
def test_string_conflicts(self):
891
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
892
raw_conflicts = resolve_conflicts(tt)
893
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
895
conflicts_s = [text_type(c) for c in cooked_conflicts]
896
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
897
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
898
'Moved existing file to '
900
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
901
'Unversioned existing file '
903
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
904
' munchkincity. Created directory.')
905
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
906
' versioned, but has versioned'
907
' children. Versioned directory.')
908
self.assertEqualDiff(
909
conflicts_s[4], "Conflict: can't delete oz because it"
910
" is not empty. Not deleting.")
911
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
912
' versioned, but has versioned'
913
' children. Versioned directory.')
914
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
915
' oz/emeraldcity. Cancelled move.')
917
def prepare_wrong_parent_kind(self):
918
tt, root = self.get_transform()
919
tt.new_file('parent', root, [b'contents'], b'parent-id')
921
tt, root = self.get_transform()
922
parent_id = tt.trans_id_file_id(b'parent-id')
923
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
926
def test_find_conflicts_wrong_parent_kind(self):
927
tt = self.prepare_wrong_parent_kind()
930
def test_resolve_conflicts_wrong_existing_parent_kind(self):
931
tt = self.prepare_wrong_parent_kind()
932
raw_conflicts = resolve_conflicts(tt)
933
self.assertEqual({('non-directory parent', 'Created directory',
934
'new-3')}, raw_conflicts)
935
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
936
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
937
b'parent-id')], cooked_conflicts)
939
self.assertFalse(self.wt.is_versioned('parent'))
940
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
942
def test_resolve_conflicts_wrong_new_parent_kind(self):
943
tt, root = self.get_transform()
944
parent_id = tt.new_directory('parent', root, b'parent-id')
945
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
947
tt, root = self.get_transform()
948
parent_id = tt.trans_id_file_id(b'parent-id')
949
tt.delete_contents(parent_id)
950
tt.create_file([b'contents'], parent_id)
951
raw_conflicts = resolve_conflicts(tt)
952
self.assertEqual({('non-directory parent', 'Created directory',
953
'new-3')}, raw_conflicts)
955
self.assertFalse(self.wt.is_versioned('parent'))
956
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
958
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
959
tt, root = self.get_transform()
960
parent_id = tt.new_directory('parent', root)
961
tt.new_file('child,', parent_id, [b'contents2'])
963
tt, root = self.get_transform()
964
parent_id = tt.trans_id_tree_path('parent')
965
tt.delete_contents(parent_id)
966
tt.create_file([b'contents'], parent_id)
967
resolve_conflicts(tt)
969
self.assertFalse(self.wt.is_versioned('parent'))
970
self.assertFalse(self.wt.is_versioned('parent.new'))
972
def test_resolve_conflicts_missing_parent(self):
973
wt = self.make_branch_and_tree('.')
974
tt = TreeTransform(wt)
975
self.addCleanup(tt.finalize)
976
parent = tt.trans_id_file_id(b'parent-id')
977
tt.new_file('file', parent, [b'Contents'])
978
raw_conflicts = resolve_conflicts(tt)
979
# Since the directory doesn't exist it's seen as 'missing'. So
980
# 'resolve_conflicts' create a conflict asking for it to be created.
981
self.assertLength(1, raw_conflicts)
982
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
984
# apply fail since the missing directory doesn't exist
985
self.assertRaises(errors.NoFinalPath, tt.apply)
987
def test_moving_versioned_directories(self):
988
create, root = self.get_transform()
989
kansas = create.new_directory('kansas', root, b'kansas-id')
990
create.new_directory('house', kansas, b'house-id')
991
create.new_directory('oz', root, b'oz-id')
993
cyclone, root = self.get_transform()
994
oz = cyclone.trans_id_tree_path('oz')
995
house = cyclone.trans_id_tree_path('house')
996
cyclone.adjust_path('house', oz, house)
999
def test_moving_root(self):
1000
create, root = self.get_transform()
1001
fun = create.new_directory('fun', root, b'fun-id')
1002
create.new_directory('sun', root, b'sun-id')
1003
create.new_directory('moon', root, b'moon')
1005
transform, root = self.get_transform()
1006
transform.adjust_root_path('oldroot', fun)
1007
new_root = transform.trans_id_tree_path('')
1008
transform.version_file(b'new-root', new_root)
1011
def test_renames(self):
1012
create, root = self.get_transform()
1013
old = create.new_directory('old-parent', root, b'old-id')
1014
intermediate = create.new_directory('intermediate', old, b'im-id')
1015
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1018
rename, root = self.get_transform()
1019
old = rename.trans_id_file_id(b'old-id')
1020
rename.adjust_path('new', root, old)
1021
myfile = rename.trans_id_file_id(b'myfile-id')
1022
rename.set_executability(True, myfile)
1025
def test_rename_fails(self):
1026
self.requireFeature(features.not_running_as_root)
1027
# see https://bugs.launchpad.net/bzr/+bug/491763
1028
create, root_id = self.get_transform()
1029
create.new_directory('first-dir', root_id, b'first-id')
1030
create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1032
if os.name == "posix" and sys.platform != "cygwin":
1033
# posix filesystems fail on renaming if the readonly bit is set
1034
osutils.make_readonly(self.wt.abspath('first-dir'))
1035
elif os.name == "nt":
1036
# windows filesystems fail on renaming open files
1037
self.addCleanup(open(self.wt.abspath('myfile')).close)
1039
self.skipTest("Can't force a permissions error on rename")
1040
# now transform to rename
1041
rename_transform, root_id = self.get_transform()
1042
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1043
dir_id = rename_transform.trans_id_file_id(b'first-id')
1044
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1045
e = self.assertRaises(errors.TransformRenameFailed,
1046
rename_transform.apply)
1047
# On nix looks like:
1048
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1049
# to .../first-dir/newname: [Errno 13] Permission denied"
1050
# On windows looks like:
1051
# "Failed to rename .../work/myfile to
1052
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1053
# This test isn't concerned with exactly what the error looks like,
1054
# and the strerror will vary across OS and locales, but the assert
1055
# that the exeception attributes are what we expect
1056
self.assertEqual(e.errno, errno.EACCES)
1057
if os.name == "posix":
1058
self.assertEndsWith(e.to_path, "/first-dir/newname")
1060
self.assertEqual(os.path.basename(e.from_path), "myfile")
1062
def test_set_executability_order(self):
1063
"""Ensure that executability behaves the same, no matter what order.
1065
- create file and set executability simultaneously
1066
- create file and set executability afterward
1067
- unsetting the executability of a file whose executability has not
1069
declared should throw an exception (this may happen when a
1070
merge attempts to create a file with a duplicate ID)
1072
transform, root = self.get_transform()
1073
wt = transform._tree
1075
self.addCleanup(wt.unlock)
1076
transform.new_file('set_on_creation', root, [b'Set on creation'],
1078
sac = transform.new_file('set_after_creation', root,
1079
[b'Set after creation'], b'sac')
1080
transform.set_executability(True, sac)
1081
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1083
self.assertRaises(KeyError, transform.set_executability, None, uws)
1085
self.assertTrue(wt.is_executable('set_on_creation'))
1086
self.assertTrue(wt.is_executable('set_after_creation'))
1088
def test_preserve_mode(self):
1089
"""File mode is preserved when replacing content"""
1090
if sys.platform == 'win32':
1091
raise TestSkipped('chmod has no effect on win32')
1092
transform, root = self.get_transform()
1093
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1095
self.wt.lock_write()
1096
self.addCleanup(self.wt.unlock)
1097
self.assertTrue(self.wt.is_executable('file1'))
1098
transform, root = self.get_transform()
1099
file1_id = transform.trans_id_tree_path('file1')
1100
transform.delete_contents(file1_id)
1101
transform.create_file([b'contents2'], file1_id)
1103
self.assertTrue(self.wt.is_executable('file1'))
1105
def test__set_mode_stats_correctly(self):
1106
"""_set_mode stats to determine file mode."""
1107
if sys.platform == 'win32':
1108
raise TestSkipped('chmod has no effect on win32')
1113
def instrumented_stat(path):
1114
stat_paths.append(path)
1115
return real_stat(path)
1117
transform, root = self.get_transform()
1119
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1120
file_id=b'bar-id-1', executable=False)
1123
transform, root = self.get_transform()
1124
bar1_id = transform.trans_id_tree_path('bar')
1125
bar2_id = transform.trans_id_tree_path('bar2')
1127
os.stat = instrumented_stat
1128
transform.create_file([b'bar2 contents\n'],
1129
bar2_id, mode_id=bar1_id)
1132
transform.finalize()
1134
bar1_abspath = self.wt.abspath('bar')
1135
self.assertEqual([bar1_abspath], stat_paths)
1137
def test_iter_changes(self):
1138
self.wt.set_root_id(b'eert_toor')
1139
transform, root = self.get_transform()
1140
transform.new_file('old', root, [b'blah'], b'id-1', True)
1142
transform, root = self.get_transform()
1144
self.assertEqual([], list(transform.iter_changes()))
1145
old = transform.trans_id_tree_path('old')
1146
transform.unversion_file(old)
1147
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1148
(b'eert_toor', b'eert_toor'),
1149
('old', 'old'), ('file', 'file'),
1150
(True, True), False)],
1151
list(transform.iter_changes()))
1152
transform.new_directory('new', root, b'id-1')
1153
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1154
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1155
('file', 'directory'),
1156
(True, False), False)],
1157
list(transform.iter_changes()))
1159
transform.finalize()
1161
def test_iter_changes_new(self):
1162
self.wt.set_root_id(b'eert_toor')
1163
transform, root = self.get_transform()
1164
transform.new_file('old', root, [b'blah'])
1166
transform, root = self.get_transform()
1168
old = transform.trans_id_tree_path('old')
1169
transform.version_file(b'id-1', old)
1170
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1171
(b'eert_toor', b'eert_toor'),
1172
('old', 'old'), ('file', 'file'),
1173
(False, False), False)],
1174
list(transform.iter_changes()))
1176
transform.finalize()
1178
def test_iter_changes_modifications(self):
1179
self.wt.set_root_id(b'eert_toor')
1180
transform, root = self.get_transform()
1181
transform.new_file('old', root, [b'blah'], b'id-1')
1182
transform.new_file('new', root, [b'blah'])
1183
transform.new_directory('subdir', root, b'subdir-id')
1185
transform, root = self.get_transform()
1187
old = transform.trans_id_tree_path('old')
1188
subdir = transform.trans_id_tree_path('subdir')
1189
new = transform.trans_id_tree_path('new')
1190
self.assertEqual([], list(transform.iter_changes()))
1193
transform.delete_contents(old)
1194
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1195
(b'eert_toor', b'eert_toor'),
1196
('old', 'old'), ('file', None),
1197
(False, False), False)],
1198
list(transform.iter_changes()))
1201
transform.create_file([b'blah'], old)
1202
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1203
(b'eert_toor', b'eert_toor'),
1204
('old', 'old'), ('file', 'file'),
1205
(False, False), False)],
1206
list(transform.iter_changes()))
1207
transform.cancel_deletion(old)
1208
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1209
(b'eert_toor', b'eert_toor'),
1210
('old', 'old'), ('file', 'file'),
1211
(False, False), False)],
1212
list(transform.iter_changes()))
1213
transform.cancel_creation(old)
1215
# move file_id to a different file
1216
self.assertEqual([], list(transform.iter_changes()))
1217
transform.unversion_file(old)
1218
transform.version_file(b'id-1', new)
1219
transform.adjust_path('old', root, new)
1220
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1221
(b'eert_toor', b'eert_toor'),
1222
('old', 'old'), ('file', 'file'),
1223
(False, False), False)],
1224
list(transform.iter_changes()))
1225
transform.cancel_versioning(new)
1226
transform._removed_id = set()
1229
self.assertEqual([], list(transform.iter_changes()))
1230
transform.set_executability(True, old)
1231
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1232
(b'eert_toor', b'eert_toor'),
1233
('old', 'old'), ('file', 'file'),
1234
(False, True), False)],
1235
list(transform.iter_changes()))
1236
transform.set_executability(None, old)
1239
self.assertEqual([], list(transform.iter_changes()))
1240
transform.adjust_path('new', root, old)
1241
transform._new_parent = {}
1242
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1243
(b'eert_toor', b'eert_toor'),
1244
('old', 'new'), ('file', 'file'),
1245
(False, False), False)],
1246
list(transform.iter_changes()))
1247
transform._new_name = {}
1250
self.assertEqual([], list(transform.iter_changes()))
1251
transform.adjust_path('new', subdir, old)
1252
transform._new_name = {}
1253
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1254
(True, True), (b'eert_toor',
1255
b'subdir-id'), ('old', 'old'),
1256
('file', 'file'), (False, False), False)],
1257
list(transform.iter_changes()))
1258
transform._new_path = {}
1261
transform.finalize()
1263
def test_iter_changes_modified_bleed(self):
1264
self.wt.set_root_id(b'eert_toor')
1265
"""Modified flag should not bleed from one change to another"""
1266
# unfortunately, we have no guarantee that file1 (which is modified)
1267
# will be applied before file2. And if it's applied after file2, it
1268
# obviously can't bleed into file2's change output. But for now, it
1270
transform, root = self.get_transform()
1271
transform.new_file('file1', root, [b'blah'], b'id-1')
1272
transform.new_file('file2', root, [b'blah'], b'id-2')
1274
transform, root = self.get_transform()
1276
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1277
transform.set_executability(True,
1278
transform.trans_id_file_id(b'id-2'))
1280
[(b'id-1', (u'file1', u'file1'), True, (True, True),
1281
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1282
('file', None), (False, False), False),
1283
(b'id-2', (u'file2', u'file2'), False, (True, True),
1284
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1285
('file', 'file'), (False, True), False)],
1286
list(transform.iter_changes()))
1288
transform.finalize()
1290
def test_iter_changes_move_missing(self):
1291
"""Test moving ids with no files around"""
1292
self.wt.set_root_id(b'toor_eert')
1293
# Need two steps because versioning a non-existant file is a conflict.
1294
transform, root = self.get_transform()
1295
transform.new_directory('floater', root, b'floater-id')
1297
transform, root = self.get_transform()
1298
transform.delete_contents(transform.trans_id_tree_path('floater'))
1300
transform, root = self.get_transform()
1301
floater = transform.trans_id_tree_path('floater')
1303
transform.adjust_path('flitter', root, floater)
1304
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1306
(b'toor_eert', b'toor_eert'),
1307
('floater', 'flitter'),
1308
(None, None), (False, False), False)],
1309
list(transform.iter_changes()))
1311
transform.finalize()
1313
def test_iter_changes_pointless(self):
1314
"""Ensure that no-ops are not treated as modifications"""
1315
self.wt.set_root_id(b'eert_toor')
1316
transform, root = self.get_transform()
1317
transform.new_file('old', root, [b'blah'], b'id-1')
1318
transform.new_directory('subdir', root, b'subdir-id')
1320
transform, root = self.get_transform()
1322
old = transform.trans_id_tree_path('old')
1323
subdir = transform.trans_id_tree_path('subdir')
1324
self.assertEqual([], list(transform.iter_changes()))
1325
transform.delete_contents(subdir)
1326
transform.create_directory(subdir)
1327
transform.set_executability(False, old)
1328
transform.unversion_file(old)
1329
transform.version_file(b'id-1', old)
1330
transform.adjust_path('old', root, old)
1331
self.assertEqual([], list(transform.iter_changes()))
1333
transform.finalize()
1335
def test_rename_count(self):
1336
transform, root = self.get_transform()
1337
transform.new_file('name1', root, [b'contents'])
1338
self.assertEqual(transform.rename_count, 0)
1340
self.assertEqual(transform.rename_count, 1)
1341
transform2, root = self.get_transform()
1342
transform2.adjust_path('name2', root,
1343
transform2.trans_id_tree_path('name1'))
1344
self.assertEqual(transform2.rename_count, 0)
1346
self.assertEqual(transform2.rename_count, 2)
1348
def test_change_parent(self):
1349
"""Ensure that after we change a parent, the results are still right.
1351
Renames and parent changes on pending transforms can happen as part
1352
of conflict resolution, and are explicitly permitted by the
1355
This test ensures they work correctly with the rename-avoidance
1358
transform, root = self.get_transform()
1359
parent1 = transform.new_directory('parent1', root)
1360
child1 = transform.new_file('child1', parent1, [b'contents'])
1361
parent2 = transform.new_directory('parent2', root)
1362
transform.adjust_path('child1', parent2, child1)
1364
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1365
self.assertPathExists(self.wt.abspath('parent2/child1'))
1366
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1367
# no rename for child1 (counting only renames during apply)
1368
self.assertEqual(2, transform.rename_count)
1370
def test_cancel_parent(self):
1371
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1373
This is like the test_change_parent, except that we cancel the parent
1374
before adjusting the path. The transform must detect that the
1375
directory is non-empty, and move children to safe locations.
1377
transform, root = self.get_transform()
1378
parent1 = transform.new_directory('parent1', root)
1379
child1 = transform.new_file('child1', parent1, [b'contents'])
1380
child2 = transform.new_file('child2', parent1, [b'contents'])
1382
transform.cancel_creation(parent1)
1384
self.fail('Failed to move child1 before deleting parent1')
1385
transform.cancel_creation(child2)
1386
transform.create_directory(parent1)
1388
transform.cancel_creation(parent1)
1389
# If the transform incorrectly believes that child2 is still in
1390
# parent1's limbo directory, it will try to rename it and fail
1391
# because was already moved by the first cancel_creation.
1393
self.fail('Transform still thinks child2 is a child of parent1')
1394
parent2 = transform.new_directory('parent2', root)
1395
transform.adjust_path('child1', parent2, child1)
1397
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1398
self.assertPathExists(self.wt.abspath('parent2/child1'))
1399
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1400
self.assertEqual(2, transform.rename_count)
1402
def test_adjust_and_cancel(self):
1403
"""Make sure adjust_path keeps track of limbo children properly"""
1404
transform, root = self.get_transform()
1405
parent1 = transform.new_directory('parent1', root)
1406
child1 = transform.new_file('child1', parent1, [b'contents'])
1407
parent2 = transform.new_directory('parent2', root)
1408
transform.adjust_path('child1', parent2, child1)
1409
transform.cancel_creation(child1)
1411
transform.cancel_creation(parent1)
1412
# if the transform thinks child1 is still in parent1's limbo
1413
# directory, it will attempt to move it and fail.
1415
self.fail('Transform still thinks child1 is a child of parent1')
1416
transform.finalize()
1418
def test_noname_contents(self):
1419
"""TreeTransform should permit deferring naming files."""
1420
transform, root = self.get_transform()
1421
parent = transform.trans_id_file_id(b'parent-id')
1423
transform.create_directory(parent)
1425
self.fail("Can't handle contents with no name")
1426
transform.finalize()
1428
def test_noname_contents_nested(self):
1429
"""TreeTransform should permit deferring naming files."""
1430
transform, root = self.get_transform()
1431
parent = transform.trans_id_file_id(b'parent-id')
1433
transform.create_directory(parent)
1435
self.fail("Can't handle contents with no name")
1436
transform.new_directory('child', parent)
1437
transform.adjust_path('parent', root, parent)
1439
self.assertPathExists(self.wt.abspath('parent/child'))
1440
self.assertEqual(1, transform.rename_count)
1442
def test_reuse_name(self):
1443
"""Avoid reusing the same limbo name for different files"""
1444
transform, root = self.get_transform()
1445
parent = transform.new_directory('parent', root)
1446
transform.new_directory('child', parent)
1448
child2 = transform.new_directory('child', parent)
1450
self.fail('Tranform tried to use the same limbo name twice')
1451
transform.adjust_path('child2', parent, child2)
1453
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1454
# child2 is put into top-level limbo because child1 has already
1455
# claimed the direct limbo path when child2 is created. There is no
1456
# advantage in renaming files once they're in top-level limbo, except
1458
self.assertEqual(2, transform.rename_count)
1460
def test_reuse_when_first_moved(self):
1461
"""Don't avoid direct paths when it is safe to use them"""
1462
transform, root = self.get_transform()
1463
parent = transform.new_directory('parent', root)
1464
child1 = transform.new_directory('child', parent)
1465
transform.adjust_path('child1', parent, child1)
1466
transform.new_directory('child', parent)
1468
# limbo/new-1 => parent
1469
self.assertEqual(1, transform.rename_count)
1471
def test_reuse_after_cancel(self):
1472
"""Don't avoid direct paths when it is safe to use them"""
1473
transform, root = self.get_transform()
1474
parent2 = transform.new_directory('parent2', root)
1475
child1 = transform.new_directory('child1', parent2)
1476
transform.cancel_creation(parent2)
1477
transform.create_directory(parent2)
1478
transform.new_directory('child1', parent2)
1479
transform.adjust_path('child2', parent2, child1)
1481
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1482
self.assertEqual(2, transform.rename_count)
1484
def test_finalize_order(self):
1485
"""Finalize must be done in child-to-parent order"""
1486
transform, root = self.get_transform()
1487
parent = transform.new_directory('parent', root)
1488
transform.new_directory('child', parent)
1490
transform.finalize()
1492
self.fail('Tried to remove parent before child1')
1494
def test_cancel_with_cancelled_child_should_succeed(self):
1495
transform, root = self.get_transform()
1496
parent = transform.new_directory('parent', root)
1497
child = transform.new_directory('child', parent)
1498
transform.cancel_creation(child)
1499
transform.cancel_creation(parent)
1500
transform.finalize()
1502
def test_rollback_on_directory_clash(self):
1504
wt = self.make_branch_and_tree('.')
1505
tt = TreeTransform(wt) # TreeTransform obtains write lock
1507
foo = tt.new_directory('foo', tt.root)
1508
tt.new_file('bar', foo, [b'foobar'])
1509
baz = tt.new_directory('baz', tt.root)
1510
tt.new_file('qux', baz, [b'quux'])
1511
# Ask for a rename 'foo' -> 'baz'
1512
tt.adjust_path('baz', tt.root, foo)
1513
# Lie to tt that we've already resolved all conflicts.
1514
tt.apply(no_conflicts=True)
1515
except BaseException:
1518
# The rename will fail because the target directory is not empty (but
1519
# raises FileExists anyway).
1520
err = self.assertRaises(errors.FileExists, tt_helper)
1521
self.assertEndsWith(err.path, "/baz")
1523
def test_two_directories_clash(self):
1525
wt = self.make_branch_and_tree('.')
1526
tt = TreeTransform(wt) # TreeTransform obtains write lock
1528
foo_1 = tt.new_directory('foo', tt.root)
1529
tt.new_directory('bar', foo_1)
1530
# Adding the same directory with a different content
1531
foo_2 = tt.new_directory('foo', tt.root)
1532
tt.new_directory('baz', foo_2)
1533
# Lie to tt that we've already resolved all conflicts.
1534
tt.apply(no_conflicts=True)
1535
except BaseException:
1538
err = self.assertRaises(errors.FileExists, tt_helper)
1539
self.assertEndsWith(err.path, "/foo")
1541
def test_two_directories_clash_finalize(self):
1543
wt = self.make_branch_and_tree('.')
1544
tt = TreeTransform(wt) # TreeTransform obtains write lock
1546
foo_1 = tt.new_directory('foo', tt.root)
1547
tt.new_directory('bar', foo_1)
1548
# Adding the same directory with a different content
1549
foo_2 = tt.new_directory('foo', tt.root)
1550
tt.new_directory('baz', foo_2)
1551
# Lie to tt that we've already resolved all conflicts.
1552
tt.apply(no_conflicts=True)
1553
except BaseException:
1556
err = self.assertRaises(errors.FileExists, tt_helper)
1557
self.assertEndsWith(err.path, "/foo")
1559
def test_file_to_directory(self):
1560
wt = self.make_branch_and_tree('.')
1561
self.build_tree(['foo'])
1564
tt = TreeTransform(wt)
1565
self.addCleanup(tt.finalize)
1566
foo_trans_id = tt.trans_id_tree_path("foo")
1567
tt.delete_contents(foo_trans_id)
1568
tt.create_directory(foo_trans_id)
1569
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1570
tt.create_file([b"aa\n"], bar_trans_id)
1571
tt.version_file(b"bar-1", bar_trans_id)
1573
self.assertPathExists("foo/bar")
1576
self.assertEqual(wt.kind("foo"), "directory")
1580
changes = wt.changes_from(wt.basis_tree())
1581
self.assertFalse(changes.has_changed(), changes)
1583
def test_file_to_symlink(self):
1584
self.requireFeature(SymlinkFeature)
1585
wt = self.make_branch_and_tree('.')
1586
self.build_tree(['foo'])
1589
tt = TreeTransform(wt)
1590
self.addCleanup(tt.finalize)
1591
foo_trans_id = tt.trans_id_tree_path("foo")
1592
tt.delete_contents(foo_trans_id)
1593
tt.create_symlink("bar", foo_trans_id)
1595
self.assertPathExists("foo")
1597
self.addCleanup(wt.unlock)
1598
self.assertEqual(wt.kind("foo"), "symlink")
1600
def test_file_to_symlink_unsupported(self):
1601
wt = self.make_branch_and_tree('.')
1602
self.build_tree(['foo'])
1605
self.overrideAttr(osutils, 'supports_symlinks', lambda p: False)
1606
tt = TreeTransform(wt)
1607
self.addCleanup(tt.finalize)
1608
foo_trans_id = tt.trans_id_tree_path("foo")
1609
tt.delete_contents(foo_trans_id)
1611
trace.push_log_file(log)
1612
tt.create_symlink("bar", foo_trans_id)
1614
self.assertContainsRe(
1616
b'Unable to create symlink "foo" on this filesystem')
1618
def test_dir_to_file(self):
1619
wt = self.make_branch_and_tree('.')
1620
self.build_tree(['foo/', 'foo/bar'])
1621
wt.add(['foo', 'foo/bar'])
1623
tt = TreeTransform(wt)
1624
self.addCleanup(tt.finalize)
1625
foo_trans_id = tt.trans_id_tree_path("foo")
1626
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1627
tt.delete_contents(foo_trans_id)
1628
tt.delete_versioned(bar_trans_id)
1629
tt.create_file([b"aa\n"], foo_trans_id)
1631
self.assertPathExists("foo")
1633
self.addCleanup(wt.unlock)
1634
self.assertEqual(wt.kind("foo"), "file")
1636
def test_dir_to_hardlink(self):
1637
self.requireFeature(HardlinkFeature)
1638
wt = self.make_branch_and_tree('.')
1639
self.build_tree(['foo/', 'foo/bar'])
1640
wt.add(['foo', 'foo/bar'])
1642
tt = TreeTransform(wt)
1643
self.addCleanup(tt.finalize)
1644
foo_trans_id = tt.trans_id_tree_path("foo")
1645
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1646
tt.delete_contents(foo_trans_id)
1647
tt.delete_versioned(bar_trans_id)
1648
self.build_tree(['baz'])
1649
tt.create_hardlink("baz", foo_trans_id)
1651
self.assertPathExists("foo")
1652
self.assertPathExists("baz")
1654
self.addCleanup(wt.unlock)
1655
self.assertEqual(wt.kind("foo"), "file")
1657
def test_no_final_path(self):
1658
transform, root = self.get_transform()
1659
trans_id = transform.trans_id_file_id(b'foo')
1660
transform.create_file([b'bar'], trans_id)
1661
transform.cancel_creation(trans_id)
1664
def test_create_from_tree(self):
1665
tree1 = self.make_branch_and_tree('tree1')
1666
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1667
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1668
tree2 = self.make_branch_and_tree('tree2')
1669
tt = TreeTransform(tree2)
1670
foo_trans_id = tt.create_path('foo', tt.root)
1671
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1672
bar_trans_id = tt.create_path('bar', tt.root)
1673
create_from_tree(tt, bar_trans_id, tree1, 'bar')
1675
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1676
self.assertFileEqual(b'baz', 'tree2/bar')
1678
def test_create_from_tree_bytes(self):
1679
"""Provided lines are used instead of tree content."""
1680
tree1 = self.make_branch_and_tree('tree1')
1681
self.build_tree_contents([('tree1/foo', b'bar'), ])
1682
tree1.add('foo', b'foo-id')
1683
tree2 = self.make_branch_and_tree('tree2')
1684
tt = TreeTransform(tree2)
1685
foo_trans_id = tt.create_path('foo', tt.root)
1686
create_from_tree(tt, foo_trans_id, tree1, 'foo', chunks=[b'qux'])
1688
self.assertFileEqual(b'qux', 'tree2/foo')
1690
def test_create_from_tree_symlink(self):
1691
self.requireFeature(SymlinkFeature)
1692
tree1 = self.make_branch_and_tree('tree1')
1693
os.symlink('bar', 'tree1/foo')
1694
tree1.add('foo', b'foo-id')
1695
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1696
foo_trans_id = tt.create_path('foo', tt.root)
1697
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1699
self.assertEqual('bar', os.readlink('tree2/foo'))
1702
89
class TransformGroup(object):
1704
91
def __init__(self, dirname, root_id):
2767
1118
conflicts.pop())
2770
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2771
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2772
(False, False), False)
2773
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2774
('', ''), ('directory', 'directory'), (False, False), False)
2777
class TestTransformPreview(tests.TestCaseWithTransport):
2779
def create_tree(self):
2780
tree = self.make_branch_and_tree('.')
2781
self.build_tree_contents([('a', b'content 1')])
2782
tree.set_root_id(b'TREE_ROOT')
2783
tree.add('a', b'a-id')
2784
tree.commit('rev1', rev_id=b'rev1')
2785
return tree.branch.repository.revision_tree(b'rev1')
2787
def get_empty_preview(self):
2788
repository = self.make_repository('repo')
2789
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2790
preview = TransformPreview(tree)
2791
self.addCleanup(preview.finalize)
2794
def test_transform_preview(self):
2795
revision_tree = self.create_tree()
2796
preview = TransformPreview(revision_tree)
2797
self.addCleanup(preview.finalize)
2799
def test_transform_preview_tree(self):
2800
revision_tree = self.create_tree()
2801
preview = TransformPreview(revision_tree)
2802
self.addCleanup(preview.finalize)
2803
preview.get_preview_tree()
2805
def test_transform_new_file(self):
2806
revision_tree = self.create_tree()
2807
preview = TransformPreview(revision_tree)
2808
self.addCleanup(preview.finalize)
2809
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2810
preview_tree = preview.get_preview_tree()
2811
self.assertEqual(preview_tree.kind('file2'), 'file')
2812
with preview_tree.get_file('file2') as f:
2813
self.assertEqual(f.read(), b'content B\n')
2815
def test_diff_preview_tree(self):
2816
revision_tree = self.create_tree()
2817
preview = TransformPreview(revision_tree)
2818
self.addCleanup(preview.finalize)
2819
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2820
preview_tree = preview.get_preview_tree()
2822
show_diff_trees(revision_tree, preview_tree, out)
2823
lines = out.getvalue().splitlines()
2824
self.assertEqual(lines[0], b"=== added file 'file2'")
2825
# 3 lines of diff administrivia
2826
self.assertEqual(lines[4], b"+content B")
2828
def test_unsupported_symlink_diff(self):
2829
self.requireFeature(SymlinkFeature)
2830
tree = self.make_branch_and_tree('.')
2831
self.build_tree_contents([('a', 'content 1')])
2832
tree.set_root_id(b'TREE_ROOT')
2833
tree.add('a', b'a-id')
2834
os.symlink('a', 'foo')
2835
tree.add('foo', b'foo-id')
2836
tree.commit('rev1', rev_id=b'rev1')
2837
revision_tree = tree.branch.repository.revision_tree(b'rev1')
2838
preview = TransformPreview(revision_tree)
2839
self.addCleanup(preview.finalize)
2840
preview.delete_versioned(preview.trans_id_tree_path('foo'))
2841
preview_tree = preview.get_preview_tree()
2844
trace.push_log_file(log)
2845
os_symlink = getattr(os, 'symlink', None)
2848
show_diff_trees(revision_tree, preview_tree, out)
2849
lines = out.getvalue().splitlines()
2851
os.symlink = os_symlink
2852
self.assertContainsRe(
2854
b'Ignoring "foo" as symlinks are not supported on this filesystem')
2856
def test_transform_conflicts(self):
2857
revision_tree = self.create_tree()
2858
preview = TransformPreview(revision_tree)
2859
self.addCleanup(preview.finalize)
2860
preview.new_file('a', preview.root, [b'content 2'])
2861
resolve_conflicts(preview)
2862
trans_id = preview.trans_id_file_id(b'a-id')
2863
self.assertEqual('a.moved', preview.final_name(trans_id))
2865
def get_tree_and_preview_tree(self):
2866
revision_tree = self.create_tree()
2867
preview = TransformPreview(revision_tree)
2868
self.addCleanup(preview.finalize)
2869
a_trans_id = preview.trans_id_file_id(b'a-id')
2870
preview.delete_contents(a_trans_id)
2871
preview.create_file([b'b content'], a_trans_id)
2872
preview_tree = preview.get_preview_tree()
2873
return revision_tree, preview_tree
2875
def test_iter_changes(self):
2876
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2877
root = revision_tree.path2id('')
2878
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2879
(root, root), ('a', 'a'), ('file', 'file'),
2880
(False, False), False)],
2881
list(preview_tree.iter_changes(revision_tree)))
2883
def test_include_unchanged_succeeds(self):
2884
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2885
changes = preview_tree.iter_changes(revision_tree,
2886
include_unchanged=True)
2887
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2889
def test_specific_files(self):
2890
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2891
changes = preview_tree.iter_changes(revision_tree,
2892
specific_files=[''])
2893
self.assertEqual([A_ENTRY], list(changes))
2895
def test_want_unversioned(self):
2896
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2897
changes = preview_tree.iter_changes(revision_tree,
2898
want_unversioned=True)
2899
self.assertEqual([A_ENTRY], list(changes))
2901
def test_ignore_extra_trees_no_specific_files(self):
2902
# extra_trees is harmless without specific_files, so we'll silently
2903
# accept it, even though we won't use it.
2904
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2905
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2907
def test_ignore_require_versioned_no_specific_files(self):
2908
# require_versioned is meaningless without specific_files.
2909
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2910
preview_tree.iter_changes(revision_tree, require_versioned=False)
2912
def test_ignore_pb(self):
2913
# pb could be supported, but TT.iter_changes doesn't support it.
2914
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2915
preview_tree.iter_changes(revision_tree)
2917
def test_kind(self):
2918
revision_tree = self.create_tree()
2919
preview = TransformPreview(revision_tree)
2920
self.addCleanup(preview.finalize)
2921
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2922
preview.new_directory('directory', preview.root, b'dir-id')
2923
preview_tree = preview.get_preview_tree()
2924
self.assertEqual('file', preview_tree.kind('file'))
2925
self.assertEqual('directory', preview_tree.kind('directory'))
2927
def test_get_file_mtime(self):
2928
preview = self.get_empty_preview()
2929
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2931
limbo_path = preview._limbo_name(file_trans_id)
2932
preview_tree = preview.get_preview_tree()
2933
self.assertEqual(os.stat(limbo_path).st_mtime,
2934
preview_tree.get_file_mtime('file'))
2936
def test_get_file_mtime_renamed(self):
2937
work_tree = self.make_branch_and_tree('tree')
2938
self.build_tree(['tree/file'])
2939
work_tree.add('file', b'file-id')
2940
preview = TransformPreview(work_tree)
2941
self.addCleanup(preview.finalize)
2942
file_trans_id = preview.trans_id_tree_path('file')
2943
preview.adjust_path('renamed', preview.root, file_trans_id)
2944
preview_tree = preview.get_preview_tree()
2945
preview_mtime = preview_tree.get_file_mtime('renamed')
2946
work_mtime = work_tree.get_file_mtime('file')
2948
def test_get_file_size(self):
2949
work_tree = self.make_branch_and_tree('tree')
2950
self.build_tree_contents([('tree/old', b'old')])
2951
work_tree.add('old', b'old-id')
2952
preview = TransformPreview(work_tree)
2953
self.addCleanup(preview.finalize)
2954
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2956
tree = preview.get_preview_tree()
2957
self.assertEqual(len('old'), tree.get_file_size('old'))
2958
self.assertEqual(len('contents'), tree.get_file_size('name'))
2960
def test_get_file(self):
2961
preview = self.get_empty_preview()
2962
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2963
preview_tree = preview.get_preview_tree()
2964
with preview_tree.get_file('file') as tree_file:
2965
self.assertEqual(b'contents', tree_file.read())
2967
def test_get_symlink_target(self):
2968
self.requireFeature(SymlinkFeature)
2969
preview = self.get_empty_preview()
2970
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2971
preview_tree = preview.get_preview_tree()
2972
self.assertEqual('target',
2973
preview_tree.get_symlink_target('symlink'))
2975
def test_all_file_ids(self):
2976
tree = self.make_branch_and_tree('tree')
2977
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2978
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2979
preview = TransformPreview(tree)
2980
self.addCleanup(preview.finalize)
2981
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2982
c_trans_id = preview.trans_id_file_id(b'c-id')
2983
preview.unversion_file(c_trans_id)
2984
preview.version_file(b'c-id', c_trans_id)
2985
preview_tree = preview.get_preview_tree()
2986
self.assertEqual({b'a-id', b'c-id', tree.path2id('')},
2987
preview_tree.all_file_ids())
2989
def test_path2id_deleted_unchanged(self):
2990
tree = self.make_branch_and_tree('tree')
2991
self.build_tree(['tree/unchanged', 'tree/deleted'])
2992
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2993
preview = TransformPreview(tree)
2994
self.addCleanup(preview.finalize)
2995
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2996
preview_tree = preview.get_preview_tree()
2997
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2998
self.assertFalse(preview_tree.is_versioned('deleted'))
3000
def test_path2id_created(self):
3001
tree = self.make_branch_and_tree('tree')
3002
self.build_tree(['tree/unchanged'])
3003
tree.add(['unchanged'], [b'unchanged-id'])
3004
preview = TransformPreview(tree)
3005
self.addCleanup(preview.finalize)
3006
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
3007
[b'contents'], b'new-id')
3008
preview_tree = preview.get_preview_tree()
3009
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3011
def test_path2id_moved(self):
3012
tree = self.make_branch_and_tree('tree')
3013
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3014
tree.add(['old_parent', 'old_parent/child'],
3015
[b'old_parent-id', b'child-id'])
3016
preview = TransformPreview(tree)
3017
self.addCleanup(preview.finalize)
3018
new_parent = preview.new_directory('new_parent', preview.root,
3020
preview.adjust_path('child', new_parent,
3021
preview.trans_id_file_id(b'child-id'))
3022
preview_tree = preview.get_preview_tree()
3023
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3024
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3026
def test_path2id_renamed_parent(self):
3027
tree = self.make_branch_and_tree('tree')
3028
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3029
tree.add(['old_name', 'old_name/child'],
3030
[b'parent-id', b'child-id'])
3031
preview = TransformPreview(tree)
3032
self.addCleanup(preview.finalize)
3033
preview.adjust_path('new_name', preview.root,
3034
preview.trans_id_file_id(b'parent-id'))
3035
preview_tree = preview.get_preview_tree()
3036
self.assertFalse(preview_tree.is_versioned('old_name/child'))
3037
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3039
def assertMatchingIterEntries(self, tt, specific_files=None):
3040
preview_tree = tt.get_preview_tree()
3041
preview_result = list(preview_tree.iter_entries_by_dir(
3042
specific_files=specific_files))
3045
actual_result = list(tree.iter_entries_by_dir(
3046
specific_files=specific_files))
3047
self.assertEqual(actual_result, preview_result)
3049
def test_iter_entries_by_dir_new(self):
3050
tree = self.make_branch_and_tree('tree')
3051
tt = TreeTransform(tree)
3052
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3053
self.assertMatchingIterEntries(tt)
3055
def test_iter_entries_by_dir_deleted(self):
3056
tree = self.make_branch_and_tree('tree')
3057
self.build_tree(['tree/deleted'])
3058
tree.add('deleted', b'deleted-id')
3059
tt = TreeTransform(tree)
3060
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3061
self.assertMatchingIterEntries(tt)
3063
def test_iter_entries_by_dir_unversioned(self):
3064
tree = self.make_branch_and_tree('tree')
3065
self.build_tree(['tree/removed'])
3066
tree.add('removed', b'removed-id')
3067
tt = TreeTransform(tree)
3068
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3069
self.assertMatchingIterEntries(tt)
3071
def test_iter_entries_by_dir_moved(self):
3072
tree = self.make_branch_and_tree('tree')
3073
self.build_tree(['tree/moved', 'tree/new_parent/'])
3074
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3075
tt = TreeTransform(tree)
3076
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3077
tt.trans_id_file_id(b'moved-id'))
3078
self.assertMatchingIterEntries(tt)
3080
def test_iter_entries_by_dir_specific_files(self):
3081
tree = self.make_branch_and_tree('tree')
3082
tree.set_root_id(b'tree-root-id')
3083
self.build_tree(['tree/parent/', 'tree/parent/child'])
3084
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3085
tt = TreeTransform(tree)
3086
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3088
def test_symlink_content_summary(self):
3089
self.requireFeature(SymlinkFeature)
3090
preview = self.get_empty_preview()
3091
preview.new_symlink('path', preview.root, 'target', b'path-id')
3092
summary = preview.get_preview_tree().path_content_summary('path')
3093
self.assertEqual(('symlink', None, None, 'target'), summary)
3095
def test_missing_content_summary(self):
3096
preview = self.get_empty_preview()
3097
summary = preview.get_preview_tree().path_content_summary('path')
3098
self.assertEqual(('missing', None, None, None), summary)
3100
def test_deleted_content_summary(self):
3101
tree = self.make_branch_and_tree('tree')
3102
self.build_tree(['tree/path/'])
3104
preview = TransformPreview(tree)
3105
self.addCleanup(preview.finalize)
3106
preview.delete_contents(preview.trans_id_tree_path('path'))
3107
summary = preview.get_preview_tree().path_content_summary('path')
3108
self.assertEqual(('missing', None, None, None), summary)
3110
def test_file_content_summary_executable(self):
3111
preview = self.get_empty_preview()
3112
path_id = preview.new_file('path', preview.root, [
3113
b'contents'], b'path-id')
3114
preview.set_executability(True, path_id)
3115
summary = preview.get_preview_tree().path_content_summary('path')
3116
self.assertEqual(4, len(summary))
3117
self.assertEqual('file', summary[0])
3118
# size must be known
3119
self.assertEqual(len('contents'), summary[1])
3121
self.assertEqual(True, summary[2])
3122
# will not have hash (not cheap to determine)
3123
self.assertIs(None, summary[3])
3125
def test_change_executability(self):
3126
tree = self.make_branch_and_tree('tree')
3127
self.build_tree(['tree/path'])
3129
preview = TransformPreview(tree)
3130
self.addCleanup(preview.finalize)
3131
path_id = preview.trans_id_tree_path('path')
3132
preview.set_executability(True, path_id)
3133
summary = preview.get_preview_tree().path_content_summary('path')
3134
self.assertEqual(True, summary[2])
3136
def test_file_content_summary_non_exec(self):
3137
preview = self.get_empty_preview()
3138
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3139
summary = preview.get_preview_tree().path_content_summary('path')
3140
self.assertEqual(4, len(summary))
3141
self.assertEqual('file', summary[0])
3142
# size must be known
3143
self.assertEqual(len('contents'), summary[1])
3145
self.assertEqual(False, summary[2])
3146
# will not have hash (not cheap to determine)
3147
self.assertIs(None, summary[3])
3149
def test_dir_content_summary(self):
3150
preview = self.get_empty_preview()
3151
preview.new_directory('path', preview.root, b'path-id')
3152
summary = preview.get_preview_tree().path_content_summary('path')
3153
self.assertEqual(('directory', None, None, None), summary)
3155
def test_tree_content_summary(self):
3156
preview = self.get_empty_preview()
3157
path = preview.new_directory('path', preview.root, b'path-id')
3158
preview.set_tree_reference(b'rev-1', path)
3159
summary = preview.get_preview_tree().path_content_summary('path')
3160
self.assertEqual(4, len(summary))
3161
self.assertEqual('tree-reference', summary[0])
3163
def test_annotate(self):
3164
tree = self.make_branch_and_tree('tree')
3165
self.build_tree_contents([('tree/file', b'a\n')])
3166
tree.add('file', b'file-id')
3167
tree.commit('a', rev_id=b'one')
3168
self.build_tree_contents([('tree/file', b'a\nb\n')])
3169
preview = TransformPreview(tree)
3170
self.addCleanup(preview.finalize)
3171
file_trans_id = preview.trans_id_file_id(b'file-id')
3172
preview.delete_contents(file_trans_id)
3173
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3174
preview_tree = preview.get_preview_tree()
3180
annotation = preview_tree.annotate_iter(
3181
'file', default_revision=b'me:')
3182
self.assertEqual(expected, annotation)
3184
def test_annotate_missing(self):
3185
preview = self.get_empty_preview()
3186
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3187
preview_tree = preview.get_preview_tree()
3193
annotation = preview_tree.annotate_iter(
3194
'file', default_revision=b'me:')
3195
self.assertEqual(expected, annotation)
3197
def test_annotate_rename(self):
3198
tree = self.make_branch_and_tree('tree')
3199
self.build_tree_contents([('tree/file', b'a\n')])
3200
tree.add('file', b'file-id')
3201
tree.commit('a', rev_id=b'one')
3202
preview = TransformPreview(tree)
3203
self.addCleanup(preview.finalize)
3204
file_trans_id = preview.trans_id_file_id(b'file-id')
3205
preview.adjust_path('newname', preview.root, file_trans_id)
3206
preview_tree = preview.get_preview_tree()
3210
annotation = preview_tree.annotate_iter(
3211
'file', default_revision=b'me:')
3212
self.assertEqual(expected, annotation)
3214
def test_annotate_deleted(self):
3215
tree = self.make_branch_and_tree('tree')
3216
self.build_tree_contents([('tree/file', b'a\n')])
3217
tree.add('file', b'file-id')
3218
tree.commit('a', rev_id=b'one')
3219
self.build_tree_contents([('tree/file', b'a\nb\n')])
3220
preview = TransformPreview(tree)
3221
self.addCleanup(preview.finalize)
3222
file_trans_id = preview.trans_id_file_id(b'file-id')
3223
preview.delete_contents(file_trans_id)
3224
preview_tree = preview.get_preview_tree()
3225
annotation = preview_tree.annotate_iter(
3226
'file', default_revision=b'me:')
3227
self.assertIs(None, annotation)
3229
def test_stored_kind(self):
3230
preview = self.get_empty_preview()
3231
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3232
preview_tree = preview.get_preview_tree()
3233
self.assertEqual('file', preview_tree.stored_kind('file'))
3235
def test_is_executable(self):
3236
preview = self.get_empty_preview()
3237
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3238
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3239
preview_tree = preview.get_preview_tree()
3240
self.assertEqual(True, preview_tree.is_executable('file'))
3242
def test_get_set_parent_ids(self):
3243
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3244
self.assertEqual([], preview_tree.get_parent_ids())
3245
preview_tree.set_parent_ids([b'rev-1'])
3246
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3248
def test_plan_file_merge(self):
3249
work_a = self.make_branch_and_tree('wta')
3250
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3251
work_a.add('file', b'file-id')
3252
base_id = work_a.commit('base version')
3253
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3254
preview = TransformPreview(work_a)
3255
self.addCleanup(preview.finalize)
3256
trans_id = preview.trans_id_file_id(b'file-id')
3257
preview.delete_contents(trans_id)
3258
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3259
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3260
tree_a = preview.get_preview_tree()
3261
tree_a.set_parent_ids([base_id])
3263
('killed-a', b'a\n'),
3264
('killed-b', b'b\n'),
3265
('unchanged', b'c\n'),
3266
('unchanged', b'd\n'),
3269
], list(tree_a.plan_file_merge('file', tree_b)))
3271
def test_plan_file_merge_revision_tree(self):
3272
work_a = self.make_branch_and_tree('wta')
3273
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3274
work_a.add('file', b'file-id')
3275
base_id = work_a.commit('base version')
3276
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3277
preview = TransformPreview(work_a.basis_tree())
3278
self.addCleanup(preview.finalize)
3279
trans_id = preview.trans_id_file_id(b'file-id')
3280
preview.delete_contents(trans_id)
3281
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3282
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3283
tree_a = preview.get_preview_tree()
3284
tree_a.set_parent_ids([base_id])
3286
('killed-a', b'a\n'),
3287
('killed-b', b'b\n'),
3288
('unchanged', b'c\n'),
3289
('unchanged', b'd\n'),
3292
], list(tree_a.plan_file_merge('file', tree_b)))
3294
def test_walkdirs(self):
3295
preview = self.get_empty_preview()
3296
preview.new_directory('', ROOT_PARENT, b'tree-root')
3297
# FIXME: new_directory should mark root.
3298
preview.fixup_new_roots()
3299
preview_tree = preview.get_preview_tree()
3300
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3301
expected = [(('', b'tree-root'),
3302
[('a', 'a', 'file', None, b'a-id', 'file')])]
3303
self.assertEqual(expected, list(preview_tree.walkdirs()))
3305
def test_extras(self):
3306
work_tree = self.make_branch_and_tree('tree')
3307
self.build_tree(['tree/removed-file', 'tree/existing-file',
3308
'tree/not-removed-file'])
3309
work_tree.add(['removed-file', 'not-removed-file'])
3310
preview = TransformPreview(work_tree)
3311
self.addCleanup(preview.finalize)
3312
preview.new_file('new-file', preview.root, [b'contents'])
3313
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3314
b'new-versioned-id')
3315
tree = preview.get_preview_tree()
3316
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3317
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3320
def test_merge_into_preview(self):
3321
work_tree = self.make_branch_and_tree('tree')
3322
self.build_tree_contents([('tree/file', b'b\n')])
3323
work_tree.add('file', b'file-id')
3324
work_tree.commit('first commit')
3325
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3326
self.build_tree_contents([('child/file', b'b\nc\n')])
3327
child_tree.commit('child commit')
3328
child_tree.lock_write()
3329
self.addCleanup(child_tree.unlock)
3330
work_tree.lock_write()
3331
self.addCleanup(work_tree.unlock)
3332
preview = TransformPreview(work_tree)
3333
self.addCleanup(preview.finalize)
3334
file_trans_id = preview.trans_id_file_id(b'file-id')
3335
preview.delete_contents(file_trans_id)
3336
preview.create_file([b'a\nb\n'], file_trans_id)
3337
preview_tree = preview.get_preview_tree()
3338
merger = Merger.from_revision_ids(preview_tree,
3339
child_tree.branch.last_revision(),
3340
other_branch=child_tree.branch,
3341
tree_branch=work_tree.branch)
3342
merger.merge_type = Merge3Merger
3343
tt = merger.make_merger().make_preview_transform()
3344
self.addCleanup(tt.finalize)
3345
final_tree = tt.get_preview_tree()
3348
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3350
def test_merge_preview_into_workingtree(self):
3351
tree = self.make_branch_and_tree('tree')
3352
tree.set_root_id(b'TREE_ROOT')
3353
tt = TransformPreview(tree)
3354
self.addCleanup(tt.finalize)
3355
tt.new_file('name', tt.root, [b'content'], b'file-id')
3356
tree2 = self.make_branch_and_tree('tree2')
3357
tree2.set_root_id(b'TREE_ROOT')
3358
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3360
merger.merge_type = Merge3Merger
3363
def test_merge_preview_into_workingtree_handles_conflicts(self):
3364
tree = self.make_branch_and_tree('tree')
3365
self.build_tree_contents([('tree/foo', b'bar')])
3366
tree.add('foo', b'foo-id')
3368
tt = TransformPreview(tree)
3369
self.addCleanup(tt.finalize)
3370
trans_id = tt.trans_id_file_id(b'foo-id')
3371
tt.delete_contents(trans_id)
3372
tt.create_file([b'baz'], trans_id)
3373
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3374
self.build_tree_contents([('tree2/foo', b'qux')])
3375
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3377
merger.merge_type = Merge3Merger
3380
def test_has_filename(self):
3381
wt = self.make_branch_and_tree('tree')
3382
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3383
tt = TransformPreview(wt)
3384
removed_id = tt.trans_id_tree_path('removed')
3385
tt.delete_contents(removed_id)
3386
tt.new_file('new', tt.root, [b'contents'])
3387
modified_id = tt.trans_id_tree_path('modified')
3388
tt.delete_contents(modified_id)
3389
tt.create_file([b'modified-contents'], modified_id)
3390
self.addCleanup(tt.finalize)
3391
tree = tt.get_preview_tree()
3392
self.assertTrue(tree.has_filename('unmodified'))
3393
self.assertFalse(tree.has_filename('not-present'))
3394
self.assertFalse(tree.has_filename('removed'))
3395
self.assertTrue(tree.has_filename('new'))
3396
self.assertTrue(tree.has_filename('modified'))
3398
def test_is_executable(self):
3399
tree = self.make_branch_and_tree('tree')
3400
preview = TransformPreview(tree)
3401
self.addCleanup(preview.finalize)
3402
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3403
preview_tree = preview.get_preview_tree()
3404
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3406
def test_commit_preview_tree(self):
3407
tree = self.make_branch_and_tree('tree')
3408
rev_id = tree.commit('rev1')
3409
tree.branch.lock_write()
3410
self.addCleanup(tree.branch.unlock)
3411
tt = TransformPreview(tree)
3412
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3413
self.addCleanup(tt.finalize)
3414
preview = tt.get_preview_tree()
3415
preview.set_parent_ids([rev_id])
3416
builder = tree.branch.get_commit_builder([rev_id])
3417
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3418
builder.finish_inventory()
3419
rev2_id = builder.commit('rev2')
3420
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3421
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3423
def test_ascii_limbo_paths(self):
3424
self.requireFeature(features.UnicodeFilenameFeature)
3425
branch = self.make_branch('any')
3426
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3427
tt = TransformPreview(tree)
3428
self.addCleanup(tt.finalize)
3429
foo_id = tt.new_directory('', ROOT_PARENT)
3430
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3431
limbo_path = tt._limbo_name(bar_id)
3432
self.assertEqual(limbo_path, limbo_path)
3435
1121
class FakeSerializer(object):
3436
1122
"""Serializer implementation that simply returns the input.