46
from bzrlib.controldir import ControlDir
47
from bzrlib.diff import show_diff_trees
48
from bzrlib.errors import (
48
from ..controldir import ControlDir
49
from ..diff import show_diff_trees
50
from ..errors import (
51
53
ExistingPendingDeletion,
53
54
ImmortalPendingDeletion,
58
from bzrlib.osutils import (
57
from ..osutils import (
62
from bzrlib.merge import Merge3Merger, Merger
63
from bzrlib.mutabletree import MutableTree
64
from bzrlib.tests import (
61
from ..merge import Merge3Merger, Merger
62
from ..mutabletree import MutableTree
69
from bzrlib.tests.features import (
68
from .features import (
73
from bzrlib.transform import (
72
from ..transform import (
84
TransformRenameFailed,
87
class TestTreeTransform(tests.TestCaseWithTransport):
90
super(TestTreeTransform, self).setUp()
91
self.wt = self.make_branch_and_tree('.', format='development-subtree')
94
def get_transform(self):
95
transform = TreeTransform(self.wt)
96
self.addCleanup(transform.finalize)
97
return transform, transform.root
99
def get_transform_for_sha1_test(self):
100
trans, root = self.get_transform()
101
self.wt.lock_tree_write()
102
self.addCleanup(self.wt.unlock)
103
contents = ['just some content\n']
104
sha1 = osutils.sha_strings(contents)
105
# Roll back the clock
106
trans._creation_mtime = time.time() - 20.0
107
return trans, root, contents, sha1
109
def test_existing_limbo(self):
110
transform, root = self.get_transform()
111
limbo_name = transform._limbodir
112
deletion_path = transform._deletiondir
113
os.mkdir(pathjoin(limbo_name, 'hehe'))
114
self.assertRaises(ImmortalLimbo, transform.apply)
115
self.assertRaises(LockError, self.wt.unlock)
116
self.assertRaises(ExistingLimbo, self.get_transform)
117
self.assertRaises(LockError, self.wt.unlock)
118
os.rmdir(pathjoin(limbo_name, 'hehe'))
120
os.rmdir(deletion_path)
121
transform, root = self.get_transform()
124
def test_existing_pending_deletion(self):
125
transform, root = self.get_transform()
126
deletion_path = self._limbodir = urlutils.local_path_from_url(
127
transform._tree._transport.abspath('pending-deletion'))
128
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
129
self.assertRaises(ImmortalPendingDeletion, transform.apply)
130
self.assertRaises(LockError, self.wt.unlock)
131
self.assertRaises(ExistingPendingDeletion, self.get_transform)
133
def test_build(self):
134
transform, root = self.get_transform()
135
self.wt.lock_tree_write()
136
self.addCleanup(self.wt.unlock)
137
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
138
imaginary_id = transform.trans_id_tree_path('imaginary')
139
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
140
self.assertEqual(imaginary_id, imaginary_id2)
141
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
142
self.assertEqual('directory', transform.final_kind(root))
143
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
144
trans_id = transform.create_path('name', root)
145
self.assertIs(transform.final_file_id(trans_id), None)
146
self.assertIs(None, transform.final_kind(trans_id))
147
transform.create_file('contents', trans_id)
148
transform.set_executability(True, trans_id)
149
transform.version_file('my_pretties', trans_id)
150
self.assertRaises(DuplicateKey, transform.version_file,
151
'my_pretties', trans_id)
152
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
153
self.assertEqual(transform.final_parent(trans_id), root)
154
self.assertIs(transform.final_parent(root), ROOT_PARENT)
155
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
156
oz_id = transform.create_path('oz', root)
157
transform.create_directory(oz_id)
158
transform.version_file('ozzie', oz_id)
159
trans_id2 = transform.create_path('name2', root)
160
transform.create_file('contents', trans_id2)
161
transform.set_executability(False, trans_id2)
162
transform.version_file('my_pretties2', trans_id2)
163
modified_paths = transform.apply().modified_paths
164
self.assertEqual('contents', self.wt.get_file_byname('name').read())
165
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
166
self.assertIs(self.wt.is_executable('my_pretties'), True)
167
self.assertIs(self.wt.is_executable('my_pretties2'), False)
168
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
169
self.assertEqual(len(modified_paths), 3)
170
tree_mod_paths = [self.wt.id2abspath(f) for f in
171
('ozzie', 'my_pretties', 'my_pretties2')]
172
self.assertSubset(tree_mod_paths, modified_paths)
173
# is it safe to finalize repeatedly?
177
def test_apply_informs_tree_of_observed_sha1(self):
178
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
179
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
182
orig = self.wt._observed_sha1
183
def _observed_sha1(*args):
186
self.wt._observed_sha1 = _observed_sha1
188
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
191
def test_create_file_caches_sha1(self):
192
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
193
trans_id = trans.create_path('file1', root)
194
trans.create_file(contents, trans_id, sha1=sha1)
195
st_val = osutils.lstat(trans._limbo_name(trans_id))
196
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
197
self.assertEqual(o_sha1, sha1)
198
self.assertEqualStat(o_st_val, st_val)
200
def test__apply_insertions_updates_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)
208
creation_mtime = trans._creation_mtime + 10.0
209
# We fake a time difference from when the file was created until now it
210
# is being renamed by using os.utime. Note that the change we actually
211
# want to see is the real ctime change from 'os.rename()', but as long
212
# as we observe a new stat value, we should be fine.
213
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
215
new_st_val = osutils.lstat(self.wt.abspath('file1'))
216
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
217
self.assertEqual(o_sha1, sha1)
218
self.assertEqualStat(o_st_val, new_st_val)
219
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
221
def test_new_file_caches_sha1(self):
222
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
223
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
225
st_val = osutils.lstat(trans._limbo_name(trans_id))
226
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
227
self.assertEqual(o_sha1, sha1)
228
self.assertEqualStat(o_st_val, st_val)
230
def test_cancel_creation_removes_observed_sha1(self):
231
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
232
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
234
self.assertTrue(trans_id in trans._observed_sha1s)
235
trans.cancel_creation(trans_id)
236
self.assertFalse(trans_id in trans._observed_sha1s)
238
def test_create_files_same_timestamp(self):
239
transform, root = self.get_transform()
240
self.wt.lock_tree_write()
241
self.addCleanup(self.wt.unlock)
242
# Roll back the clock, so that we know everything is being set to the
244
transform._creation_mtime = creation_mtime = time.time() - 20.0
245
transform.create_file('content-one',
246
transform.create_path('one', root))
247
time.sleep(1) # *ugly*
248
transform.create_file('content-two',
249
transform.create_path('two', root))
251
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
253
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
255
# We only guarantee 2s resolution
256
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
257
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
258
# But if we have more than that, all files should get the same result
259
self.assertEqual(st1.st_mtime, st2.st_mtime)
261
def test_change_root_id(self):
262
transform, root = self.get_transform()
263
self.assertNotEqual('new-root-id', self.wt.get_root_id())
264
transform.new_directory('', ROOT_PARENT, 'new-root-id')
265
transform.delete_contents(root)
266
transform.unversion_file(root)
267
transform.fixup_new_roots()
269
self.assertEqual('new-root-id', self.wt.get_root_id())
271
def test_change_root_id_add_files(self):
272
transform, root = self.get_transform()
273
self.assertNotEqual('new-root-id', self.wt.get_root_id())
274
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
275
transform.new_file('file', new_trans_id, ['new-contents\n'],
277
transform.delete_contents(root)
278
transform.unversion_file(root)
279
transform.fixup_new_roots()
281
self.assertEqual('new-root-id', self.wt.get_root_id())
282
self.assertEqual('new-file-id', self.wt.path2id('file'))
283
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
285
def test_add_two_roots(self):
286
transform, root = self.get_transform()
287
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
288
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
289
self.assertRaises(ValueError, transform.fixup_new_roots)
291
def test_retain_existing_root(self):
292
tt, root = self.get_transform()
294
tt.new_directory('', ROOT_PARENT, 'new-root-id')
296
self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
298
def test_retain_existing_root_added_file(self):
299
tt, root = self.get_transform()
300
new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
301
child = tt.new_directory('child', new_trans_id, 'child-id')
303
self.assertEqual(tt.root, tt.final_parent(child))
305
def test_add_unversioned_root(self):
306
transform, root = self.get_transform()
307
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
308
transform.delete_contents(transform.root)
309
transform.fixup_new_roots()
310
self.assertNotIn(transform.root, transform._new_id)
312
def test_remove_root_fixup(self):
313
transform, root = self.get_transform()
314
old_root_id = self.wt.get_root_id()
315
self.assertNotEqual('new-root-id', old_root_id)
316
transform.delete_contents(root)
317
transform.unversion_file(root)
318
transform.fixup_new_roots()
320
self.assertEqual(old_root_id, self.wt.get_root_id())
322
transform, root = self.get_transform()
323
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
324
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
325
self.assertRaises(ValueError, transform.fixup_new_roots)
327
def test_fixup_new_roots_permits_empty_tree(self):
328
transform, root = self.get_transform()
329
transform.delete_contents(root)
330
transform.unversion_file(root)
331
transform.fixup_new_roots()
332
self.assertIs(None, transform.final_kind(root))
333
self.assertIs(None, transform.final_file_id(root))
335
def test_apply_retains_root_directory(self):
336
# Do not attempt to delete the physical root directory, because that
338
transform, root = self.get_transform()
340
transform.delete_contents(root)
341
e = self.assertRaises(AssertionError, self.assertRaises,
342
errors.TransformRenameFailed,
344
self.assertContainsRe('TransformRenameFailed not raised', str(e))
346
def test_apply_retains_file_id(self):
347
transform, root = self.get_transform()
348
old_root_id = transform.tree_file_id(root)
349
transform.unversion_file(root)
351
self.assertEqual(old_root_id, self.wt.get_root_id())
353
def test_hardlink(self):
354
self.requireFeature(HardlinkFeature)
355
transform, root = self.get_transform()
356
transform.new_file('file1', root, 'contents')
358
target = self.make_branch_and_tree('target')
359
target_transform = TreeTransform(target)
360
trans_id = target_transform.create_path('file1', target_transform.root)
361
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
362
target_transform.apply()
363
self.assertPathExists('target/file1')
364
source_stat = os.stat(self.wt.abspath('file1'))
365
target_stat = os.stat('target/file1')
366
self.assertEqual(source_stat, target_stat)
368
def test_convenience(self):
369
transform, root = self.get_transform()
370
self.wt.lock_tree_write()
371
self.addCleanup(self.wt.unlock)
372
trans_id = transform.new_file('name', root, 'contents',
374
oz = transform.new_directory('oz', root, 'oz-id')
375
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
376
toto = transform.new_file('toto', dorothy, 'toto-contents',
379
self.assertEqual(len(transform.find_conflicts()), 0)
381
self.assertRaises(ReusingTransform, transform.find_conflicts)
382
self.assertEqual('contents', file(self.wt.abspath('name')).read())
383
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
384
self.assertIs(self.wt.is_executable('my_pretties'), True)
385
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
386
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
387
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
389
self.assertEqual('toto-contents',
390
self.wt.get_file_byname('oz/dorothy/toto').read())
391
self.assertIs(self.wt.is_executable('toto-id'), False)
393
def test_tree_reference(self):
394
transform, root = self.get_transform()
395
tree = transform._tree
396
trans_id = transform.new_directory('reference', root, 'subtree-id')
397
transform.set_tree_reference('subtree-revision', trans_id)
400
self.addCleanup(tree.unlock)
401
self.assertEqual('subtree-revision',
402
tree.root_inventory['subtree-id'].reference_revision)
404
def test_conflicts(self):
405
transform, root = self.get_transform()
406
trans_id = transform.new_file('name', root, 'contents',
408
self.assertEqual(len(transform.find_conflicts()), 0)
409
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
410
self.assertEqual(transform.find_conflicts(),
411
[('duplicate', trans_id, trans_id2, 'name')])
412
self.assertRaises(MalformedTransform, transform.apply)
413
transform.adjust_path('name', trans_id, trans_id2)
414
self.assertEqual(transform.find_conflicts(),
415
[('non-directory parent', trans_id)])
416
tinman_id = transform.trans_id_tree_path('tinman')
417
transform.adjust_path('name', tinman_id, trans_id2)
418
self.assertEqual(transform.find_conflicts(),
419
[('unversioned parent', tinman_id),
420
('missing parent', tinman_id)])
421
lion_id = transform.create_path('lion', root)
422
self.assertEqual(transform.find_conflicts(),
423
[('unversioned parent', tinman_id),
424
('missing parent', tinman_id)])
425
transform.adjust_path('name', lion_id, trans_id2)
426
self.assertEqual(transform.find_conflicts(),
427
[('unversioned parent', lion_id),
428
('missing parent', lion_id)])
429
transform.version_file("Courage", lion_id)
430
self.assertEqual(transform.find_conflicts(),
431
[('missing parent', lion_id),
432
('versioning no contents', lion_id)])
433
transform.adjust_path('name2', root, trans_id2)
434
self.assertEqual(transform.find_conflicts(),
435
[('versioning no contents', lion_id)])
436
transform.create_file('Contents, okay?', lion_id)
437
transform.adjust_path('name2', trans_id2, trans_id2)
438
self.assertEqual(transform.find_conflicts(),
439
[('parent loop', trans_id2),
440
('non-directory parent', trans_id2)])
441
transform.adjust_path('name2', root, trans_id2)
442
oz_id = transform.new_directory('oz', root)
443
transform.set_executability(True, oz_id)
444
self.assertEqual(transform.find_conflicts(),
445
[('unversioned executability', oz_id)])
446
transform.version_file('oz-id', oz_id)
447
self.assertEqual(transform.find_conflicts(),
448
[('non-file executability', oz_id)])
449
transform.set_executability(None, oz_id)
450
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
452
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
453
self.assertEqual('contents', file(self.wt.abspath('name')).read())
454
transform2, root = self.get_transform()
455
oz_id = transform2.trans_id_tree_file_id('oz-id')
456
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
457
result = transform2.find_conflicts()
458
fp = FinalPaths(transform2)
459
self.assert_('oz/tip' in transform2._tree_path_ids)
460
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
461
self.assertEqual(len(result), 2)
462
self.assertEqual((result[0][0], result[0][1]),
463
('duplicate', newtip))
464
self.assertEqual((result[1][0], result[1][2]),
465
('duplicate id', newtip))
466
transform2.finalize()
467
transform3 = TreeTransform(self.wt)
468
self.addCleanup(transform3.finalize)
469
oz_id = transform3.trans_id_tree_file_id('oz-id')
470
transform3.delete_contents(oz_id)
471
self.assertEqual(transform3.find_conflicts(),
472
[('missing parent', oz_id)])
473
root_id = transform3.root
474
tip_id = transform3.trans_id_tree_file_id('tip-id')
475
transform3.adjust_path('tip', root_id, tip_id)
478
def test_conflict_on_case_insensitive(self):
479
tree = self.make_branch_and_tree('tree')
480
# Don't try this at home, kids!
481
# Force the tree to report that it is case sensitive, for conflict
483
tree.case_sensitive = True
484
transform = TreeTransform(tree)
485
self.addCleanup(transform.finalize)
486
transform.new_file('file', transform.root, 'content')
487
transform.new_file('FiLe', transform.root, 'content')
488
result = transform.find_conflicts()
489
self.assertEqual([], result)
491
# Force the tree to report that it is case insensitive, for conflict
493
tree.case_sensitive = False
494
transform = TreeTransform(tree)
495
self.addCleanup(transform.finalize)
496
transform.new_file('file', transform.root, 'content')
497
transform.new_file('FiLe', transform.root, 'content')
498
result = transform.find_conflicts()
499
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
501
def test_conflict_on_case_insensitive_existing(self):
502
tree = self.make_branch_and_tree('tree')
503
self.build_tree(['tree/FiLe'])
504
# Don't try this at home, kids!
505
# Force the tree to report that it is case sensitive, for conflict
507
tree.case_sensitive = True
508
transform = TreeTransform(tree)
509
self.addCleanup(transform.finalize)
510
transform.new_file('file', transform.root, 'content')
511
result = transform.find_conflicts()
512
self.assertEqual([], result)
514
# Force the tree to report that it is case insensitive, for conflict
516
tree.case_sensitive = False
517
transform = TreeTransform(tree)
518
self.addCleanup(transform.finalize)
519
transform.new_file('file', transform.root, 'content')
520
result = transform.find_conflicts()
521
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
523
def test_resolve_case_insensitive_conflict(self):
524
tree = self.make_branch_and_tree('tree')
525
# Don't try this at home, kids!
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, 'content')
532
transform.new_file('FiLe', transform.root, 'content')
533
resolve_conflicts(transform)
535
self.assertPathExists('tree/file')
536
self.assertPathExists('tree/FiLe.moved')
538
def test_resolve_checkout_case_conflict(self):
539
tree = self.make_branch_and_tree('tree')
540
# Don't try this at home, kids!
541
# Force the tree to report that it is case insensitive, for conflict
543
tree.case_sensitive = False
544
transform = TreeTransform(tree)
545
self.addCleanup(transform.finalize)
546
transform.new_file('file', transform.root, 'content')
547
transform.new_file('FiLe', transform.root, 'content')
548
resolve_conflicts(transform,
549
pass_func=lambda t, c: resolve_checkout(t, c, []))
551
self.assertPathExists('tree/file')
552
self.assertPathExists('tree/FiLe.moved')
554
def test_apply_case_conflict(self):
555
"""Ensure that a transform with case conflicts can always be applied"""
556
tree = self.make_branch_and_tree('tree')
557
transform = TreeTransform(tree)
558
self.addCleanup(transform.finalize)
559
transform.new_file('file', transform.root, 'content')
560
transform.new_file('FiLe', transform.root, 'content')
561
dir = transform.new_directory('dir', transform.root)
562
transform.new_file('dirfile', dir, 'content')
563
transform.new_file('dirFiLe', dir, 'content')
564
resolve_conflicts(transform)
566
self.assertPathExists('tree/file')
567
if not os.path.exists('tree/FiLe.moved'):
568
self.assertPathExists('tree/FiLe')
569
self.assertPathExists('tree/dir/dirfile')
570
if not os.path.exists('tree/dir/dirFiLe.moved'):
571
self.assertPathExists('tree/dir/dirFiLe')
573
def test_case_insensitive_limbo(self):
574
tree = self.make_branch_and_tree('tree')
575
# Don't try this at home, kids!
576
# Force the tree to report that it is case insensitive
577
tree.case_sensitive = False
578
transform = TreeTransform(tree)
579
self.addCleanup(transform.finalize)
580
dir = transform.new_directory('dir', transform.root)
581
first = transform.new_file('file', dir, 'content')
582
second = transform.new_file('FiLe', dir, 'content')
583
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
584
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
586
def test_adjust_path_updates_child_limbo_names(self):
587
tree = self.make_branch_and_tree('tree')
588
transform = TreeTransform(tree)
589
self.addCleanup(transform.finalize)
590
foo_id = transform.new_directory('foo', transform.root)
591
bar_id = transform.new_directory('bar', foo_id)
592
baz_id = transform.new_directory('baz', bar_id)
593
qux_id = transform.new_directory('qux', baz_id)
594
transform.adjust_path('quxx', foo_id, bar_id)
595
self.assertStartsWith(transform._limbo_name(qux_id),
596
transform._limbo_name(bar_id))
598
def test_add_del(self):
599
start, root = self.get_transform()
600
start.new_directory('a', root, 'a')
602
transform, root = self.get_transform()
603
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
604
transform.new_directory('a', root, 'a')
607
def test_unversioning(self):
608
create_tree, root = self.get_transform()
609
parent_id = create_tree.new_directory('parent', root, 'parent-id')
610
create_tree.new_file('child', parent_id, 'child', 'child-id')
612
unversion = TreeTransform(self.wt)
613
self.addCleanup(unversion.finalize)
614
parent = unversion.trans_id_tree_path('parent')
615
unversion.unversion_file(parent)
616
self.assertEqual(unversion.find_conflicts(),
617
[('unversioned parent', parent_id)])
618
file_id = unversion.trans_id_tree_file_id('child-id')
619
unversion.unversion_file(file_id)
622
def test_name_invariants(self):
623
create_tree, root = self.get_transform()
625
root = create_tree.root
626
create_tree.new_file('name1', root, 'hello1', 'name1')
627
create_tree.new_file('name2', root, 'hello2', 'name2')
628
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
629
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
630
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
631
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
634
mangle_tree,root = self.get_transform()
635
root = mangle_tree.root
637
name1 = mangle_tree.trans_id_tree_file_id('name1')
638
name2 = mangle_tree.trans_id_tree_file_id('name2')
639
mangle_tree.adjust_path('name2', root, name1)
640
mangle_tree.adjust_path('name1', root, name2)
642
#tests for deleting parent directories
643
ddir = mangle_tree.trans_id_tree_file_id('ddir')
644
mangle_tree.delete_contents(ddir)
645
dfile = mangle_tree.trans_id_tree_file_id('dfile')
646
mangle_tree.delete_versioned(dfile)
647
mangle_tree.unversion_file(dfile)
648
mfile = mangle_tree.trans_id_tree_file_id('mfile')
649
mangle_tree.adjust_path('mfile', root, mfile)
651
#tests for adding parent directories
652
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
653
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
654
mangle_tree.adjust_path('mfile2', newdir, mfile2)
655
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
656
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
657
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
658
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
660
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
661
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
662
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
663
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
664
self.assertEqual(file(mfile2_path).read(), 'later2')
665
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
666
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
667
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
668
self.assertEqual(file(newfile_path).read(), 'hello3')
669
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
670
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
671
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
673
def test_both_rename(self):
674
create_tree,root = self.get_transform()
675
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
676
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
678
mangle_tree,root = self.get_transform()
679
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
680
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
681
mangle_tree.adjust_path('test', root, selftest)
682
mangle_tree.adjust_path('test_too_much', root, selftest)
683
mangle_tree.set_executability(True, blackbox)
686
def test_both_rename2(self):
687
create_tree,root = self.get_transform()
688
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
689
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
690
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
691
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
694
mangle_tree,root = self.get_transform()
695
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
696
tests = mangle_tree.trans_id_tree_file_id('tests-id')
697
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
698
mangle_tree.adjust_path('selftest', bzrlib, tests)
699
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
700
mangle_tree.set_executability(True, test_too_much)
703
def test_both_rename3(self):
704
create_tree,root = self.get_transform()
705
tests = create_tree.new_directory('tests', root, 'tests-id')
706
create_tree.new_file('test_too_much.py', tests, 'hello1',
709
mangle_tree,root = self.get_transform()
710
tests = mangle_tree.trans_id_tree_file_id('tests-id')
711
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
712
mangle_tree.adjust_path('selftest', root, tests)
713
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
714
mangle_tree.set_executability(True, test_too_much)
717
def test_move_dangling_ie(self):
718
create_tree, root = self.get_transform()
720
root = create_tree.root
721
create_tree.new_file('name1', root, 'hello1', 'name1')
723
delete_contents, root = self.get_transform()
724
file = delete_contents.trans_id_tree_file_id('name1')
725
delete_contents.delete_contents(file)
726
delete_contents.apply()
727
move_id, root = self.get_transform()
728
name1 = move_id.trans_id_tree_file_id('name1')
729
newdir = move_id.new_directory('dir', root, 'newdir')
730
move_id.adjust_path('name2', newdir, name1)
733
def test_replace_dangling_ie(self):
734
create_tree, root = self.get_transform()
736
root = create_tree.root
737
create_tree.new_file('name1', root, 'hello1', 'name1')
739
delete_contents = TreeTransform(self.wt)
740
self.addCleanup(delete_contents.finalize)
741
file = delete_contents.trans_id_tree_file_id('name1')
742
delete_contents.delete_contents(file)
743
delete_contents.apply()
744
delete_contents.finalize()
745
replace = TreeTransform(self.wt)
746
self.addCleanup(replace.finalize)
747
name2 = replace.new_file('name2', root, 'hello2', 'name1')
748
conflicts = replace.find_conflicts()
749
name1 = replace.trans_id_tree_file_id('name1')
750
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
751
resolve_conflicts(replace)
754
def _test_symlinks(self, link_name1,link_target1,
755
link_name2, link_target2):
757
def ozpath(p): return 'oz/' + p
759
self.requireFeature(SymlinkFeature)
760
transform, root = self.get_transform()
761
oz_id = transform.new_directory('oz', root, 'oz-id')
762
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
764
wiz_id = transform.create_path(link_name2, oz_id)
765
transform.create_symlink(link_target2, wiz_id)
766
transform.version_file('wiz-id2', wiz_id)
767
transform.set_executability(True, wiz_id)
768
self.assertEqual(transform.find_conflicts(),
769
[('non-file executability', wiz_id)])
770
transform.set_executability(None, wiz_id)
772
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
773
self.assertEqual('symlink',
774
file_kind(self.wt.abspath(ozpath(link_name1))))
775
self.assertEqual(link_target2,
776
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
777
self.assertEqual(link_target1,
778
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
780
def test_symlinks(self):
781
self._test_symlinks('wizard', 'wizard-target',
782
'wizard2', 'behind_curtain')
784
def test_symlinks_unicode(self):
785
self.requireFeature(features.UnicodeFilenameFeature)
786
self._test_symlinks(u'\N{Euro Sign}wizard',
787
u'wizard-targ\N{Euro Sign}t',
788
u'\N{Euro Sign}wizard2',
789
u'b\N{Euro Sign}hind_curtain')
791
def test_unable_create_symlink(self):
793
wt = self.make_branch_and_tree('.')
794
tt = TreeTransform(wt) # TreeTransform obtains write lock
796
tt.new_symlink('foo', tt.root, 'bar')
800
os_symlink = getattr(os, 'symlink', None)
803
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
805
"Unable to create symlink 'foo' on this platform",
809
os.symlink = os_symlink
811
def get_conflicted(self):
812
create,root = self.get_transform()
813
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
814
oz = create.new_directory('oz', root, 'oz-id')
815
create.new_directory('emeraldcity', oz, 'emerald-id')
817
conflicts,root = self.get_transform()
818
# set up duplicate entry, duplicate id
819
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
821
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
822
oz = conflicts.trans_id_tree_file_id('oz-id')
823
# set up DeletedParent parent conflict
824
conflicts.delete_versioned(oz)
825
emerald = conflicts.trans_id_tree_file_id('emerald-id')
826
# set up MissingParent conflict
827
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
828
conflicts.adjust_path('munchkincity', root, munchkincity)
829
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
831
conflicts.adjust_path('emeraldcity', emerald, emerald)
832
return conflicts, emerald, oz, old_dorothy, new_dorothy
834
def test_conflict_resolution(self):
835
conflicts, emerald, oz, old_dorothy, new_dorothy =\
836
self.get_conflicted()
837
resolve_conflicts(conflicts)
838
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
839
self.assertIs(conflicts.final_file_id(old_dorothy), None)
840
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
841
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
842
self.assertEqual(conflicts.final_parent(emerald), oz)
845
def test_cook_conflicts(self):
846
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
847
raw_conflicts = resolve_conflicts(tt)
848
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
849
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
850
'dorothy', None, 'dorothy-id')
851
self.assertEqual(cooked_conflicts[0], duplicate)
852
duplicate_id = DuplicateID('Unversioned existing file',
853
'dorothy.moved', 'dorothy', None,
855
self.assertEqual(cooked_conflicts[1], duplicate_id)
856
missing_parent = MissingParent('Created directory', 'munchkincity',
858
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
859
self.assertEqual(cooked_conflicts[2], missing_parent)
860
unversioned_parent = UnversionedParent('Versioned directory',
863
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
865
self.assertEqual(cooked_conflicts[3], unversioned_parent)
866
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
867
'oz/emeraldcity', 'emerald-id', 'emerald-id')
868
self.assertEqual(cooked_conflicts[4], deleted_parent)
869
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
870
self.assertEqual(cooked_conflicts[6], parent_loop)
871
self.assertEqual(len(cooked_conflicts), 7)
874
def test_string_conflicts(self):
875
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
876
raw_conflicts = resolve_conflicts(tt)
877
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
879
conflicts_s = [unicode(c) for c in cooked_conflicts]
880
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
881
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
882
'Moved existing file to '
884
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
885
'Unversioned existing file '
887
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
888
' munchkincity. Created directory.')
889
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
890
' versioned, but has versioned'
891
' children. Versioned directory.')
892
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
893
" is not empty. Not deleting.")
894
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
895
' versioned, but has versioned'
896
' children. Versioned directory.')
897
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
898
' oz/emeraldcity. Cancelled move.')
900
def prepare_wrong_parent_kind(self):
901
tt, root = self.get_transform()
902
tt.new_file('parent', root, 'contents', 'parent-id')
904
tt, root = self.get_transform()
905
parent_id = tt.trans_id_file_id('parent-id')
906
tt.new_file('child,', parent_id, 'contents2', 'file-id')
909
def test_find_conflicts_wrong_parent_kind(self):
910
tt = self.prepare_wrong_parent_kind()
913
def test_resolve_conflicts_wrong_existing_parent_kind(self):
914
tt = self.prepare_wrong_parent_kind()
915
raw_conflicts = resolve_conflicts(tt)
916
self.assertEqual(set([('non-directory parent', 'Created directory',
917
'new-3')]), raw_conflicts)
918
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
919
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
920
'parent-id')], cooked_conflicts)
922
self.assertEqual(None, self.wt.path2id('parent'))
923
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
925
def test_resolve_conflicts_wrong_new_parent_kind(self):
926
tt, root = self.get_transform()
927
parent_id = tt.new_directory('parent', root, 'parent-id')
928
tt.new_file('child,', parent_id, 'contents2', 'file-id')
930
tt, root = self.get_transform()
931
parent_id = tt.trans_id_file_id('parent-id')
932
tt.delete_contents(parent_id)
933
tt.create_file('contents', parent_id)
934
raw_conflicts = resolve_conflicts(tt)
935
self.assertEqual(set([('non-directory parent', 'Created directory',
936
'new-3')]), raw_conflicts)
938
self.assertEqual(None, self.wt.path2id('parent'))
939
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
941
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
942
tt, root = self.get_transform()
943
parent_id = tt.new_directory('parent', root)
944
tt.new_file('child,', parent_id, 'contents2')
946
tt, root = self.get_transform()
947
parent_id = tt.trans_id_tree_path('parent')
948
tt.delete_contents(parent_id)
949
tt.create_file('contents', parent_id)
950
resolve_conflicts(tt)
952
self.assertIs(None, self.wt.path2id('parent'))
953
self.assertIs(None, self.wt.path2id('parent.new'))
955
def test_resolve_conflicts_missing_parent(self):
956
wt = self.make_branch_and_tree('.')
957
tt = TreeTransform(wt)
958
self.addCleanup(tt.finalize)
959
parent = tt.trans_id_file_id('parent-id')
960
tt.new_file('file', parent, 'Contents')
961
raw_conflicts = resolve_conflicts(tt)
962
# Since the directory doesn't exist it's seen as 'missing'. So
963
# 'resolve_conflicts' create a conflict asking for it to be created.
964
self.assertLength(1, raw_conflicts)
965
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
967
# apply fail since the missing directory doesn't exist
968
self.assertRaises(errors.NoFinalPath, tt.apply)
970
def test_moving_versioned_directories(self):
971
create, root = self.get_transform()
972
kansas = create.new_directory('kansas', root, 'kansas-id')
973
create.new_directory('house', kansas, 'house-id')
974
create.new_directory('oz', root, 'oz-id')
976
cyclone, root = self.get_transform()
977
oz = cyclone.trans_id_tree_file_id('oz-id')
978
house = cyclone.trans_id_tree_file_id('house-id')
979
cyclone.adjust_path('house', oz, house)
982
def test_moving_root(self):
983
create, root = self.get_transform()
984
fun = create.new_directory('fun', root, 'fun-id')
985
create.new_directory('sun', root, 'sun-id')
986
create.new_directory('moon', root, 'moon')
988
transform, root = self.get_transform()
989
transform.adjust_root_path('oldroot', fun)
990
new_root = transform.trans_id_tree_path('')
991
transform.version_file('new-root', new_root)
994
def test_renames(self):
995
create, root = self.get_transform()
996
old = create.new_directory('old-parent', root, 'old-id')
997
intermediate = create.new_directory('intermediate', old, 'im-id')
998
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1001
rename, root = self.get_transform()
1002
old = rename.trans_id_file_id('old-id')
1003
rename.adjust_path('new', root, old)
1004
myfile = rename.trans_id_file_id('myfile-id')
1005
rename.set_executability(True, myfile)
1008
def test_rename_fails(self):
1009
self.requireFeature(features.not_running_as_root)
1010
# see https://bugs.launchpad.net/bzr/+bug/491763
1011
create, root_id = self.get_transform()
1012
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1013
myfile = create.new_file('myfile', root_id, 'myfile-text',
1016
if os.name == "posix" and sys.platform != "cygwin":
1017
# posix filesystems fail on renaming if the readonly bit is set
1018
osutils.make_readonly(self.wt.abspath('first-dir'))
1019
elif os.name == "nt":
1020
# windows filesystems fail on renaming open files
1021
self.addCleanup(file(self.wt.abspath('myfile')).close)
1023
self.skip("Don't know how to force a permissions error on rename")
1024
# now transform to rename
1025
rename_transform, root_id = self.get_transform()
1026
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1027
dir_id = rename_transform.trans_id_file_id('first-id')
1028
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1029
e = self.assertRaises(errors.TransformRenameFailed,
1030
rename_transform.apply)
1031
# On nix looks like:
1032
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1033
# to .../first-dir/newname: [Errno 13] Permission denied"
1034
# On windows looks like:
1035
# "Failed to rename .../work/myfile to
1036
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1037
# This test isn't concerned with exactly what the error looks like,
1038
# and the strerror will vary across OS and locales, but the assert
1039
# that the exeception attributes are what we expect
1040
self.assertEqual(e.errno, errno.EACCES)
1041
if os.name == "posix":
1042
self.assertEndsWith(e.to_path, "/first-dir/newname")
1044
self.assertEqual(os.path.basename(e.from_path), "myfile")
1046
def test_set_executability_order(self):
1047
"""Ensure that executability behaves the same, no matter what order.
1049
- create file and set executability simultaneously
1050
- create file and set executability afterward
1051
- unsetting the executability of a file whose executability has not been
1052
declared should throw an exception (this may happen when a
1053
merge attempts to create a file with a duplicate ID)
1055
transform, root = self.get_transform()
1056
wt = transform._tree
1058
self.addCleanup(wt.unlock)
1059
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1061
sac = transform.new_file('set_after_creation', root,
1062
'Set after creation', 'sac')
1063
transform.set_executability(True, sac)
1064
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1066
self.assertRaises(KeyError, transform.set_executability, None, uws)
1068
self.assertTrue(wt.is_executable('soc'))
1069
self.assertTrue(wt.is_executable('sac'))
1071
def test_preserve_mode(self):
1072
"""File mode is preserved when replacing content"""
1073
if sys.platform == 'win32':
1074
raise TestSkipped('chmod has no effect on win32')
1075
transform, root = self.get_transform()
1076
transform.new_file('file1', root, 'contents', 'file1-id', True)
1078
self.wt.lock_write()
1079
self.addCleanup(self.wt.unlock)
1080
self.assertTrue(self.wt.is_executable('file1-id'))
1081
transform, root = self.get_transform()
1082
file1_id = transform.trans_id_tree_file_id('file1-id')
1083
transform.delete_contents(file1_id)
1084
transform.create_file('contents2', file1_id)
1086
self.assertTrue(self.wt.is_executable('file1-id'))
1088
def test__set_mode_stats_correctly(self):
1089
"""_set_mode stats to determine file mode."""
1090
if sys.platform == 'win32':
1091
raise TestSkipped('chmod has no effect on win32')
1095
def instrumented_stat(path):
1096
stat_paths.append(path)
1097
return real_stat(path)
1099
transform, root = self.get_transform()
1101
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1102
file_id='bar-id-1', executable=False)
1105
transform, root = self.get_transform()
1106
bar1_id = transform.trans_id_tree_path('bar')
1107
bar2_id = transform.trans_id_tree_path('bar2')
1109
os.stat = instrumented_stat
1110
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1113
transform.finalize()
1115
bar1_abspath = self.wt.abspath('bar')
1116
self.assertEqual([bar1_abspath], stat_paths)
1118
def test_iter_changes(self):
1119
self.wt.set_root_id('eert_toor')
1120
transform, root = self.get_transform()
1121
transform.new_file('old', root, 'blah', 'id-1', True)
1123
transform, root = self.get_transform()
1125
self.assertEqual([], list(transform.iter_changes()))
1126
old = transform.trans_id_tree_file_id('id-1')
1127
transform.unversion_file(old)
1128
self.assertEqual([('id-1', ('old', None), False, (True, False),
1129
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1130
(True, True))], list(transform.iter_changes()))
1131
transform.new_directory('new', root, 'id-1')
1132
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1133
('eert_toor', 'eert_toor'), ('old', 'new'),
1134
('file', 'directory'),
1135
(True, False))], list(transform.iter_changes()))
1137
transform.finalize()
1139
def test_iter_changes_new(self):
1140
self.wt.set_root_id('eert_toor')
1141
transform, root = self.get_transform()
1142
transform.new_file('old', root, 'blah')
1144
transform, root = self.get_transform()
1146
old = transform.trans_id_tree_path('old')
1147
transform.version_file('id-1', old)
1148
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1149
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1150
(False, False))], list(transform.iter_changes()))
1152
transform.finalize()
1154
def test_iter_changes_modifications(self):
1155
self.wt.set_root_id('eert_toor')
1156
transform, root = self.get_transform()
1157
transform.new_file('old', root, 'blah', 'id-1')
1158
transform.new_file('new', root, 'blah')
1159
transform.new_directory('subdir', root, 'subdir-id')
1161
transform, root = self.get_transform()
1163
old = transform.trans_id_tree_path('old')
1164
subdir = transform.trans_id_tree_file_id('subdir-id')
1165
new = transform.trans_id_tree_path('new')
1166
self.assertEqual([], list(transform.iter_changes()))
1169
transform.delete_contents(old)
1170
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1171
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1172
(False, False))], list(transform.iter_changes()))
1175
transform.create_file('blah', old)
1176
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1177
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1178
(False, False))], list(transform.iter_changes()))
1179
transform.cancel_deletion(old)
1180
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1181
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1182
(False, False))], list(transform.iter_changes()))
1183
transform.cancel_creation(old)
1185
# move file_id to a different file
1186
self.assertEqual([], list(transform.iter_changes()))
1187
transform.unversion_file(old)
1188
transform.version_file('id-1', new)
1189
transform.adjust_path('old', root, new)
1190
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1191
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1192
(False, False))], list(transform.iter_changes()))
1193
transform.cancel_versioning(new)
1194
transform._removed_id = set()
1197
self.assertEqual([], list(transform.iter_changes()))
1198
transform.set_executability(True, old)
1199
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1200
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1201
(False, True))], list(transform.iter_changes()))
1202
transform.set_executability(None, old)
1205
self.assertEqual([], list(transform.iter_changes()))
1206
transform.adjust_path('new', root, old)
1207
transform._new_parent = {}
1208
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1209
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1210
(False, False))], list(transform.iter_changes()))
1211
transform._new_name = {}
1214
self.assertEqual([], list(transform.iter_changes()))
1215
transform.adjust_path('new', subdir, old)
1216
transform._new_name = {}
1217
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1218
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1219
('file', 'file'), (False, False))],
1220
list(transform.iter_changes()))
1221
transform._new_path = {}
1224
transform.finalize()
1226
def test_iter_changes_modified_bleed(self):
1227
self.wt.set_root_id('eert_toor')
1228
"""Modified flag should not bleed from one change to another"""
1229
# unfortunately, we have no guarantee that file1 (which is modified)
1230
# will be applied before file2. And if it's applied after file2, it
1231
# obviously can't bleed into file2's change output. But for now, it
1233
transform, root = self.get_transform()
1234
transform.new_file('file1', root, 'blah', 'id-1')
1235
transform.new_file('file2', root, 'blah', 'id-2')
1237
transform, root = self.get_transform()
1239
transform.delete_contents(transform.trans_id_file_id('id-1'))
1240
transform.set_executability(True,
1241
transform.trans_id_file_id('id-2'))
1242
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1243
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1244
('file', None), (False, False)),
1245
('id-2', (u'file2', u'file2'), False, (True, True),
1246
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1247
('file', 'file'), (False, True))],
1248
list(transform.iter_changes()))
1250
transform.finalize()
1252
def test_iter_changes_move_missing(self):
1253
"""Test moving ids with no files around"""
1254
self.wt.set_root_id('toor_eert')
1255
# Need two steps because versioning a non-existant file is a conflict.
1256
transform, root = self.get_transform()
1257
transform.new_directory('floater', root, 'floater-id')
1259
transform, root = self.get_transform()
1260
transform.delete_contents(transform.trans_id_tree_path('floater'))
1262
transform, root = self.get_transform()
1263
floater = transform.trans_id_tree_path('floater')
1265
transform.adjust_path('flitter', root, floater)
1266
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1267
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1268
(None, None), (False, False))], list(transform.iter_changes()))
1270
transform.finalize()
1272
def test_iter_changes_pointless(self):
1273
"""Ensure that no-ops are not treated as modifications"""
1274
self.wt.set_root_id('eert_toor')
1275
transform, root = self.get_transform()
1276
transform.new_file('old', root, 'blah', 'id-1')
1277
transform.new_directory('subdir', root, 'subdir-id')
1279
transform, root = self.get_transform()
1281
old = transform.trans_id_tree_path('old')
1282
subdir = transform.trans_id_tree_file_id('subdir-id')
1283
self.assertEqual([], list(transform.iter_changes()))
1284
transform.delete_contents(subdir)
1285
transform.create_directory(subdir)
1286
transform.set_executability(False, old)
1287
transform.unversion_file(old)
1288
transform.version_file('id-1', old)
1289
transform.adjust_path('old', root, old)
1290
self.assertEqual([], list(transform.iter_changes()))
1292
transform.finalize()
1294
def test_rename_count(self):
1295
transform, root = self.get_transform()
1296
transform.new_file('name1', root, 'contents')
1297
self.assertEqual(transform.rename_count, 0)
1299
self.assertEqual(transform.rename_count, 1)
1300
transform2, root = self.get_transform()
1301
transform2.adjust_path('name2', root,
1302
transform2.trans_id_tree_path('name1'))
1303
self.assertEqual(transform2.rename_count, 0)
1305
self.assertEqual(transform2.rename_count, 2)
1307
def test_change_parent(self):
1308
"""Ensure that after we change a parent, the results are still right.
1310
Renames and parent changes on pending transforms can happen as part
1311
of conflict resolution, and are explicitly permitted by the
1314
This test ensures they work correctly with the rename-avoidance
1317
transform, root = self.get_transform()
1318
parent1 = transform.new_directory('parent1', root)
1319
child1 = transform.new_file('child1', parent1, 'contents')
1320
parent2 = transform.new_directory('parent2', root)
1321
transform.adjust_path('child1', parent2, child1)
1323
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1324
self.assertPathExists(self.wt.abspath('parent2/child1'))
1325
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1326
# no rename for child1 (counting only renames during apply)
1327
self.assertEqual(2, transform.rename_count)
1329
def test_cancel_parent(self):
1330
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1332
This is like the test_change_parent, except that we cancel the parent
1333
before adjusting the path. The transform must detect that the
1334
directory is non-empty, and move children to safe locations.
1336
transform, root = self.get_transform()
1337
parent1 = transform.new_directory('parent1', root)
1338
child1 = transform.new_file('child1', parent1, 'contents')
1339
child2 = transform.new_file('child2', parent1, 'contents')
1341
transform.cancel_creation(parent1)
1343
self.fail('Failed to move child1 before deleting parent1')
1344
transform.cancel_creation(child2)
1345
transform.create_directory(parent1)
1347
transform.cancel_creation(parent1)
1348
# If the transform incorrectly believes that child2 is still in
1349
# parent1's limbo directory, it will try to rename it and fail
1350
# because was already moved by the first cancel_creation.
1352
self.fail('Transform still thinks child2 is a child of parent1')
1353
parent2 = transform.new_directory('parent2', root)
1354
transform.adjust_path('child1', parent2, child1)
1356
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1357
self.assertPathExists(self.wt.abspath('parent2/child1'))
1358
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1359
self.assertEqual(2, transform.rename_count)
1361
def test_adjust_and_cancel(self):
1362
"""Make sure adjust_path keeps track of limbo children properly"""
1363
transform, root = self.get_transform()
1364
parent1 = transform.new_directory('parent1', root)
1365
child1 = transform.new_file('child1', parent1, 'contents')
1366
parent2 = transform.new_directory('parent2', root)
1367
transform.adjust_path('child1', parent2, child1)
1368
transform.cancel_creation(child1)
1370
transform.cancel_creation(parent1)
1371
# if the transform thinks child1 is still in parent1's limbo
1372
# directory, it will attempt to move it and fail.
1374
self.fail('Transform still thinks child1 is a child of parent1')
1375
transform.finalize()
1377
def test_noname_contents(self):
1378
"""TreeTransform should permit deferring naming files."""
1379
transform, root = self.get_transform()
1380
parent = transform.trans_id_file_id('parent-id')
1382
transform.create_directory(parent)
1384
self.fail("Can't handle contents with no name")
1385
transform.finalize()
1387
def test_noname_contents_nested(self):
1388
"""TreeTransform should permit deferring naming files."""
1389
transform, root = self.get_transform()
1390
parent = transform.trans_id_file_id('parent-id')
1392
transform.create_directory(parent)
1394
self.fail("Can't handle contents with no name")
1395
child = transform.new_directory('child', parent)
1396
transform.adjust_path('parent', root, parent)
1398
self.assertPathExists(self.wt.abspath('parent/child'))
1399
self.assertEqual(1, transform.rename_count)
1401
def test_reuse_name(self):
1402
"""Avoid reusing the same limbo name for different files"""
1403
transform, root = self.get_transform()
1404
parent = transform.new_directory('parent', root)
1405
child1 = transform.new_directory('child', parent)
1407
child2 = transform.new_directory('child', parent)
1409
self.fail('Tranform tried to use the same limbo name twice')
1410
transform.adjust_path('child2', parent, child2)
1412
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1413
# child2 is put into top-level limbo because child1 has already
1414
# claimed the direct limbo path when child2 is created. There is no
1415
# advantage in renaming files once they're in top-level limbo, except
1417
self.assertEqual(2, transform.rename_count)
1419
def test_reuse_when_first_moved(self):
1420
"""Don't avoid direct paths when it is safe to use them"""
1421
transform, root = self.get_transform()
1422
parent = transform.new_directory('parent', root)
1423
child1 = transform.new_directory('child', parent)
1424
transform.adjust_path('child1', parent, child1)
1425
child2 = transform.new_directory('child', parent)
1427
# limbo/new-1 => parent
1428
self.assertEqual(1, transform.rename_count)
1430
def test_reuse_after_cancel(self):
1431
"""Don't avoid direct paths when it is safe to use them"""
1432
transform, root = self.get_transform()
1433
parent2 = transform.new_directory('parent2', root)
1434
child1 = transform.new_directory('child1', parent2)
1435
transform.cancel_creation(parent2)
1436
transform.create_directory(parent2)
1437
child2 = transform.new_directory('child1', parent2)
1438
transform.adjust_path('child2', parent2, child1)
1440
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1441
self.assertEqual(2, transform.rename_count)
1443
def test_finalize_order(self):
1444
"""Finalize must be done in child-to-parent order"""
1445
transform, root = self.get_transform()
1446
parent = transform.new_directory('parent', root)
1447
child = transform.new_directory('child', parent)
1449
transform.finalize()
1451
self.fail('Tried to remove parent before child1')
1453
def test_cancel_with_cancelled_child_should_succeed(self):
1454
transform, root = self.get_transform()
1455
parent = transform.new_directory('parent', root)
1456
child = transform.new_directory('child', parent)
1457
transform.cancel_creation(child)
1458
transform.cancel_creation(parent)
1459
transform.finalize()
1461
def test_rollback_on_directory_clash(self):
1463
wt = self.make_branch_and_tree('.')
1464
tt = TreeTransform(wt) # TreeTransform obtains write lock
1466
foo = tt.new_directory('foo', tt.root)
1467
tt.new_file('bar', foo, 'foobar')
1468
baz = tt.new_directory('baz', tt.root)
1469
tt.new_file('qux', baz, 'quux')
1470
# Ask for a rename 'foo' -> 'baz'
1471
tt.adjust_path('baz', tt.root, foo)
1472
# Lie to tt that we've already resolved all conflicts.
1473
tt.apply(no_conflicts=True)
1477
# The rename will fail because the target directory is not empty (but
1478
# raises FileExists anyway).
1479
err = self.assertRaises(errors.FileExists, tt_helper)
1480
self.assertEndsWith(err.path, "/baz")
1482
def test_two_directories_clash(self):
1484
wt = self.make_branch_and_tree('.')
1485
tt = TreeTransform(wt) # TreeTransform obtains write lock
1487
foo_1 = tt.new_directory('foo', tt.root)
1488
tt.new_directory('bar', foo_1)
1489
# Adding the same directory with a different content
1490
foo_2 = tt.new_directory('foo', tt.root)
1491
tt.new_directory('baz', foo_2)
1492
# Lie to tt that we've already resolved all conflicts.
1493
tt.apply(no_conflicts=True)
1497
err = self.assertRaises(errors.FileExists, tt_helper)
1498
self.assertEndsWith(err.path, "/foo")
1500
def test_two_directories_clash_finalize(self):
1502
wt = self.make_branch_and_tree('.')
1503
tt = TreeTransform(wt) # TreeTransform obtains write lock
1505
foo_1 = tt.new_directory('foo', tt.root)
1506
tt.new_directory('bar', foo_1)
1507
# Adding the same directory with a different content
1508
foo_2 = tt.new_directory('foo', tt.root)
1509
tt.new_directory('baz', foo_2)
1510
# Lie to tt that we've already resolved all conflicts.
1511
tt.apply(no_conflicts=True)
1515
err = self.assertRaises(errors.FileExists, tt_helper)
1516
self.assertEndsWith(err.path, "/foo")
1518
def test_file_to_directory(self):
1519
wt = self.make_branch_and_tree('.')
1520
self.build_tree(['foo'])
1523
tt = TreeTransform(wt)
1524
self.addCleanup(tt.finalize)
1525
foo_trans_id = tt.trans_id_tree_path("foo")
1526
tt.delete_contents(foo_trans_id)
1527
tt.create_directory(foo_trans_id)
1528
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1529
tt.create_file(["aa\n"], bar_trans_id)
1530
tt.version_file("bar-1", bar_trans_id)
1532
self.assertPathExists("foo/bar")
1535
self.assertEqual(wt.kind(wt.path2id("foo")), "directory")
1539
changes = wt.changes_from(wt.basis_tree())
1540
self.assertFalse(changes.has_changed(), changes)
1542
def test_file_to_symlink(self):
1543
self.requireFeature(SymlinkFeature)
1544
wt = self.make_branch_and_tree('.')
1545
self.build_tree(['foo'])
1548
tt = TreeTransform(wt)
1549
self.addCleanup(tt.finalize)
1550
foo_trans_id = tt.trans_id_tree_path("foo")
1551
tt.delete_contents(foo_trans_id)
1552
tt.create_symlink("bar", foo_trans_id)
1554
self.assertPathExists("foo")
1556
self.addCleanup(wt.unlock)
1557
self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
1559
def test_dir_to_file(self):
1560
wt = self.make_branch_and_tree('.')
1561
self.build_tree(['foo/', 'foo/bar'])
1562
wt.add(['foo', 'foo/bar'])
1564
tt = TreeTransform(wt)
1565
self.addCleanup(tt.finalize)
1566
foo_trans_id = tt.trans_id_tree_path("foo")
1567
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1568
tt.delete_contents(foo_trans_id)
1569
tt.delete_versioned(bar_trans_id)
1570
tt.create_file(["aa\n"], foo_trans_id)
1572
self.assertPathExists("foo")
1574
self.addCleanup(wt.unlock)
1575
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1577
def test_dir_to_hardlink(self):
1578
self.requireFeature(HardlinkFeature)
1579
wt = self.make_branch_and_tree('.')
1580
self.build_tree(['foo/', 'foo/bar'])
1581
wt.add(['foo', 'foo/bar'])
1583
tt = TreeTransform(wt)
1584
self.addCleanup(tt.finalize)
1585
foo_trans_id = tt.trans_id_tree_path("foo")
1586
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1587
tt.delete_contents(foo_trans_id)
1588
tt.delete_versioned(bar_trans_id)
1589
self.build_tree(['baz'])
1590
tt.create_hardlink("baz", foo_trans_id)
1592
self.assertPathExists("foo")
1593
self.assertPathExists("baz")
1595
self.addCleanup(wt.unlock)
1596
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1598
def test_no_final_path(self):
1599
transform, root = self.get_transform()
1600
trans_id = transform.trans_id_file_id('foo')
1601
transform.create_file('bar', trans_id)
1602
transform.cancel_creation(trans_id)
1605
def test_create_from_tree(self):
1606
tree1 = self.make_branch_and_tree('tree1')
1607
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1608
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1609
tree2 = self.make_branch_and_tree('tree2')
1610
tt = TreeTransform(tree2)
1611
foo_trans_id = tt.create_path('foo', tt.root)
1612
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1613
bar_trans_id = tt.create_path('bar', tt.root)
1614
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1616
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1617
self.assertFileEqual('baz', 'tree2/bar')
1619
def test_create_from_tree_bytes(self):
1620
"""Provided lines are used instead of tree content."""
1621
tree1 = self.make_branch_and_tree('tree1')
1622
self.build_tree_contents([('tree1/foo', 'bar'),])
1623
tree1.add('foo', 'foo-id')
1624
tree2 = self.make_branch_and_tree('tree2')
1625
tt = TreeTransform(tree2)
1626
foo_trans_id = tt.create_path('foo', tt.root)
1627
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1629
self.assertFileEqual('qux', 'tree2/foo')
1631
def test_create_from_tree_symlink(self):
1632
self.requireFeature(SymlinkFeature)
1633
tree1 = self.make_branch_and_tree('tree1')
1634
os.symlink('bar', 'tree1/foo')
1635
tree1.add('foo', 'foo-id')
1636
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1637
foo_trans_id = tt.create_path('foo', tt.root)
1638
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1640
self.assertEqual('bar', os.readlink('tree2/foo'))
1643
88
class TransformGroup(object):
1645
90
def __init__(self, dirname, root_id):
2689
1117
conflicts.pop())
2692
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2693
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2695
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2696
('', ''), ('directory', 'directory'), (False, False))
2699
class TestTransformPreview(tests.TestCaseWithTransport):
2701
def create_tree(self):
2702
tree = self.make_branch_and_tree('.')
2703
self.build_tree_contents([('a', 'content 1')])
2704
tree.set_root_id('TREE_ROOT')
2705
tree.add('a', 'a-id')
2706
tree.commit('rev1', rev_id='rev1')
2707
return tree.branch.repository.revision_tree('rev1')
2709
def get_empty_preview(self):
2710
repository = self.make_repository('repo')
2711
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2712
preview = TransformPreview(tree)
2713
self.addCleanup(preview.finalize)
2716
def test_transform_preview(self):
2717
revision_tree = self.create_tree()
2718
preview = TransformPreview(revision_tree)
2719
self.addCleanup(preview.finalize)
2721
def test_transform_preview_tree(self):
2722
revision_tree = self.create_tree()
2723
preview = TransformPreview(revision_tree)
2724
self.addCleanup(preview.finalize)
2725
preview.get_preview_tree()
2727
def test_transform_new_file(self):
2728
revision_tree = self.create_tree()
2729
preview = TransformPreview(revision_tree)
2730
self.addCleanup(preview.finalize)
2731
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2732
preview_tree = preview.get_preview_tree()
2733
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2735
preview_tree.get_file('file2-id').read(), 'content B\n')
2737
def test_diff_preview_tree(self):
2738
revision_tree = self.create_tree()
2739
preview = TransformPreview(revision_tree)
2740
self.addCleanup(preview.finalize)
2741
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2742
preview_tree = preview.get_preview_tree()
2744
show_diff_trees(revision_tree, preview_tree, out)
2745
lines = out.getvalue().splitlines()
2746
self.assertEqual(lines[0], "=== added file 'file2'")
2747
# 3 lines of diff administrivia
2748
self.assertEqual(lines[4], "+content B")
2750
def test_transform_conflicts(self):
2751
revision_tree = self.create_tree()
2752
preview = TransformPreview(revision_tree)
2753
self.addCleanup(preview.finalize)
2754
preview.new_file('a', preview.root, 'content 2')
2755
resolve_conflicts(preview)
2756
trans_id = preview.trans_id_file_id('a-id')
2757
self.assertEqual('a.moved', preview.final_name(trans_id))
2759
def get_tree_and_preview_tree(self):
2760
revision_tree = self.create_tree()
2761
preview = TransformPreview(revision_tree)
2762
self.addCleanup(preview.finalize)
2763
a_trans_id = preview.trans_id_file_id('a-id')
2764
preview.delete_contents(a_trans_id)
2765
preview.create_file('b content', a_trans_id)
2766
preview_tree = preview.get_preview_tree()
2767
return revision_tree, preview_tree
2769
def test_iter_changes(self):
2770
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2771
root = revision_tree.get_root_id()
2772
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2773
(root, root), ('a', 'a'), ('file', 'file'),
2775
list(preview_tree.iter_changes(revision_tree)))
2777
def test_include_unchanged_succeeds(self):
2778
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2779
changes = preview_tree.iter_changes(revision_tree,
2780
include_unchanged=True)
2781
root = revision_tree.get_root_id()
2783
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2785
def test_specific_files(self):
2786
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2787
changes = preview_tree.iter_changes(revision_tree,
2788
specific_files=[''])
2789
self.assertEqual([A_ENTRY], list(changes))
2791
def test_want_unversioned(self):
2792
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2793
changes = preview_tree.iter_changes(revision_tree,
2794
want_unversioned=True)
2795
self.assertEqual([A_ENTRY], list(changes))
2797
def test_ignore_extra_trees_no_specific_files(self):
2798
# extra_trees is harmless without specific_files, so we'll silently
2799
# accept it, even though we won't use it.
2800
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2801
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2803
def test_ignore_require_versioned_no_specific_files(self):
2804
# require_versioned is meaningless without specific_files.
2805
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2806
preview_tree.iter_changes(revision_tree, require_versioned=False)
2808
def test_ignore_pb(self):
2809
# pb could be supported, but TT.iter_changes doesn't support it.
2810
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2811
preview_tree.iter_changes(revision_tree)
2813
def test_kind(self):
2814
revision_tree = self.create_tree()
2815
preview = TransformPreview(revision_tree)
2816
self.addCleanup(preview.finalize)
2817
preview.new_file('file', preview.root, 'contents', 'file-id')
2818
preview.new_directory('directory', preview.root, 'dir-id')
2819
preview_tree = preview.get_preview_tree()
2820
self.assertEqual('file', preview_tree.kind('file-id'))
2821
self.assertEqual('directory', preview_tree.kind('dir-id'))
2823
def test_get_file_mtime(self):
2824
preview = self.get_empty_preview()
2825
file_trans_id = preview.new_file('file', preview.root, 'contents',
2827
limbo_path = preview._limbo_name(file_trans_id)
2828
preview_tree = preview.get_preview_tree()
2829
self.assertEqual(os.stat(limbo_path).st_mtime,
2830
preview_tree.get_file_mtime('file-id'))
2832
def test_get_file_mtime_renamed(self):
2833
work_tree = self.make_branch_and_tree('tree')
2834
self.build_tree(['tree/file'])
2835
work_tree.add('file', 'file-id')
2836
preview = TransformPreview(work_tree)
2837
self.addCleanup(preview.finalize)
2838
file_trans_id = preview.trans_id_tree_file_id('file-id')
2839
preview.adjust_path('renamed', preview.root, file_trans_id)
2840
preview_tree = preview.get_preview_tree()
2841
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2842
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2844
def test_get_file_size(self):
2845
work_tree = self.make_branch_and_tree('tree')
2846
self.build_tree_contents([('tree/old', 'old')])
2847
work_tree.add('old', 'old-id')
2848
preview = TransformPreview(work_tree)
2849
self.addCleanup(preview.finalize)
2850
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2852
tree = preview.get_preview_tree()
2853
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2854
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2856
def test_get_file(self):
2857
preview = self.get_empty_preview()
2858
preview.new_file('file', preview.root, 'contents', 'file-id')
2859
preview_tree = preview.get_preview_tree()
2860
tree_file = preview_tree.get_file('file-id')
2862
self.assertEqual('contents', tree_file.read())
2866
def test_get_symlink_target(self):
2867
self.requireFeature(SymlinkFeature)
2868
preview = self.get_empty_preview()
2869
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2870
preview_tree = preview.get_preview_tree()
2871
self.assertEqual('target',
2872
preview_tree.get_symlink_target('symlink-id'))
2874
def test_all_file_ids(self):
2875
tree = self.make_branch_and_tree('tree')
2876
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2877
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2878
preview = TransformPreview(tree)
2879
self.addCleanup(preview.finalize)
2880
preview.unversion_file(preview.trans_id_file_id('b-id'))
2881
c_trans_id = preview.trans_id_file_id('c-id')
2882
preview.unversion_file(c_trans_id)
2883
preview.version_file('c-id', c_trans_id)
2884
preview_tree = preview.get_preview_tree()
2885
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2886
preview_tree.all_file_ids())
2888
def test_path2id_deleted_unchanged(self):
2889
tree = self.make_branch_and_tree('tree')
2890
self.build_tree(['tree/unchanged', 'tree/deleted'])
2891
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2892
preview = TransformPreview(tree)
2893
self.addCleanup(preview.finalize)
2894
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2895
preview_tree = preview.get_preview_tree()
2896
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2897
self.assertIs(None, preview_tree.path2id('deleted'))
2899
def test_path2id_created(self):
2900
tree = self.make_branch_and_tree('tree')
2901
self.build_tree(['tree/unchanged'])
2902
tree.add(['unchanged'], ['unchanged-id'])
2903
preview = TransformPreview(tree)
2904
self.addCleanup(preview.finalize)
2905
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2906
'contents', 'new-id')
2907
preview_tree = preview.get_preview_tree()
2908
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2910
def test_path2id_moved(self):
2911
tree = self.make_branch_and_tree('tree')
2912
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2913
tree.add(['old_parent', 'old_parent/child'],
2914
['old_parent-id', 'child-id'])
2915
preview = TransformPreview(tree)
2916
self.addCleanup(preview.finalize)
2917
new_parent = preview.new_directory('new_parent', preview.root,
2919
preview.adjust_path('child', new_parent,
2920
preview.trans_id_file_id('child-id'))
2921
preview_tree = preview.get_preview_tree()
2922
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2923
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2925
def test_path2id_renamed_parent(self):
2926
tree = self.make_branch_and_tree('tree')
2927
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2928
tree.add(['old_name', 'old_name/child'],
2929
['parent-id', 'child-id'])
2930
preview = TransformPreview(tree)
2931
self.addCleanup(preview.finalize)
2932
preview.adjust_path('new_name', preview.root,
2933
preview.trans_id_file_id('parent-id'))
2934
preview_tree = preview.get_preview_tree()
2935
self.assertIs(None, preview_tree.path2id('old_name/child'))
2936
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2938
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2939
preview_tree = tt.get_preview_tree()
2940
preview_result = list(preview_tree.iter_entries_by_dir(
2944
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2945
self.assertEqual(actual_result, preview_result)
2947
def test_iter_entries_by_dir_new(self):
2948
tree = self.make_branch_and_tree('tree')
2949
tt = TreeTransform(tree)
2950
tt.new_file('new', tt.root, 'contents', 'new-id')
2951
self.assertMatchingIterEntries(tt)
2953
def test_iter_entries_by_dir_deleted(self):
2954
tree = self.make_branch_and_tree('tree')
2955
self.build_tree(['tree/deleted'])
2956
tree.add('deleted', 'deleted-id')
2957
tt = TreeTransform(tree)
2958
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2959
self.assertMatchingIterEntries(tt)
2961
def test_iter_entries_by_dir_unversioned(self):
2962
tree = self.make_branch_and_tree('tree')
2963
self.build_tree(['tree/removed'])
2964
tree.add('removed', 'removed-id')
2965
tt = TreeTransform(tree)
2966
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2967
self.assertMatchingIterEntries(tt)
2969
def test_iter_entries_by_dir_moved(self):
2970
tree = self.make_branch_and_tree('tree')
2971
self.build_tree(['tree/moved', 'tree/new_parent/'])
2972
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2973
tt = TreeTransform(tree)
2974
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2975
tt.trans_id_file_id('moved-id'))
2976
self.assertMatchingIterEntries(tt)
2978
def test_iter_entries_by_dir_specific_file_ids(self):
2979
tree = self.make_branch_and_tree('tree')
2980
tree.set_root_id('tree-root-id')
2981
self.build_tree(['tree/parent/', 'tree/parent/child'])
2982
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2983
tt = TreeTransform(tree)
2984
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2986
def test_symlink_content_summary(self):
2987
self.requireFeature(SymlinkFeature)
2988
preview = self.get_empty_preview()
2989
preview.new_symlink('path', preview.root, 'target', 'path-id')
2990
summary = preview.get_preview_tree().path_content_summary('path')
2991
self.assertEqual(('symlink', None, None, 'target'), summary)
2993
def test_missing_content_summary(self):
2994
preview = self.get_empty_preview()
2995
summary = preview.get_preview_tree().path_content_summary('path')
2996
self.assertEqual(('missing', None, None, None), summary)
2998
def test_deleted_content_summary(self):
2999
tree = self.make_branch_and_tree('tree')
3000
self.build_tree(['tree/path/'])
3002
preview = TransformPreview(tree)
3003
self.addCleanup(preview.finalize)
3004
preview.delete_contents(preview.trans_id_tree_path('path'))
3005
summary = preview.get_preview_tree().path_content_summary('path')
3006
self.assertEqual(('missing', None, None, None), summary)
3008
def test_file_content_summary_executable(self):
3009
preview = self.get_empty_preview()
3010
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3011
preview.set_executability(True, path_id)
3012
summary = preview.get_preview_tree().path_content_summary('path')
3013
self.assertEqual(4, len(summary))
3014
self.assertEqual('file', summary[0])
3015
# size must be known
3016
self.assertEqual(len('contents'), summary[1])
3018
self.assertEqual(True, summary[2])
3019
# will not have hash (not cheap to determine)
3020
self.assertIs(None, summary[3])
3022
def test_change_executability(self):
3023
tree = self.make_branch_and_tree('tree')
3024
self.build_tree(['tree/path'])
3026
preview = TransformPreview(tree)
3027
self.addCleanup(preview.finalize)
3028
path_id = preview.trans_id_tree_path('path')
3029
preview.set_executability(True, path_id)
3030
summary = preview.get_preview_tree().path_content_summary('path')
3031
self.assertEqual(True, summary[2])
3033
def test_file_content_summary_non_exec(self):
3034
preview = self.get_empty_preview()
3035
preview.new_file('path', preview.root, 'contents', 'path-id')
3036
summary = preview.get_preview_tree().path_content_summary('path')
3037
self.assertEqual(4, len(summary))
3038
self.assertEqual('file', summary[0])
3039
# size must be known
3040
self.assertEqual(len('contents'), summary[1])
3042
self.assertEqual(False, summary[2])
3043
# will not have hash (not cheap to determine)
3044
self.assertIs(None, summary[3])
3046
def test_dir_content_summary(self):
3047
preview = self.get_empty_preview()
3048
preview.new_directory('path', preview.root, 'path-id')
3049
summary = preview.get_preview_tree().path_content_summary('path')
3050
self.assertEqual(('directory', None, None, None), summary)
3052
def test_tree_content_summary(self):
3053
preview = self.get_empty_preview()
3054
path = preview.new_directory('path', preview.root, 'path-id')
3055
preview.set_tree_reference('rev-1', path)
3056
summary = preview.get_preview_tree().path_content_summary('path')
3057
self.assertEqual(4, len(summary))
3058
self.assertEqual('tree-reference', summary[0])
3060
def test_annotate(self):
3061
tree = self.make_branch_and_tree('tree')
3062
self.build_tree_contents([('tree/file', 'a\n')])
3063
tree.add('file', 'file-id')
3064
tree.commit('a', rev_id='one')
3065
self.build_tree_contents([('tree/file', 'a\nb\n')])
3066
preview = TransformPreview(tree)
3067
self.addCleanup(preview.finalize)
3068
file_trans_id = preview.trans_id_file_id('file-id')
3069
preview.delete_contents(file_trans_id)
3070
preview.create_file('a\nb\nc\n', file_trans_id)
3071
preview_tree = preview.get_preview_tree()
3077
annotation = preview_tree.annotate_iter('file-id', 'me:')
3078
self.assertEqual(expected, annotation)
3080
def test_annotate_missing(self):
3081
preview = self.get_empty_preview()
3082
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3083
preview_tree = preview.get_preview_tree()
3089
annotation = preview_tree.annotate_iter('file-id', 'me:')
3090
self.assertEqual(expected, annotation)
3092
def test_annotate_rename(self):
3093
tree = self.make_branch_and_tree('tree')
3094
self.build_tree_contents([('tree/file', 'a\n')])
3095
tree.add('file', 'file-id')
3096
tree.commit('a', rev_id='one')
3097
preview = TransformPreview(tree)
3098
self.addCleanup(preview.finalize)
3099
file_trans_id = preview.trans_id_file_id('file-id')
3100
preview.adjust_path('newname', preview.root, file_trans_id)
3101
preview_tree = preview.get_preview_tree()
3105
annotation = preview_tree.annotate_iter('file-id', 'me:')
3106
self.assertEqual(expected, annotation)
3108
def test_annotate_deleted(self):
3109
tree = self.make_branch_and_tree('tree')
3110
self.build_tree_contents([('tree/file', 'a\n')])
3111
tree.add('file', 'file-id')
3112
tree.commit('a', rev_id='one')
3113
self.build_tree_contents([('tree/file', 'a\nb\n')])
3114
preview = TransformPreview(tree)
3115
self.addCleanup(preview.finalize)
3116
file_trans_id = preview.trans_id_file_id('file-id')
3117
preview.delete_contents(file_trans_id)
3118
preview_tree = preview.get_preview_tree()
3119
annotation = preview_tree.annotate_iter('file-id', 'me:')
3120
self.assertIs(None, annotation)
3122
def test_stored_kind(self):
3123
preview = self.get_empty_preview()
3124
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3125
preview_tree = preview.get_preview_tree()
3126
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3128
def test_is_executable(self):
3129
preview = self.get_empty_preview()
3130
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3131
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3132
preview_tree = preview.get_preview_tree()
3133
self.assertEqual(True, preview_tree.is_executable('file-id'))
3135
def test_get_set_parent_ids(self):
3136
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3137
self.assertEqual([], preview_tree.get_parent_ids())
3138
preview_tree.set_parent_ids(['rev-1'])
3139
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3141
def test_plan_file_merge(self):
3142
work_a = self.make_branch_and_tree('wta')
3143
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3144
work_a.add('file', 'file-id')
3145
base_id = work_a.commit('base version')
3146
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3147
preview = TransformPreview(work_a)
3148
self.addCleanup(preview.finalize)
3149
trans_id = preview.trans_id_file_id('file-id')
3150
preview.delete_contents(trans_id)
3151
preview.create_file('b\nc\nd\ne\n', trans_id)
3152
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3153
tree_a = preview.get_preview_tree()
3154
tree_a.set_parent_ids([base_id])
3156
('killed-a', 'a\n'),
3157
('killed-b', 'b\n'),
3158
('unchanged', 'c\n'),
3159
('unchanged', 'd\n'),
3162
], list(tree_a.plan_file_merge('file-id', tree_b)))
3164
def test_plan_file_merge_revision_tree(self):
3165
work_a = self.make_branch_and_tree('wta')
3166
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3167
work_a.add('file', 'file-id')
3168
base_id = work_a.commit('base version')
3169
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3170
preview = TransformPreview(work_a.basis_tree())
3171
self.addCleanup(preview.finalize)
3172
trans_id = preview.trans_id_file_id('file-id')
3173
preview.delete_contents(trans_id)
3174
preview.create_file('b\nc\nd\ne\n', trans_id)
3175
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3176
tree_a = preview.get_preview_tree()
3177
tree_a.set_parent_ids([base_id])
3179
('killed-a', 'a\n'),
3180
('killed-b', 'b\n'),
3181
('unchanged', 'c\n'),
3182
('unchanged', 'd\n'),
3185
], list(tree_a.plan_file_merge('file-id', tree_b)))
3187
def test_walkdirs(self):
3188
preview = self.get_empty_preview()
3189
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3190
# FIXME: new_directory should mark root.
3191
preview.fixup_new_roots()
3192
preview_tree = preview.get_preview_tree()
3193
file_trans_id = preview.new_file('a', preview.root, 'contents',
3195
expected = [(('', 'tree-root'),
3196
[('a', 'a', 'file', None, 'a-id', 'file')])]
3197
self.assertEqual(expected, list(preview_tree.walkdirs()))
3199
def test_extras(self):
3200
work_tree = self.make_branch_and_tree('tree')
3201
self.build_tree(['tree/removed-file', 'tree/existing-file',
3202
'tree/not-removed-file'])
3203
work_tree.add(['removed-file', 'not-removed-file'])
3204
preview = TransformPreview(work_tree)
3205
self.addCleanup(preview.finalize)
3206
preview.new_file('new-file', preview.root, 'contents')
3207
preview.new_file('new-versioned-file', preview.root, 'contents',
3209
tree = preview.get_preview_tree()
3210
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3211
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3214
def test_merge_into_preview(self):
3215
work_tree = self.make_branch_and_tree('tree')
3216
self.build_tree_contents([('tree/file','b\n')])
3217
work_tree.add('file', 'file-id')
3218
work_tree.commit('first commit')
3219
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3220
self.build_tree_contents([('child/file','b\nc\n')])
3221
child_tree.commit('child commit')
3222
child_tree.lock_write()
3223
self.addCleanup(child_tree.unlock)
3224
work_tree.lock_write()
3225
self.addCleanup(work_tree.unlock)
3226
preview = TransformPreview(work_tree)
3227
self.addCleanup(preview.finalize)
3228
file_trans_id = preview.trans_id_file_id('file-id')
3229
preview.delete_contents(file_trans_id)
3230
preview.create_file('a\nb\n', file_trans_id)
3231
preview_tree = preview.get_preview_tree()
3232
merger = Merger.from_revision_ids(None, preview_tree,
3233
child_tree.branch.last_revision(),
3234
other_branch=child_tree.branch,
3235
tree_branch=work_tree.branch)
3236
merger.merge_type = Merge3Merger
3237
tt = merger.make_merger().make_preview_transform()
3238
self.addCleanup(tt.finalize)
3239
final_tree = tt.get_preview_tree()
3240
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3242
def test_merge_preview_into_workingtree(self):
3243
tree = self.make_branch_and_tree('tree')
3244
tree.set_root_id('TREE_ROOT')
3245
tt = TransformPreview(tree)
3246
self.addCleanup(tt.finalize)
3247
tt.new_file('name', tt.root, 'content', 'file-id')
3248
tree2 = self.make_branch_and_tree('tree2')
3249
tree2.set_root_id('TREE_ROOT')
3250
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3251
None, tree.basis_tree())
3252
merger.merge_type = Merge3Merger
3255
def test_merge_preview_into_workingtree_handles_conflicts(self):
3256
tree = self.make_branch_and_tree('tree')
3257
self.build_tree_contents([('tree/foo', 'bar')])
3258
tree.add('foo', 'foo-id')
3260
tt = TransformPreview(tree)
3261
self.addCleanup(tt.finalize)
3262
trans_id = tt.trans_id_file_id('foo-id')
3263
tt.delete_contents(trans_id)
3264
tt.create_file('baz', trans_id)
3265
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3266
self.build_tree_contents([('tree2/foo', 'qux')])
3268
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3269
pb, tree.basis_tree())
3270
merger.merge_type = Merge3Merger
3273
def test_has_filename(self):
3274
wt = self.make_branch_and_tree('tree')
3275
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3276
tt = TransformPreview(wt)
3277
removed_id = tt.trans_id_tree_path('removed')
3278
tt.delete_contents(removed_id)
3279
tt.new_file('new', tt.root, 'contents')
3280
modified_id = tt.trans_id_tree_path('modified')
3281
tt.delete_contents(modified_id)
3282
tt.create_file('modified-contents', modified_id)
3283
self.addCleanup(tt.finalize)
3284
tree = tt.get_preview_tree()
3285
self.assertTrue(tree.has_filename('unmodified'))
3286
self.assertFalse(tree.has_filename('not-present'))
3287
self.assertFalse(tree.has_filename('removed'))
3288
self.assertTrue(tree.has_filename('new'))
3289
self.assertTrue(tree.has_filename('modified'))
3291
def test_is_executable(self):
3292
tree = self.make_branch_and_tree('tree')
3293
preview = TransformPreview(tree)
3294
self.addCleanup(preview.finalize)
3295
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3296
preview_tree = preview.get_preview_tree()
3297
self.assertEqual(False, preview_tree.is_executable('baz-id',
3299
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3301
def test_commit_preview_tree(self):
3302
tree = self.make_branch_and_tree('tree')
3303
rev_id = tree.commit('rev1')
3304
tree.branch.lock_write()
3305
self.addCleanup(tree.branch.unlock)
3306
tt = TransformPreview(tree)
3307
tt.new_file('file', tt.root, 'contents', 'file_id')
3308
self.addCleanup(tt.finalize)
3309
preview = tt.get_preview_tree()
3310
preview.set_parent_ids([rev_id])
3311
builder = tree.branch.get_commit_builder([rev_id])
3312
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3313
builder.finish_inventory()
3314
rev2_id = builder.commit('rev2')
3315
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3316
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3318
def test_ascii_limbo_paths(self):
3319
self.requireFeature(features.UnicodeFilenameFeature)
3320
branch = self.make_branch('any')
3321
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3322
tt = TransformPreview(tree)
3323
self.addCleanup(tt.finalize)
3324
foo_id = tt.new_directory('', ROOT_PARENT)
3325
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3326
limbo_path = tt._limbo_name(bar_id)
3327
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3330
1120
class FakeSerializer(object):
3331
1121
"""Serializer implementation that simply returns the input.