1
# Copyright (C) 2006-2012, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
29
revision as _mod_revision,
36
from ..conflicts import (
45
from ..controldir import ControlDir
46
from ..diff import show_diff_trees
47
from ..errors import (
50
ExistingPendingDeletion,
52
ImmortalPendingDeletion,
57
from ..osutils import (
61
from ..merge import Merge3Merger, Merger
62
from ..mutabletree import MutableTree
63
from ..sixish import (
73
from .features import (
77
from ..transform import (
91
class TestTreeTransform(tests.TestCaseWithTransport):
94
super(TestTreeTransform, self).setUp()
95
self.wt = self.make_branch_and_tree('.', format='development-subtree')
98
def get_transform(self):
99
transform = TreeTransform(self.wt)
100
self.addCleanup(transform.finalize)
101
return transform, transform.root
103
def get_transform_for_sha1_test(self):
104
trans, root = self.get_transform()
105
self.wt.lock_tree_write()
106
self.addCleanup(self.wt.unlock)
107
contents = [b'just some content\n']
108
sha1 = osutils.sha_strings(contents)
109
# Roll back the clock
110
trans._creation_mtime = time.time() - 20.0
111
return trans, root, contents, sha1
113
def test_existing_limbo(self):
114
transform, root = self.get_transform()
115
limbo_name = transform._limbodir
116
deletion_path = transform._deletiondir
117
os.mkdir(pathjoin(limbo_name, 'hehe'))
118
self.assertRaises(ImmortalLimbo, transform.apply)
119
self.assertRaises(LockError, self.wt.unlock)
120
self.assertRaises(ExistingLimbo, self.get_transform)
121
self.assertRaises(LockError, self.wt.unlock)
122
os.rmdir(pathjoin(limbo_name, 'hehe'))
124
os.rmdir(deletion_path)
125
transform, root = self.get_transform()
128
def test_existing_pending_deletion(self):
129
transform, root = self.get_transform()
130
deletion_path = self._limbodir = urlutils.local_path_from_url(
131
transform._tree._transport.abspath('pending-deletion'))
132
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
133
self.assertRaises(ImmortalPendingDeletion, transform.apply)
134
self.assertRaises(LockError, self.wt.unlock)
135
self.assertRaises(ExistingPendingDeletion, self.get_transform)
137
def test_build(self):
138
transform, root = self.get_transform()
139
self.wt.lock_tree_write()
140
self.addCleanup(self.wt.unlock)
141
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
142
imaginary_id = transform.trans_id_tree_path('imaginary')
143
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
144
self.assertEqual(imaginary_id, imaginary_id2)
145
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
146
self.assertEqual('directory', transform.final_kind(root))
147
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
148
trans_id = transform.create_path('name', root)
149
self.assertIs(transform.final_file_id(trans_id), None)
150
self.assertIs(None, transform.final_kind(trans_id))
151
transform.create_file([b'contents'], trans_id)
152
transform.set_executability(True, trans_id)
153
transform.version_file(b'my_pretties', trans_id)
154
self.assertRaises(DuplicateKey, transform.version_file,
155
b'my_pretties', trans_id)
156
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
157
self.assertEqual(transform.final_parent(trans_id), root)
158
self.assertIs(transform.final_parent(root), ROOT_PARENT)
159
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
160
oz_id = transform.create_path('oz', root)
161
transform.create_directory(oz_id)
162
transform.version_file(b'ozzie', oz_id)
163
trans_id2 = transform.create_path('name2', root)
164
transform.create_file([b'contents'], trans_id2)
165
transform.set_executability(False, trans_id2)
166
transform.version_file(b'my_pretties2', trans_id2)
167
modified_paths = transform.apply().modified_paths
168
with self.wt.get_file('name') as f:
169
self.assertEqual(b'contents', f.read())
170
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
171
self.assertIs(self.wt.is_executable('name'), True)
172
self.assertIs(self.wt.is_executable('name2'), False)
173
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
174
self.assertEqual(len(modified_paths), 3)
175
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
176
(b'ozzie', b'my_pretties', b'my_pretties2')]
177
self.assertSubset(tree_mod_paths, modified_paths)
178
# is it safe to finalize repeatedly?
182
def test_apply_informs_tree_of_observed_sha1(self):
183
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
184
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
187
orig = self.wt._observed_sha1
189
def _observed_sha1(*args):
192
self.wt._observed_sha1 = _observed_sha1
194
self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
197
def test_create_file_caches_sha1(self):
198
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
199
trans_id = trans.create_path('file1', root)
200
trans.create_file(contents, trans_id, sha1=sha1)
201
st_val = osutils.lstat(trans._limbo_name(trans_id))
202
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
203
self.assertEqual(o_sha1, sha1)
204
self.assertEqualStat(o_st_val, st_val)
206
def test__apply_insertions_updates_sha1(self):
207
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
208
trans_id = trans.create_path('file1', root)
209
trans.create_file(contents, trans_id, sha1=sha1)
210
st_val = osutils.lstat(trans._limbo_name(trans_id))
211
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
212
self.assertEqual(o_sha1, sha1)
213
self.assertEqualStat(o_st_val, st_val)
214
creation_mtime = trans._creation_mtime + 10.0
215
# We fake a time difference from when the file was created until now it
216
# is being renamed by using os.utime. Note that the change we actually
217
# want to see is the real ctime change from 'os.rename()', but as long
218
# as we observe a new stat value, we should be fine.
219
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
221
new_st_val = osutils.lstat(self.wt.abspath('file1'))
222
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
223
self.assertEqual(o_sha1, sha1)
224
self.assertEqualStat(o_st_val, new_st_val)
225
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
227
def test_new_file_caches_sha1(self):
228
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
229
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
231
st_val = osutils.lstat(trans._limbo_name(trans_id))
232
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
233
self.assertEqual(o_sha1, sha1)
234
self.assertEqualStat(o_st_val, st_val)
236
def test_cancel_creation_removes_observed_sha1(self):
237
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
238
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
240
self.assertTrue(trans_id in trans._observed_sha1s)
241
trans.cancel_creation(trans_id)
242
self.assertFalse(trans_id in trans._observed_sha1s)
244
def test_create_files_same_timestamp(self):
245
transform, root = self.get_transform()
246
self.wt.lock_tree_write()
247
self.addCleanup(self.wt.unlock)
248
# Roll back the clock, so that we know everything is being set to the
250
transform._creation_mtime = creation_mtime = time.time() - 20.0
251
transform.create_file([b'content-one'],
252
transform.create_path('one', root))
253
time.sleep(1) # *ugly*
254
transform.create_file([b'content-two'],
255
transform.create_path('two', root))
257
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
259
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
261
# We only guarantee 2s resolution
263
abs(creation_mtime - st1.st_mtime) < 2.0,
264
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
265
# But if we have more than that, all files should get the same result
266
self.assertEqual(st1.st_mtime, st2.st_mtime)
268
def test_change_root_id(self):
269
transform, root = self.get_transform()
270
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
271
transform.new_directory('', ROOT_PARENT, b'new-root-id')
272
transform.delete_contents(root)
273
transform.unversion_file(root)
274
transform.fixup_new_roots()
276
self.assertEqual(b'new-root-id', self.wt.get_root_id())
278
def test_change_root_id_add_files(self):
279
transform, root = self.get_transform()
280
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
281
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
282
transform.new_file('file', new_trans_id, [b'new-contents\n'],
284
transform.delete_contents(root)
285
transform.unversion_file(root)
286
transform.fixup_new_roots()
288
self.assertEqual(b'new-root-id', self.wt.get_root_id())
289
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
290
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
292
def test_add_two_roots(self):
293
transform, root = self.get_transform()
294
transform.new_directory('', ROOT_PARENT, b'new-root-id')
295
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
296
self.assertRaises(ValueError, transform.fixup_new_roots)
298
def test_retain_existing_root(self):
299
tt, root = self.get_transform()
301
tt.new_directory('', ROOT_PARENT, b'new-root-id')
303
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
305
def test_retain_existing_root_added_file(self):
306
tt, root = self.get_transform()
307
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
308
child = tt.new_directory('child', new_trans_id, b'child-id')
310
self.assertEqual(tt.root, tt.final_parent(child))
312
def test_add_unversioned_root(self):
313
transform, root = self.get_transform()
314
transform.new_directory('', ROOT_PARENT, None)
315
transform.delete_contents(transform.root)
316
transform.fixup_new_roots()
317
self.assertNotIn(transform.root, transform._new_id)
319
def test_remove_root_fixup(self):
320
transform, root = self.get_transform()
321
old_root_id = self.wt.get_root_id()
322
self.assertNotEqual(b'new-root-id', old_root_id)
323
transform.delete_contents(root)
324
transform.unversion_file(root)
325
transform.fixup_new_roots()
327
self.assertEqual(old_root_id, self.wt.get_root_id())
329
transform, root = self.get_transform()
330
transform.new_directory('', ROOT_PARENT, b'new-root-id')
331
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
332
self.assertRaises(ValueError, transform.fixup_new_roots)
334
def test_fixup_new_roots_permits_empty_tree(self):
335
transform, root = self.get_transform()
336
transform.delete_contents(root)
337
transform.unversion_file(root)
338
transform.fixup_new_roots()
339
self.assertIs(None, transform.final_kind(root))
340
self.assertIs(None, transform.final_file_id(root))
342
def test_apply_retains_root_directory(self):
343
# Do not attempt to delete the physical root directory, because that
345
transform, root = self.get_transform()
347
transform.delete_contents(root)
348
e = self.assertRaises(AssertionError, self.assertRaises,
349
errors.TransformRenameFailed,
351
self.assertContainsRe('TransformRenameFailed not raised', str(e))
353
def test_apply_retains_file_id(self):
354
transform, root = self.get_transform()
355
old_root_id = transform.tree_file_id(root)
356
transform.unversion_file(root)
358
self.assertEqual(old_root_id, self.wt.get_root_id())
360
def test_hardlink(self):
361
self.requireFeature(HardlinkFeature)
362
transform, root = self.get_transform()
363
transform.new_file('file1', root, [b'contents'])
365
target = self.make_branch_and_tree('target')
366
target_transform = TreeTransform(target)
367
trans_id = target_transform.create_path('file1', target_transform.root)
368
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
369
target_transform.apply()
370
self.assertPathExists('target/file1')
371
source_stat = os.stat(self.wt.abspath('file1'))
372
target_stat = os.stat('target/file1')
373
self.assertEqual(source_stat, target_stat)
375
def test_convenience(self):
376
transform, root = self.get_transform()
377
self.wt.lock_tree_write()
378
self.addCleanup(self.wt.unlock)
379
transform.new_file('name', root, [b'contents'], b'my_pretties', True)
380
oz = transform.new_directory('oz', root, b'oz-id')
381
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
382
transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
385
self.assertEqual(len(transform.find_conflicts()), 0)
387
self.assertRaises(ReusingTransform, transform.find_conflicts)
388
with open(self.wt.abspath('name'), 'r') as f:
389
self.assertEqual('contents', f.read())
390
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
391
self.assertIs(self.wt.is_executable('name'), True)
392
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
393
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
394
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
396
self.assertEqual(b'toto-contents',
397
self.wt.get_file('oz/dorothy/toto').read())
398
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
400
def test_tree_reference(self):
401
transform, root = self.get_transform()
402
tree = transform._tree
403
trans_id = transform.new_directory('reference', root, b'subtree-id')
404
transform.set_tree_reference(b'subtree-revision', trans_id)
407
self.addCleanup(tree.unlock)
410
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
412
def test_conflicts(self):
413
transform, root = self.get_transform()
414
trans_id = transform.new_file('name', root, [b'contents'],
416
self.assertEqual(len(transform.find_conflicts()), 0)
417
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
418
self.assertEqual(transform.find_conflicts(),
419
[('duplicate', trans_id, trans_id2, 'name')])
420
self.assertRaises(MalformedTransform, transform.apply)
421
transform.adjust_path('name', trans_id, trans_id2)
422
self.assertEqual(transform.find_conflicts(),
423
[('non-directory parent', trans_id)])
424
tinman_id = transform.trans_id_tree_path('tinman')
425
transform.adjust_path('name', tinman_id, trans_id2)
426
self.assertEqual(transform.find_conflicts(),
427
[('unversioned parent', tinman_id),
428
('missing parent', tinman_id)])
429
lion_id = transform.create_path('lion', root)
430
self.assertEqual(transform.find_conflicts(),
431
[('unversioned parent', tinman_id),
432
('missing parent', tinman_id)])
433
transform.adjust_path('name', lion_id, trans_id2)
434
self.assertEqual(transform.find_conflicts(),
435
[('unversioned parent', lion_id),
436
('missing parent', lion_id)])
437
transform.version_file(b"Courage", lion_id)
438
self.assertEqual(transform.find_conflicts(),
439
[('missing parent', lion_id),
440
('versioning no contents', lion_id)])
441
transform.adjust_path('name2', root, trans_id2)
442
self.assertEqual(transform.find_conflicts(),
443
[('versioning no contents', lion_id)])
444
transform.create_file([b'Contents, okay?'], lion_id)
445
transform.adjust_path('name2', trans_id2, trans_id2)
446
self.assertEqual(transform.find_conflicts(),
447
[('parent loop', trans_id2),
448
('non-directory parent', trans_id2)])
449
transform.adjust_path('name2', root, trans_id2)
450
oz_id = transform.new_directory('oz', root)
451
transform.set_executability(True, oz_id)
452
self.assertEqual(transform.find_conflicts(),
453
[('unversioned executability', oz_id)])
454
transform.version_file(b'oz-id', oz_id)
455
self.assertEqual(transform.find_conflicts(),
456
[('non-file executability', oz_id)])
457
transform.set_executability(None, oz_id)
458
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
460
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
461
with open(self.wt.abspath('name'), 'rb') as f:
462
self.assertEqual(b'contents', f.read())
463
transform2, root = self.get_transform()
464
oz_id = transform2.trans_id_tree_path('oz')
465
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
466
result = transform2.find_conflicts()
467
fp = FinalPaths(transform2)
468
self.assertTrue('oz/tip' in transform2._tree_path_ids)
469
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
470
self.assertEqual(len(result), 2)
471
self.assertEqual((result[0][0], result[0][1]),
472
('duplicate', newtip))
473
self.assertEqual((result[1][0], result[1][2]),
474
('duplicate id', newtip))
475
transform2.finalize()
476
transform3 = TreeTransform(self.wt)
477
self.addCleanup(transform3.finalize)
478
oz_id = transform3.trans_id_tree_path('oz')
479
transform3.delete_contents(oz_id)
480
self.assertEqual(transform3.find_conflicts(),
481
[('missing parent', oz_id)])
482
root_id = transform3.root
483
tip_id = transform3.trans_id_tree_path('oz/tip')
484
transform3.adjust_path('tip', root_id, tip_id)
487
def test_conflict_on_case_insensitive(self):
488
tree = self.make_branch_and_tree('tree')
489
# Don't try this at home, kids!
490
# Force the tree to report that it is case sensitive, for conflict
492
tree.case_sensitive = True
493
transform = TreeTransform(tree)
494
self.addCleanup(transform.finalize)
495
transform.new_file('file', transform.root, [b'content'])
496
transform.new_file('FiLe', transform.root, [b'content'])
497
result = transform.find_conflicts()
498
self.assertEqual([], result)
500
# Force the tree to report that it is case insensitive, for conflict
502
tree.case_sensitive = False
503
transform = TreeTransform(tree)
504
self.addCleanup(transform.finalize)
505
transform.new_file('file', transform.root, [b'content'])
506
transform.new_file('FiLe', transform.root, [b'content'])
507
result = transform.find_conflicts()
508
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
510
def test_conflict_on_case_insensitive_existing(self):
511
tree = self.make_branch_and_tree('tree')
512
self.build_tree(['tree/FiLe'])
513
# Don't try this at home, kids!
514
# Force the tree to report that it is case sensitive, for conflict
516
tree.case_sensitive = True
517
transform = TreeTransform(tree)
518
self.addCleanup(transform.finalize)
519
transform.new_file('file', transform.root, [b'content'])
520
result = transform.find_conflicts()
521
self.assertEqual([], result)
523
# Force the tree to report that it is case insensitive, for conflict
525
tree.case_sensitive = False
526
transform = TreeTransform(tree)
527
self.addCleanup(transform.finalize)
528
transform.new_file('file', transform.root, [b'content'])
529
result = transform.find_conflicts()
530
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
532
def test_resolve_case_insensitive_conflict(self):
533
tree = self.make_branch_and_tree('tree')
534
# Don't try this at home, kids!
535
# Force the tree to report that it is case insensitive, for conflict
537
tree.case_sensitive = False
538
transform = TreeTransform(tree)
539
self.addCleanup(transform.finalize)
540
transform.new_file('file', transform.root, [b'content'])
541
transform.new_file('FiLe', transform.root, [b'content'])
542
resolve_conflicts(transform)
544
self.assertPathExists('tree/file')
545
self.assertPathExists('tree/FiLe.moved')
547
def test_resolve_checkout_case_conflict(self):
548
tree = self.make_branch_and_tree('tree')
549
# Don't try this at home, kids!
550
# Force the tree to report that it is case insensitive, for conflict
552
tree.case_sensitive = False
553
transform = TreeTransform(tree)
554
self.addCleanup(transform.finalize)
555
transform.new_file('file', transform.root, [b'content'])
556
transform.new_file('FiLe', transform.root, [b'content'])
557
resolve_conflicts(transform,
558
pass_func=lambda t, c: resolve_checkout(t, c, []))
560
self.assertPathExists('tree/file')
561
self.assertPathExists('tree/FiLe.moved')
563
def test_apply_case_conflict(self):
564
"""Ensure that a transform with case conflicts can always be applied"""
565
tree = self.make_branch_and_tree('tree')
566
transform = TreeTransform(tree)
567
self.addCleanup(transform.finalize)
568
transform.new_file('file', transform.root, [b'content'])
569
transform.new_file('FiLe', transform.root, [b'content'])
570
dir = transform.new_directory('dir', transform.root)
571
transform.new_file('dirfile', dir, [b'content'])
572
transform.new_file('dirFiLe', dir, [b'content'])
573
resolve_conflicts(transform)
575
self.assertPathExists('tree/file')
576
if not os.path.exists('tree/FiLe.moved'):
577
self.assertPathExists('tree/FiLe')
578
self.assertPathExists('tree/dir/dirfile')
579
if not os.path.exists('tree/dir/dirFiLe.moved'):
580
self.assertPathExists('tree/dir/dirFiLe')
582
def test_case_insensitive_limbo(self):
583
tree = self.make_branch_and_tree('tree')
584
# Don't try this at home, kids!
585
# Force the tree to report that it is case insensitive
586
tree.case_sensitive = False
587
transform = TreeTransform(tree)
588
self.addCleanup(transform.finalize)
589
dir = transform.new_directory('dir', transform.root)
590
first = transform.new_file('file', dir, [b'content'])
591
second = transform.new_file('FiLe', dir, [b'content'])
592
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
593
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
595
def test_adjust_path_updates_child_limbo_names(self):
596
tree = self.make_branch_and_tree('tree')
597
transform = TreeTransform(tree)
598
self.addCleanup(transform.finalize)
599
foo_id = transform.new_directory('foo', transform.root)
600
bar_id = transform.new_directory('bar', foo_id)
601
baz_id = transform.new_directory('baz', bar_id)
602
qux_id = transform.new_directory('qux', baz_id)
603
transform.adjust_path('quxx', foo_id, bar_id)
604
self.assertStartsWith(transform._limbo_name(qux_id),
605
transform._limbo_name(bar_id))
607
def test_add_del(self):
608
start, root = self.get_transform()
609
start.new_directory('a', root, b'a')
611
transform, root = self.get_transform()
612
transform.delete_versioned(transform.trans_id_tree_path('a'))
613
transform.new_directory('a', root, b'a')
616
def test_unversioning(self):
617
create_tree, root = self.get_transform()
618
parent_id = create_tree.new_directory('parent', root, b'parent-id')
619
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
621
unversion = TreeTransform(self.wt)
622
self.addCleanup(unversion.finalize)
623
parent = unversion.trans_id_tree_path('parent')
624
unversion.unversion_file(parent)
625
self.assertEqual(unversion.find_conflicts(),
626
[('unversioned parent', parent_id)])
627
file_id = unversion.trans_id_tree_path('parent/child')
628
unversion.unversion_file(file_id)
631
def test_name_invariants(self):
632
create_tree, root = self.get_transform()
634
root = create_tree.root
635
create_tree.new_file('name1', root, [b'hello1'], b'name1')
636
create_tree.new_file('name2', root, [b'hello2'], b'name2')
637
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
638
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
639
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
640
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
643
mangle_tree, root = self.get_transform()
644
root = mangle_tree.root
646
name1 = mangle_tree.trans_id_tree_path('name1')
647
name2 = mangle_tree.trans_id_tree_path('name2')
648
mangle_tree.adjust_path('name2', root, name1)
649
mangle_tree.adjust_path('name1', root, name2)
651
# tests for deleting parent directories
652
ddir = mangle_tree.trans_id_tree_path('dying_directory')
653
mangle_tree.delete_contents(ddir)
654
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
655
mangle_tree.delete_versioned(dfile)
656
mangle_tree.unversion_file(dfile)
657
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
658
mangle_tree.adjust_path('mfile', root, mfile)
660
# tests for adding parent directories
661
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
662
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
663
mangle_tree.adjust_path('mfile2', newdir, mfile2)
664
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
665
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
666
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
667
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
669
with open(self.wt.abspath('name1'), 'r') as f:
670
self.assertEqual(f.read(), 'hello2')
671
with open(self.wt.abspath('name2'), 'r') as f:
672
self.assertEqual(f.read(), 'hello1')
673
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
674
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
675
with open(mfile2_path, 'r') as f:
676
self.assertEqual(f.read(), 'later2')
677
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
678
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
679
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
680
with open(newfile_path, 'r') as f:
681
self.assertEqual(f.read(), 'hello3')
682
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
683
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
684
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
686
def test_both_rename(self):
687
create_tree, root = self.get_transform()
688
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
689
create_tree.new_file('blackbox.py', newdir, [
690
b'hello1'], b'blackbox-id')
692
mangle_tree, root = self.get_transform()
693
selftest = mangle_tree.trans_id_tree_path('selftest')
694
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
695
mangle_tree.adjust_path('test', root, selftest)
696
mangle_tree.adjust_path('test_too_much', root, selftest)
697
mangle_tree.set_executability(True, blackbox)
700
def test_both_rename2(self):
701
create_tree, root = self.get_transform()
702
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
703
tests = create_tree.new_directory('tests', breezy, b'tests-id')
704
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
705
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
708
mangle_tree, root = self.get_transform()
709
breezy = mangle_tree.trans_id_tree_path('breezy')
710
tests = mangle_tree.trans_id_tree_path('breezy/tests')
711
test_too_much = mangle_tree.trans_id_tree_path(
712
'breezy/tests/blackbox/test_too_much.py')
713
mangle_tree.adjust_path('selftest', breezy, tests)
714
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
715
mangle_tree.set_executability(True, test_too_much)
718
def test_both_rename3(self):
719
create_tree, root = self.get_transform()
720
tests = create_tree.new_directory('tests', root, b'tests-id')
721
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
724
mangle_tree, root = self.get_transform()
725
tests = mangle_tree.trans_id_tree_path('tests')
726
test_too_much = mangle_tree.trans_id_tree_path(
727
'tests/test_too_much.py')
728
mangle_tree.adjust_path('selftest', root, tests)
729
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
730
mangle_tree.set_executability(True, test_too_much)
733
def test_move_dangling_ie(self):
734
create_tree, root = self.get_transform()
736
root = create_tree.root
737
create_tree.new_file('name1', root, [b'hello1'], b'name1')
739
delete_contents, root = self.get_transform()
740
file = delete_contents.trans_id_tree_path('name1')
741
delete_contents.delete_contents(file)
742
delete_contents.apply()
743
move_id, root = self.get_transform()
744
name1 = move_id.trans_id_tree_path('name1')
745
newdir = move_id.new_directory('dir', root, b'newdir')
746
move_id.adjust_path('name2', newdir, name1)
749
def test_replace_dangling_ie(self):
750
create_tree, root = self.get_transform()
752
root = create_tree.root
753
create_tree.new_file('name1', root, [b'hello1'], b'name1')
755
delete_contents = TreeTransform(self.wt)
756
self.addCleanup(delete_contents.finalize)
757
file = delete_contents.trans_id_tree_path('name1')
758
delete_contents.delete_contents(file)
759
delete_contents.apply()
760
delete_contents.finalize()
761
replace = TreeTransform(self.wt)
762
self.addCleanup(replace.finalize)
763
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
764
conflicts = replace.find_conflicts()
765
name1 = replace.trans_id_tree_path('name1')
766
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
767
resolve_conflicts(replace)
770
def _test_symlinks(self, link_name1, link_target1,
771
link_name2, link_target2):
776
self.requireFeature(SymlinkFeature)
777
transform, root = self.get_transform()
778
oz_id = transform.new_directory('oz', root, b'oz-id')
779
transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
780
wiz_id = transform.create_path(link_name2, oz_id)
781
transform.create_symlink(link_target2, wiz_id)
782
transform.version_file(b'wiz-id2', wiz_id)
783
transform.set_executability(True, wiz_id)
784
self.assertEqual(transform.find_conflicts(),
785
[('non-file executability', wiz_id)])
786
transform.set_executability(None, wiz_id)
788
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
789
self.assertEqual('symlink',
790
file_kind(self.wt.abspath(ozpath(link_name1))))
791
self.assertEqual(link_target2,
792
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
793
self.assertEqual(link_target1,
794
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
796
def test_symlinks(self):
797
self._test_symlinks('wizard', 'wizard-target',
798
'wizard2', 'behind_curtain')
800
def test_symlinks_unicode(self):
801
self.requireFeature(features.UnicodeFilenameFeature)
802
self._test_symlinks(u'\N{Euro Sign}wizard',
803
u'wizard-targ\N{Euro Sign}t',
804
u'\N{Euro Sign}wizard2',
805
u'b\N{Euro Sign}hind_curtain')
807
def test_unable_create_symlink(self):
809
wt = self.make_branch_and_tree('.')
810
tt = TreeTransform(wt) # TreeTransform obtains write lock
812
tt.new_symlink('foo', tt.root, 'bar')
816
os_symlink = getattr(os, 'symlink', None)
819
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
821
"Unable to create symlink 'foo' on this platform",
825
os.symlink = os_symlink
827
def get_conflicted(self):
828
create, root = self.get_transform()
829
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
830
oz = create.new_directory('oz', root, b'oz-id')
831
create.new_directory('emeraldcity', oz, b'emerald-id')
833
conflicts, root = self.get_transform()
834
# set up duplicate entry, duplicate id
835
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
837
old_dorothy = conflicts.trans_id_tree_path('dorothy')
838
oz = conflicts.trans_id_tree_path('oz')
839
# set up DeletedParent parent conflict
840
conflicts.delete_versioned(oz)
841
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
842
# set up MissingParent conflict
843
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
844
conflicts.adjust_path('munchkincity', root, munchkincity)
845
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
847
conflicts.adjust_path('emeraldcity', emerald, emerald)
848
return conflicts, emerald, oz, old_dorothy, new_dorothy
850
def test_conflict_resolution(self):
851
conflicts, emerald, oz, old_dorothy, new_dorothy =\
852
self.get_conflicted()
853
resolve_conflicts(conflicts)
854
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
855
self.assertIs(conflicts.final_file_id(old_dorothy), None)
856
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
857
self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
858
self.assertEqual(conflicts.final_parent(emerald), oz)
861
def test_cook_conflicts(self):
862
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
863
raw_conflicts = resolve_conflicts(tt)
864
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
865
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
866
'dorothy', None, b'dorothy-id')
867
self.assertEqual(cooked_conflicts[0], duplicate)
868
duplicate_id = DuplicateID('Unversioned existing file',
869
'dorothy.moved', 'dorothy', None,
871
self.assertEqual(cooked_conflicts[1], duplicate_id)
872
missing_parent = MissingParent('Created directory', 'munchkincity',
874
deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
875
self.assertEqual(cooked_conflicts[2], missing_parent)
876
unversioned_parent = UnversionedParent('Versioned directory',
879
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
881
self.assertEqual(cooked_conflicts[3], unversioned_parent)
882
parent_loop = ParentLoop(
883
'Cancelled move', 'oz/emeraldcity',
884
'oz/emeraldcity', b'emerald-id', b'emerald-id')
885
self.assertEqual(cooked_conflicts[4], deleted_parent)
886
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
887
self.assertEqual(cooked_conflicts[6], parent_loop)
888
self.assertEqual(len(cooked_conflicts), 7)
891
def test_string_conflicts(self):
892
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
893
raw_conflicts = resolve_conflicts(tt)
894
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
896
conflicts_s = [text_type(c) for c in cooked_conflicts]
897
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
898
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
899
'Moved existing file to '
901
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
902
'Unversioned existing file '
904
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
905
' munchkincity. Created directory.')
906
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
907
' versioned, but has versioned'
908
' children. Versioned directory.')
909
self.assertEqualDiff(
910
conflicts_s[4], "Conflict: can't delete oz because it"
911
" is not empty. Not deleting.")
912
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
913
' versioned, but has versioned'
914
' children. Versioned directory.')
915
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
916
' oz/emeraldcity. Cancelled move.')
918
def prepare_wrong_parent_kind(self):
919
tt, root = self.get_transform()
920
tt.new_file('parent', root, [b'contents'], b'parent-id')
922
tt, root = self.get_transform()
923
parent_id = tt.trans_id_file_id(b'parent-id')
924
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
927
def test_find_conflicts_wrong_parent_kind(self):
928
tt = self.prepare_wrong_parent_kind()
931
def test_resolve_conflicts_wrong_existing_parent_kind(self):
932
tt = self.prepare_wrong_parent_kind()
933
raw_conflicts = resolve_conflicts(tt)
934
self.assertEqual({('non-directory parent', 'Created directory',
935
'new-3')}, raw_conflicts)
936
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
937
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
938
b'parent-id')], cooked_conflicts)
940
self.assertFalse(self.wt.is_versioned('parent'))
941
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
943
def test_resolve_conflicts_wrong_new_parent_kind(self):
944
tt, root = self.get_transform()
945
parent_id = tt.new_directory('parent', root, b'parent-id')
946
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
948
tt, root = self.get_transform()
949
parent_id = tt.trans_id_file_id(b'parent-id')
950
tt.delete_contents(parent_id)
951
tt.create_file([b'contents'], parent_id)
952
raw_conflicts = resolve_conflicts(tt)
953
self.assertEqual({('non-directory parent', 'Created directory',
954
'new-3')}, raw_conflicts)
956
self.assertFalse(self.wt.is_versioned('parent'))
957
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
959
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
960
tt, root = self.get_transform()
961
parent_id = tt.new_directory('parent', root)
962
tt.new_file('child,', parent_id, [b'contents2'])
964
tt, root = self.get_transform()
965
parent_id = tt.trans_id_tree_path('parent')
966
tt.delete_contents(parent_id)
967
tt.create_file([b'contents'], parent_id)
968
resolve_conflicts(tt)
970
self.assertFalse(self.wt.is_versioned('parent'))
971
self.assertFalse(self.wt.is_versioned('parent.new'))
973
def test_resolve_conflicts_missing_parent(self):
974
wt = self.make_branch_and_tree('.')
975
tt = TreeTransform(wt)
976
self.addCleanup(tt.finalize)
977
parent = tt.trans_id_file_id(b'parent-id')
978
tt.new_file('file', parent, [b'Contents'])
979
raw_conflicts = resolve_conflicts(tt)
980
# Since the directory doesn't exist it's seen as 'missing'. So
981
# 'resolve_conflicts' create a conflict asking for it to be created.
982
self.assertLength(1, raw_conflicts)
983
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
985
# apply fail since the missing directory doesn't exist
986
self.assertRaises(errors.NoFinalPath, tt.apply)
988
def test_moving_versioned_directories(self):
989
create, root = self.get_transform()
990
kansas = create.new_directory('kansas', root, b'kansas-id')
991
create.new_directory('house', kansas, b'house-id')
992
create.new_directory('oz', root, b'oz-id')
994
cyclone, root = self.get_transform()
995
oz = cyclone.trans_id_tree_path('oz')
996
house = cyclone.trans_id_tree_path('house')
997
cyclone.adjust_path('house', oz, house)
1000
def test_moving_root(self):
1001
create, root = self.get_transform()
1002
fun = create.new_directory('fun', root, b'fun-id')
1003
create.new_directory('sun', root, b'sun-id')
1004
create.new_directory('moon', root, b'moon')
1006
transform, root = self.get_transform()
1007
transform.adjust_root_path('oldroot', fun)
1008
new_root = transform.trans_id_tree_path('')
1009
transform.version_file(b'new-root', new_root)
1012
def test_renames(self):
1013
create, root = self.get_transform()
1014
old = create.new_directory('old-parent', root, b'old-id')
1015
intermediate = create.new_directory('intermediate', old, b'im-id')
1016
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1019
rename, root = self.get_transform()
1020
old = rename.trans_id_file_id(b'old-id')
1021
rename.adjust_path('new', root, old)
1022
myfile = rename.trans_id_file_id(b'myfile-id')
1023
rename.set_executability(True, myfile)
1026
def test_rename_fails(self):
1027
self.requireFeature(features.not_running_as_root)
1028
# see https://bugs.launchpad.net/bzr/+bug/491763
1029
create, root_id = self.get_transform()
1030
create.new_directory('first-dir', root_id, b'first-id')
1031
create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1033
if os.name == "posix" and sys.platform != "cygwin":
1034
# posix filesystems fail on renaming if the readonly bit is set
1035
osutils.make_readonly(self.wt.abspath('first-dir'))
1036
elif os.name == "nt":
1037
# windows filesystems fail on renaming open files
1038
self.addCleanup(open(self.wt.abspath('myfile')).close)
1040
self.skipTest("Can't force a permissions error on rename")
1041
# now transform to rename
1042
rename_transform, root_id = self.get_transform()
1043
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1044
dir_id = rename_transform.trans_id_file_id(b'first-id')
1045
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1046
e = self.assertRaises(errors.TransformRenameFailed,
1047
rename_transform.apply)
1048
# On nix looks like:
1049
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1050
# to .../first-dir/newname: [Errno 13] Permission denied"
1051
# On windows looks like:
1052
# "Failed to rename .../work/myfile to
1053
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1054
# This test isn't concerned with exactly what the error looks like,
1055
# and the strerror will vary across OS and locales, but the assert
1056
# that the exeception attributes are what we expect
1057
self.assertEqual(e.errno, errno.EACCES)
1058
if os.name == "posix":
1059
self.assertEndsWith(e.to_path, "/first-dir/newname")
1061
self.assertEqual(os.path.basename(e.from_path), "myfile")
1063
def test_set_executability_order(self):
1064
"""Ensure that executability behaves the same, no matter what order.
1066
- create file and set executability simultaneously
1067
- create file and set executability afterward
1068
- unsetting the executability of a file whose executability has not
1070
declared should throw an exception (this may happen when a
1071
merge attempts to create a file with a duplicate ID)
1073
transform, root = self.get_transform()
1074
wt = transform._tree
1076
self.addCleanup(wt.unlock)
1077
transform.new_file('set_on_creation', root, [b'Set on creation'],
1079
sac = transform.new_file('set_after_creation', root,
1080
[b'Set after creation'], b'sac')
1081
transform.set_executability(True, sac)
1082
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1084
self.assertRaises(KeyError, transform.set_executability, None, uws)
1086
self.assertTrue(wt.is_executable('set_on_creation'))
1087
self.assertTrue(wt.is_executable('set_after_creation'))
1089
def test_preserve_mode(self):
1090
"""File mode is preserved when replacing content"""
1091
if sys.platform == 'win32':
1092
raise TestSkipped('chmod has no effect on win32')
1093
transform, root = self.get_transform()
1094
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1096
self.wt.lock_write()
1097
self.addCleanup(self.wt.unlock)
1098
self.assertTrue(self.wt.is_executable('file1'))
1099
transform, root = self.get_transform()
1100
file1_id = transform.trans_id_tree_path('file1')
1101
transform.delete_contents(file1_id)
1102
transform.create_file([b'contents2'], file1_id)
1104
self.assertTrue(self.wt.is_executable('file1'))
1106
def test__set_mode_stats_correctly(self):
1107
"""_set_mode stats to determine file mode."""
1108
if sys.platform == 'win32':
1109
raise TestSkipped('chmod has no effect on win32')
1114
def instrumented_stat(path):
1115
stat_paths.append(path)
1116
return real_stat(path)
1118
transform, root = self.get_transform()
1120
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1121
file_id=b'bar-id-1', executable=False)
1124
transform, root = self.get_transform()
1125
bar1_id = transform.trans_id_tree_path('bar')
1126
bar2_id = transform.trans_id_tree_path('bar2')
1128
os.stat = instrumented_stat
1129
transform.create_file([b'bar2 contents\n'],
1130
bar2_id, mode_id=bar1_id)
1133
transform.finalize()
1135
bar1_abspath = self.wt.abspath('bar')
1136
self.assertEqual([bar1_abspath], stat_paths)
1138
def test_iter_changes(self):
1139
self.wt.set_root_id(b'eert_toor')
1140
transform, root = self.get_transform()
1141
transform.new_file('old', root, [b'blah'], b'id-1', True)
1143
transform, root = self.get_transform()
1145
self.assertEqual([], list(transform.iter_changes()))
1146
old = transform.trans_id_tree_path('old')
1147
transform.unversion_file(old)
1148
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1149
(b'eert_toor', b'eert_toor'),
1150
('old', 'old'), ('file', 'file'),
1151
(True, True))], 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))], list(transform.iter_changes()))
1158
transform.finalize()
1160
def test_iter_changes_new(self):
1161
self.wt.set_root_id(b'eert_toor')
1162
transform, root = self.get_transform()
1163
transform.new_file('old', root, [b'blah'])
1165
transform, root = self.get_transform()
1167
old = transform.trans_id_tree_path('old')
1168
transform.version_file(b'id-1', old)
1169
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1170
(b'eert_toor', b'eert_toor'),
1171
('old', 'old'), ('file', 'file'),
1173
list(transform.iter_changes()))
1175
transform.finalize()
1177
def test_iter_changes_modifications(self):
1178
self.wt.set_root_id(b'eert_toor')
1179
transform, root = self.get_transform()
1180
transform.new_file('old', root, [b'blah'], b'id-1')
1181
transform.new_file('new', root, [b'blah'])
1182
transform.new_directory('subdir', root, b'subdir-id')
1184
transform, root = self.get_transform()
1186
old = transform.trans_id_tree_path('old')
1187
subdir = transform.trans_id_tree_path('subdir')
1188
new = transform.trans_id_tree_path('new')
1189
self.assertEqual([], list(transform.iter_changes()))
1192
transform.delete_contents(old)
1193
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1194
(b'eert_toor', b'eert_toor'),
1195
('old', 'old'), ('file', None),
1197
list(transform.iter_changes()))
1200
transform.create_file([b'blah'], old)
1201
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1202
(b'eert_toor', b'eert_toor'),
1203
('old', 'old'), ('file', 'file'),
1205
list(transform.iter_changes()))
1206
transform.cancel_deletion(old)
1207
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1208
(b'eert_toor', b'eert_toor'),
1209
('old', 'old'), ('file', 'file'),
1211
list(transform.iter_changes()))
1212
transform.cancel_creation(old)
1214
# move file_id to a different file
1215
self.assertEqual([], list(transform.iter_changes()))
1216
transform.unversion_file(old)
1217
transform.version_file(b'id-1', new)
1218
transform.adjust_path('old', root, new)
1219
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1220
(b'eert_toor', b'eert_toor'),
1221
('old', 'old'), ('file', 'file'),
1223
list(transform.iter_changes()))
1224
transform.cancel_versioning(new)
1225
transform._removed_id = set()
1228
self.assertEqual([], list(transform.iter_changes()))
1229
transform.set_executability(True, old)
1230
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1231
(b'eert_toor', b'eert_toor'),
1232
('old', 'old'), ('file', 'file'),
1234
list(transform.iter_changes()))
1235
transform.set_executability(None, old)
1238
self.assertEqual([], list(transform.iter_changes()))
1239
transform.adjust_path('new', root, old)
1240
transform._new_parent = {}
1241
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1242
(b'eert_toor', b'eert_toor'),
1243
('old', 'new'), ('file', 'file'),
1245
list(transform.iter_changes()))
1246
transform._new_name = {}
1249
self.assertEqual([], list(transform.iter_changes()))
1250
transform.adjust_path('new', subdir, old)
1251
transform._new_name = {}
1252
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1253
(True, True), (b'eert_toor',
1254
b'subdir-id'), ('old', 'old'),
1255
('file', 'file'), (False, False))],
1256
list(transform.iter_changes()))
1257
transform._new_path = {}
1260
transform.finalize()
1262
def test_iter_changes_modified_bleed(self):
1263
self.wt.set_root_id(b'eert_toor')
1264
"""Modified flag should not bleed from one change to another"""
1265
# unfortunately, we have no guarantee that file1 (which is modified)
1266
# will be applied before file2. And if it's applied after file2, it
1267
# obviously can't bleed into file2's change output. But for now, it
1269
transform, root = self.get_transform()
1270
transform.new_file('file1', root, [b'blah'], b'id-1')
1271
transform.new_file('file2', root, [b'blah'], b'id-2')
1273
transform, root = self.get_transform()
1275
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1276
transform.set_executability(True,
1277
transform.trans_id_file_id(b'id-2'))
1279
[(b'id-1', (u'file1', u'file1'), True, (True, True),
1280
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1281
('file', None), (False, False)),
1282
(b'id-2', (u'file2', u'file2'), False, (True, True),
1283
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1284
('file', 'file'), (False, True))],
1285
list(transform.iter_changes()))
1287
transform.finalize()
1289
def test_iter_changes_move_missing(self):
1290
"""Test moving ids with no files around"""
1291
self.wt.set_root_id(b'toor_eert')
1292
# Need two steps because versioning a non-existant file is a conflict.
1293
transform, root = self.get_transform()
1294
transform.new_directory('floater', root, b'floater-id')
1296
transform, root = self.get_transform()
1297
transform.delete_contents(transform.trans_id_tree_path('floater'))
1299
transform, root = self.get_transform()
1300
floater = transform.trans_id_tree_path('floater')
1302
transform.adjust_path('flitter', root, floater)
1303
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1305
(b'toor_eert', b'toor_eert'),
1306
('floater', 'flitter'),
1307
(None, None), (False, False))],
1308
list(transform.iter_changes()))
1310
transform.finalize()
1312
def test_iter_changes_pointless(self):
1313
"""Ensure that no-ops are not treated as modifications"""
1314
self.wt.set_root_id(b'eert_toor')
1315
transform, root = self.get_transform()
1316
transform.new_file('old', root, [b'blah'], b'id-1')
1317
transform.new_directory('subdir', root, b'subdir-id')
1319
transform, root = self.get_transform()
1321
old = transform.trans_id_tree_path('old')
1322
subdir = transform.trans_id_tree_path('subdir')
1323
self.assertEqual([], list(transform.iter_changes()))
1324
transform.delete_contents(subdir)
1325
transform.create_directory(subdir)
1326
transform.set_executability(False, old)
1327
transform.unversion_file(old)
1328
transform.version_file(b'id-1', old)
1329
transform.adjust_path('old', root, old)
1330
self.assertEqual([], list(transform.iter_changes()))
1332
transform.finalize()
1334
def test_rename_count(self):
1335
transform, root = self.get_transform()
1336
transform.new_file('name1', root, [b'contents'])
1337
self.assertEqual(transform.rename_count, 0)
1339
self.assertEqual(transform.rename_count, 1)
1340
transform2, root = self.get_transform()
1341
transform2.adjust_path('name2', root,
1342
transform2.trans_id_tree_path('name1'))
1343
self.assertEqual(transform2.rename_count, 0)
1345
self.assertEqual(transform2.rename_count, 2)
1347
def test_change_parent(self):
1348
"""Ensure that after we change a parent, the results are still right.
1350
Renames and parent changes on pending transforms can happen as part
1351
of conflict resolution, and are explicitly permitted by the
1354
This test ensures they work correctly with the rename-avoidance
1357
transform, root = self.get_transform()
1358
parent1 = transform.new_directory('parent1', root)
1359
child1 = transform.new_file('child1', parent1, [b'contents'])
1360
parent2 = transform.new_directory('parent2', root)
1361
transform.adjust_path('child1', parent2, child1)
1363
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1364
self.assertPathExists(self.wt.abspath('parent2/child1'))
1365
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1366
# no rename for child1 (counting only renames during apply)
1367
self.assertEqual(2, transform.rename_count)
1369
def test_cancel_parent(self):
1370
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1372
This is like the test_change_parent, except that we cancel the parent
1373
before adjusting the path. The transform must detect that the
1374
directory is non-empty, and move children to safe locations.
1376
transform, root = self.get_transform()
1377
parent1 = transform.new_directory('parent1', root)
1378
child1 = transform.new_file('child1', parent1, [b'contents'])
1379
child2 = transform.new_file('child2', parent1, [b'contents'])
1381
transform.cancel_creation(parent1)
1383
self.fail('Failed to move child1 before deleting parent1')
1384
transform.cancel_creation(child2)
1385
transform.create_directory(parent1)
1387
transform.cancel_creation(parent1)
1388
# If the transform incorrectly believes that child2 is still in
1389
# parent1's limbo directory, it will try to rename it and fail
1390
# because was already moved by the first cancel_creation.
1392
self.fail('Transform still thinks child2 is a child of parent1')
1393
parent2 = transform.new_directory('parent2', root)
1394
transform.adjust_path('child1', parent2, child1)
1396
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1397
self.assertPathExists(self.wt.abspath('parent2/child1'))
1398
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1399
self.assertEqual(2, transform.rename_count)
1401
def test_adjust_and_cancel(self):
1402
"""Make sure adjust_path keeps track of limbo children properly"""
1403
transform, root = self.get_transform()
1404
parent1 = transform.new_directory('parent1', root)
1405
child1 = transform.new_file('child1', parent1, [b'contents'])
1406
parent2 = transform.new_directory('parent2', root)
1407
transform.adjust_path('child1', parent2, child1)
1408
transform.cancel_creation(child1)
1410
transform.cancel_creation(parent1)
1411
# if the transform thinks child1 is still in parent1's limbo
1412
# directory, it will attempt to move it and fail.
1414
self.fail('Transform still thinks child1 is a child of parent1')
1415
transform.finalize()
1417
def test_noname_contents(self):
1418
"""TreeTransform should permit deferring naming files."""
1419
transform, root = self.get_transform()
1420
parent = transform.trans_id_file_id(b'parent-id')
1422
transform.create_directory(parent)
1424
self.fail("Can't handle contents with no name")
1425
transform.finalize()
1427
def test_noname_contents_nested(self):
1428
"""TreeTransform should permit deferring naming files."""
1429
transform, root = self.get_transform()
1430
parent = transform.trans_id_file_id(b'parent-id')
1432
transform.create_directory(parent)
1434
self.fail("Can't handle contents with no name")
1435
transform.new_directory('child', parent)
1436
transform.adjust_path('parent', root, parent)
1438
self.assertPathExists(self.wt.abspath('parent/child'))
1439
self.assertEqual(1, transform.rename_count)
1441
def test_reuse_name(self):
1442
"""Avoid reusing the same limbo name for different files"""
1443
transform, root = self.get_transform()
1444
parent = transform.new_directory('parent', root)
1445
transform.new_directory('child', parent)
1447
child2 = transform.new_directory('child', parent)
1449
self.fail('Tranform tried to use the same limbo name twice')
1450
transform.adjust_path('child2', parent, child2)
1452
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1453
# child2 is put into top-level limbo because child1 has already
1454
# claimed the direct limbo path when child2 is created. There is no
1455
# advantage in renaming files once they're in top-level limbo, except
1457
self.assertEqual(2, transform.rename_count)
1459
def test_reuse_when_first_moved(self):
1460
"""Don't avoid direct paths when it is safe to use them"""
1461
transform, root = self.get_transform()
1462
parent = transform.new_directory('parent', root)
1463
child1 = transform.new_directory('child', parent)
1464
transform.adjust_path('child1', parent, child1)
1465
transform.new_directory('child', parent)
1467
# limbo/new-1 => parent
1468
self.assertEqual(1, transform.rename_count)
1470
def test_reuse_after_cancel(self):
1471
"""Don't avoid direct paths when it is safe to use them"""
1472
transform, root = self.get_transform()
1473
parent2 = transform.new_directory('parent2', root)
1474
child1 = transform.new_directory('child1', parent2)
1475
transform.cancel_creation(parent2)
1476
transform.create_directory(parent2)
1477
transform.new_directory('child1', parent2)
1478
transform.adjust_path('child2', parent2, child1)
1480
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1481
self.assertEqual(2, transform.rename_count)
1483
def test_finalize_order(self):
1484
"""Finalize must be done in child-to-parent order"""
1485
transform, root = self.get_transform()
1486
parent = transform.new_directory('parent', root)
1487
transform.new_directory('child', parent)
1489
transform.finalize()
1491
self.fail('Tried to remove parent before child1')
1493
def test_cancel_with_cancelled_child_should_succeed(self):
1494
transform, root = self.get_transform()
1495
parent = transform.new_directory('parent', root)
1496
child = transform.new_directory('child', parent)
1497
transform.cancel_creation(child)
1498
transform.cancel_creation(parent)
1499
transform.finalize()
1501
def test_rollback_on_directory_clash(self):
1503
wt = self.make_branch_and_tree('.')
1504
tt = TreeTransform(wt) # TreeTransform obtains write lock
1506
foo = tt.new_directory('foo', tt.root)
1507
tt.new_file('bar', foo, [b'foobar'])
1508
baz = tt.new_directory('baz', tt.root)
1509
tt.new_file('qux', baz, [b'quux'])
1510
# Ask for a rename 'foo' -> 'baz'
1511
tt.adjust_path('baz', tt.root, foo)
1512
# Lie to tt that we've already resolved all conflicts.
1513
tt.apply(no_conflicts=True)
1514
except BaseException:
1517
# The rename will fail because the target directory is not empty (but
1518
# raises FileExists anyway).
1519
err = self.assertRaises(errors.FileExists, tt_helper)
1520
self.assertEndsWith(err.path, "/baz")
1522
def test_two_directories_clash(self):
1524
wt = self.make_branch_and_tree('.')
1525
tt = TreeTransform(wt) # TreeTransform obtains write lock
1527
foo_1 = tt.new_directory('foo', tt.root)
1528
tt.new_directory('bar', foo_1)
1529
# Adding the same directory with a different content
1530
foo_2 = tt.new_directory('foo', tt.root)
1531
tt.new_directory('baz', foo_2)
1532
# Lie to tt that we've already resolved all conflicts.
1533
tt.apply(no_conflicts=True)
1534
except BaseException:
1537
err = self.assertRaises(errors.FileExists, tt_helper)
1538
self.assertEndsWith(err.path, "/foo")
1540
def test_two_directories_clash_finalize(self):
1542
wt = self.make_branch_and_tree('.')
1543
tt = TreeTransform(wt) # TreeTransform obtains write lock
1545
foo_1 = tt.new_directory('foo', tt.root)
1546
tt.new_directory('bar', foo_1)
1547
# Adding the same directory with a different content
1548
foo_2 = tt.new_directory('foo', tt.root)
1549
tt.new_directory('baz', foo_2)
1550
# Lie to tt that we've already resolved all conflicts.
1551
tt.apply(no_conflicts=True)
1552
except BaseException:
1555
err = self.assertRaises(errors.FileExists, tt_helper)
1556
self.assertEndsWith(err.path, "/foo")
1558
def test_file_to_directory(self):
1559
wt = self.make_branch_and_tree('.')
1560
self.build_tree(['foo'])
1563
tt = TreeTransform(wt)
1564
self.addCleanup(tt.finalize)
1565
foo_trans_id = tt.trans_id_tree_path("foo")
1566
tt.delete_contents(foo_trans_id)
1567
tt.create_directory(foo_trans_id)
1568
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1569
tt.create_file([b"aa\n"], bar_trans_id)
1570
tt.version_file(b"bar-1", bar_trans_id)
1572
self.assertPathExists("foo/bar")
1575
self.assertEqual(wt.kind("foo"), "directory")
1579
changes = wt.changes_from(wt.basis_tree())
1580
self.assertFalse(changes.has_changed(), changes)
1582
def test_file_to_symlink(self):
1583
self.requireFeature(SymlinkFeature)
1584
wt = self.make_branch_and_tree('.')
1585
self.build_tree(['foo'])
1588
tt = TreeTransform(wt)
1589
self.addCleanup(tt.finalize)
1590
foo_trans_id = tt.trans_id_tree_path("foo")
1591
tt.delete_contents(foo_trans_id)
1592
tt.create_symlink("bar", foo_trans_id)
1594
self.assertPathExists("foo")
1596
self.addCleanup(wt.unlock)
1597
self.assertEqual(wt.kind("foo"), "symlink")
1599
def test_dir_to_file(self):
1600
wt = self.make_branch_and_tree('.')
1601
self.build_tree(['foo/', 'foo/bar'])
1602
wt.add(['foo', 'foo/bar'])
1604
tt = TreeTransform(wt)
1605
self.addCleanup(tt.finalize)
1606
foo_trans_id = tt.trans_id_tree_path("foo")
1607
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1608
tt.delete_contents(foo_trans_id)
1609
tt.delete_versioned(bar_trans_id)
1610
tt.create_file([b"aa\n"], foo_trans_id)
1612
self.assertPathExists("foo")
1614
self.addCleanup(wt.unlock)
1615
self.assertEqual(wt.kind("foo"), "file")
1617
def test_dir_to_hardlink(self):
1618
self.requireFeature(HardlinkFeature)
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
self.build_tree(['baz'])
1630
tt.create_hardlink("baz", foo_trans_id)
1632
self.assertPathExists("foo")
1633
self.assertPathExists("baz")
1635
self.addCleanup(wt.unlock)
1636
self.assertEqual(wt.kind("foo"), "file")
1638
def test_no_final_path(self):
1639
transform, root = self.get_transform()
1640
trans_id = transform.trans_id_file_id(b'foo')
1641
transform.create_file([b'bar'], trans_id)
1642
transform.cancel_creation(trans_id)
1645
def test_create_from_tree(self):
1646
tree1 = self.make_branch_and_tree('tree1')
1647
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1648
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1649
tree2 = self.make_branch_and_tree('tree2')
1650
tt = TreeTransform(tree2)
1651
foo_trans_id = tt.create_path('foo', tt.root)
1652
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1653
bar_trans_id = tt.create_path('bar', tt.root)
1654
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id=b'bar-id')
1656
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1657
self.assertFileEqual(b'baz', 'tree2/bar')
1659
def test_create_from_tree_bytes(self):
1660
"""Provided lines are used instead of tree content."""
1661
tree1 = self.make_branch_and_tree('tree1')
1662
self.build_tree_contents([('tree1/foo', b'bar'), ])
1663
tree1.add('foo', b'foo-id')
1664
tree2 = self.make_branch_and_tree('tree2')
1665
tt = TreeTransform(tree2)
1666
foo_trans_id = tt.create_path('foo', tt.root)
1667
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id',
1670
self.assertFileEqual(b'qux', 'tree2/foo')
1672
def test_create_from_tree_symlink(self):
1673
self.requireFeature(SymlinkFeature)
1674
tree1 = self.make_branch_and_tree('tree1')
1675
os.symlink('bar', 'tree1/foo')
1676
tree1.add('foo', b'foo-id')
1677
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1678
foo_trans_id = tt.create_path('foo', tt.root)
1679
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1681
self.assertEqual('bar', os.readlink('tree2/foo'))
1684
class TransformGroup(object):
1686
def __init__(self, dirname, root_id):
1689
self.wt = ControlDir.create_standalone_workingtree(dirname)
1690
self.wt.set_root_id(root_id)
1691
self.b = self.wt.branch
1692
self.tt = TreeTransform(self.wt)
1693
self.root = self.tt.trans_id_tree_path('')
1696
def conflict_text(tree, merge):
1697
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1698
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1701
class TestInventoryAltered(tests.TestCaseWithTransport):
1703
def test_inventory_altered_unchanged(self):
1704
tree = self.make_branch_and_tree('tree')
1705
self.build_tree(['tree/foo'])
1706
tree.add('foo', b'foo-id')
1707
with TransformPreview(tree) as tt:
1708
self.assertEqual([], tt._inventory_altered())
1710
def test_inventory_altered_changed_parent_id(self):
1711
tree = self.make_branch_and_tree('tree')
1712
self.build_tree(['tree/foo'])
1713
tree.add('foo', b'foo-id')
1714
with TransformPreview(tree) as tt:
1715
tt.unversion_file(tt.root)
1716
tt.version_file(b'new-id', tt.root)
1717
foo_trans_id = tt.trans_id_tree_path('foo')
1718
foo_tuple = ('foo', foo_trans_id)
1719
root_tuple = ('', tt.root)
1720
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1722
def test_inventory_altered_noop_changed_parent_id(self):
1723
tree = self.make_branch_and_tree('tree')
1724
self.build_tree(['tree/foo'])
1725
tree.add('foo', b'foo-id')
1726
with TransformPreview(tree) as tt:
1727
tt.unversion_file(tt.root)
1728
tt.version_file(tree.get_root_id(), tt.root)
1729
tt.trans_id_tree_path('foo')
1730
self.assertEqual([], tt._inventory_altered())
1733
class TestTransformMerge(TestCaseInTempDir):
1735
def test_text_merge(self):
1736
root_id = generate_ids.gen_root_id()
1737
base = TransformGroup("base", root_id)
1738
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1739
base.tt.new_file('b', base.root, [b'b1'], b'b')
1740
base.tt.new_file('c', base.root, [b'c'], b'c')
1741
base.tt.new_file('d', base.root, [b'd'], b'd')
1742
base.tt.new_file('e', base.root, [b'e'], b'e')
1743
base.tt.new_file('f', base.root, [b'f'], b'f')
1744
base.tt.new_directory('g', base.root, b'g')
1745
base.tt.new_directory('h', base.root, b'h')
1747
other = TransformGroup("other", root_id)
1748
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1749
other.tt.new_file('b', other.root, [b'b2'], b'b')
1750
other.tt.new_file('c', other.root, [b'c2'], b'c')
1751
other.tt.new_file('d', other.root, [b'd'], b'd')
1752
other.tt.new_file('e', other.root, [b'e2'], b'e')
1753
other.tt.new_file('f', other.root, [b'f'], b'f')
1754
other.tt.new_file('g', other.root, [b'g'], b'g')
1755
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1756
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
1758
this = TransformGroup("this", root_id)
1759
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1760
this.tt.new_file('b', this.root, [b'b'], b'b')
1761
this.tt.new_file('c', this.root, [b'c'], b'c')
1762
this.tt.new_file('d', this.root, [b'd2'], b'd')
1763
this.tt.new_file('e', this.root, [b'e2'], b'e')
1764
this.tt.new_file('f', this.root, [b'f'], b'f')
1765
this.tt.new_file('g', this.root, [b'g'], b'g')
1766
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1767
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
1769
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1772
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1773
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
1774
# three-way text conflict
1775
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1776
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
1778
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
1780
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
1781
# Ambigious clean merge
1782
self.assertEqual(this.wt.get_file(this.wt.id2path(b'e')).read(), b'e2')
1784
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1785
# Correct correct results when THIS == OTHER
1786
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
1787
# Text conflict when THIS & OTHER are text and BASE is dir
1788
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1789
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1790
self.assertEqual(this.wt.get_file('h.THIS').read(),
1792
self.assertEqual(this.wt.get_file('h.OTHER').read(),
1794
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1795
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1796
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1797
self.assertEqual(this.wt.get_file('i.THIS').read(),
1799
self.assertEqual(this.wt.get_file('i.OTHER').read(),
1801
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1802
modified = [b'a', b'b', b'c', b'h', b'i']
1803
merge_modified = this.wt.merge_modified()
1804
self.assertSubset(merge_modified, modified)
1805
self.assertEqual(len(merge_modified), len(modified))
1806
with open(this.wt.abspath(this.wt.id2path(b'a')), 'wb') as f:
1809
merge_modified = this.wt.merge_modified()
1810
self.assertSubset(merge_modified, modified)
1811
self.assertEqual(len(merge_modified), len(modified))
1815
def test_file_merge(self):
1816
self.requireFeature(SymlinkFeature)
1817
root_id = generate_ids.gen_root_id()
1818
base = TransformGroup("BASE", root_id)
1819
this = TransformGroup("THIS", root_id)
1820
other = TransformGroup("OTHER", root_id)
1821
for tg in this, base, other:
1822
tg.tt.new_directory('a', tg.root, b'a')
1823
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1824
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1825
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1826
targets = ((base, 'base-e', 'base-f', None, None),
1827
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1828
(other, 'other-e', None, 'other-g', 'other-h'))
1829
for tg, e_target, f_target, g_target, h_target in targets:
1830
for link, target in (('e', e_target), ('f', f_target),
1831
('g', g_target), ('h', h_target)):
1832
if target is not None:
1833
tg.tt.new_symlink(link, tg.root, target,
1834
link.encode('ascii'))
1836
for tg in this, base, other:
1838
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1839
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1840
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1841
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1842
for suffix in ('THIS', 'BASE', 'OTHER'):
1843
self.assertEqual(os.readlink(
1844
this.wt.abspath('d.' + suffix)), suffix)
1845
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1846
self.assertEqual(this.wt.id2path(b'd'), 'd.OTHER')
1847
self.assertEqual(this.wt.id2path(b'f'), 'f.THIS')
1848
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1849
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1850
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1851
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1852
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1853
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1854
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1855
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1856
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1857
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1859
def test_filename_merge(self):
1860
root_id = generate_ids.gen_root_id()
1861
base = TransformGroup("BASE", root_id)
1862
this = TransformGroup("THIS", root_id)
1863
other = TransformGroup("OTHER", root_id)
1864
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1865
for t in [base, this, other]]
1866
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1867
for t in [base, this, other]]
1868
base.tt.new_directory('c', base_a, b'c')
1869
this.tt.new_directory('c1', this_a, b'c')
1870
other.tt.new_directory('c', other_b, b'c')
1872
base.tt.new_directory('d', base_a, b'd')
1873
this.tt.new_directory('d1', this_b, b'd')
1874
other.tt.new_directory('d', other_a, b'd')
1876
base.tt.new_directory('e', base_a, b'e')
1877
this.tt.new_directory('e', this_a, b'e')
1878
other.tt.new_directory('e1', other_b, b'e')
1880
base.tt.new_directory('f', base_a, b'f')
1881
this.tt.new_directory('f1', this_b, b'f')
1882
other.tt.new_directory('f1', other_b, b'f')
1884
for tg in [this, base, other]:
1886
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1887
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1888
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1889
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1890
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
1892
def test_filename_merge_conflicts(self):
1893
root_id = generate_ids.gen_root_id()
1894
base = TransformGroup("BASE", root_id)
1895
this = TransformGroup("THIS", root_id)
1896
other = TransformGroup("OTHER", root_id)
1897
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1898
for t in [base, this, other]]
1899
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1900
for t in [base, this, other]]
1902
base.tt.new_file('g', base_a, [b'g'], b'g')
1903
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1905
base.tt.new_file('h', base_a, [b'h'], b'h')
1906
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1908
base.tt.new_file('i', base.root, [b'i'], b'i')
1909
other.tt.new_directory('i1', this_b, b'i')
1911
for tg in [this, base, other]:
1913
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1915
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
1916
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1917
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1918
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
1919
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1920
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1921
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1924
class TestBuildTree(tests.TestCaseWithTransport):
1926
def test_build_tree_with_symlinks(self):
1927
self.requireFeature(SymlinkFeature)
1929
a = ControlDir.create_standalone_workingtree('a')
1931
with open('a/foo/bar', 'wb') as f:
1932
f.write(b'contents')
1933
os.symlink('a/foo/bar', 'a/foo/baz')
1934
a.add(['foo', 'foo/bar', 'foo/baz'])
1935
a.commit('initial commit')
1936
b = ControlDir.create_standalone_workingtree('b')
1937
basis = a.basis_tree()
1939
self.addCleanup(basis.unlock)
1940
build_tree(basis, b)
1941
self.assertIs(os.path.isdir('b/foo'), True)
1942
with open('b/foo/bar', 'rb') as f:
1943
self.assertEqual(f.read(), b"contents")
1944
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1946
def test_build_with_references(self):
1947
tree = self.make_branch_and_tree('source',
1948
format='development-subtree')
1949
subtree = self.make_branch_and_tree('source/subtree',
1950
format='development-subtree')
1951
tree.add_reference(subtree)
1952
tree.commit('a revision')
1953
tree.branch.create_checkout('target')
1954
self.assertPathExists('target')
1955
self.assertPathExists('target/subtree')
1957
def test_file_conflict_handling(self):
1958
"""Ensure that when building trees, conflict handling is done"""
1959
source = self.make_branch_and_tree('source')
1960
target = self.make_branch_and_tree('target')
1961
self.build_tree(['source/file', 'target/file'])
1962
source.add('file', b'new-file')
1963
source.commit('added file')
1964
build_tree(source.basis_tree(), target)
1966
[DuplicateEntry('Moved existing file to', 'file.moved',
1967
'file', None, 'new-file')],
1969
target2 = self.make_branch_and_tree('target2')
1970
with open('target2/file', 'wb') as target_file, \
1971
open('source/file', 'rb') as source_file:
1972
target_file.write(source_file.read())
1973
build_tree(source.basis_tree(), target2)
1974
self.assertEqual([], target2.conflicts())
1976
def test_symlink_conflict_handling(self):
1977
"""Ensure that when building trees, conflict handling is done"""
1978
self.requireFeature(SymlinkFeature)
1979
source = self.make_branch_and_tree('source')
1980
os.symlink('foo', 'source/symlink')
1981
source.add('symlink', b'new-symlink')
1982
source.commit('added file')
1983
target = self.make_branch_and_tree('target')
1984
os.symlink('bar', 'target/symlink')
1985
build_tree(source.basis_tree(), target)
1987
[DuplicateEntry('Moved existing file to', 'symlink.moved',
1988
'symlink', None, 'new-symlink')],
1990
target = self.make_branch_and_tree('target2')
1991
os.symlink('foo', 'target2/symlink')
1992
build_tree(source.basis_tree(), target)
1993
self.assertEqual([], target.conflicts())
1995
def test_directory_conflict_handling(self):
1996
"""Ensure that when building trees, conflict handling is done"""
1997
source = self.make_branch_and_tree('source')
1998
target = self.make_branch_and_tree('target')
1999
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
2000
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
2001
source.commit('added file')
2002
build_tree(source.basis_tree(), target)
2003
self.assertEqual([], target.conflicts())
2004
self.assertPathExists('target/dir1/file')
2006
# Ensure contents are merged
2007
target = self.make_branch_and_tree('target2')
2008
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
2009
build_tree(source.basis_tree(), target)
2010
self.assertEqual([], target.conflicts())
2011
self.assertPathExists('target2/dir1/file2')
2012
self.assertPathExists('target2/dir1/file')
2014
# Ensure new contents are suppressed for existing branches
2015
target = self.make_branch_and_tree('target3')
2016
self.make_branch('target3/dir1')
2017
self.build_tree(['target3/dir1/file2'])
2018
build_tree(source.basis_tree(), target)
2019
self.assertPathDoesNotExist('target3/dir1/file')
2020
self.assertPathExists('target3/dir1/file2')
2021
self.assertPathExists('target3/dir1.diverted/file')
2023
[DuplicateEntry('Diverted to', 'dir1.diverted',
2024
'dir1', 'new-dir1', None)],
2027
target = self.make_branch_and_tree('target4')
2028
self.build_tree(['target4/dir1/'])
2029
self.make_branch('target4/dir1/file')
2030
build_tree(source.basis_tree(), target)
2031
self.assertPathExists('target4/dir1/file')
2032
self.assertEqual('directory', file_kind('target4/dir1/file'))
2033
self.assertPathExists('target4/dir1/file.diverted')
2035
[DuplicateEntry('Diverted to', 'dir1/file.diverted',
2036
'dir1/file', 'new-file', None)],
2039
def test_mixed_conflict_handling(self):
2040
"""Ensure that when building trees, conflict handling is done"""
2041
source = self.make_branch_and_tree('source')
2042
target = self.make_branch_and_tree('target')
2043
self.build_tree(['source/name', 'target/name/'])
2044
source.add('name', b'new-name')
2045
source.commit('added file')
2046
build_tree(source.basis_tree(), target)
2048
[DuplicateEntry('Moved existing file to',
2049
'name.moved', 'name', None, 'new-name')],
2052
def test_raises_in_populated(self):
2053
source = self.make_branch_and_tree('source')
2054
self.build_tree(['source/name'])
2056
source.commit('added name')
2057
target = self.make_branch_and_tree('target')
2058
self.build_tree(['target/name'])
2060
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2061
build_tree, source.basis_tree(), target)
2063
def test_build_tree_rename_count(self):
2064
source = self.make_branch_and_tree('source')
2065
self.build_tree(['source/file1', 'source/dir1/'])
2066
source.add(['file1', 'dir1'])
2067
source.commit('add1')
2068
target1 = self.make_branch_and_tree('target1')
2069
transform_result = build_tree(source.basis_tree(), target1)
2070
self.assertEqual(2, transform_result.rename_count)
2072
self.build_tree(['source/dir1/file2'])
2073
source.add(['dir1/file2'])
2074
source.commit('add3')
2075
target2 = self.make_branch_and_tree('target2')
2076
transform_result = build_tree(source.basis_tree(), target2)
2077
# children of non-root directories should not be renamed
2078
self.assertEqual(2, transform_result.rename_count)
2080
def create_ab_tree(self):
2081
"""Create a committed test tree with two files"""
2082
source = self.make_branch_and_tree('source')
2083
self.build_tree_contents([('source/file1', b'A')])
2084
self.build_tree_contents([('source/file2', b'B')])
2085
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2086
source.commit('commit files')
2088
self.addCleanup(source.unlock)
2091
def test_build_tree_accelerator_tree(self):
2092
source = self.create_ab_tree()
2093
self.build_tree_contents([('source/file2', b'C')])
2095
real_source_get_file = source.get_file
2099
return real_source_get_file(path)
2100
source.get_file = get_file
2101
target = self.make_branch_and_tree('target')
2102
revision_tree = source.basis_tree()
2103
revision_tree.lock_read()
2104
self.addCleanup(revision_tree.unlock)
2105
build_tree(revision_tree, target, source)
2106
self.assertEqual(['file1'], calls)
2108
self.addCleanup(target.unlock)
2109
self.assertEqual([], list(target.iter_changes(revision_tree)))
2111
def test_build_tree_accelerator_tree_observes_sha1(self):
2112
source = self.create_ab_tree()
2113
sha1 = osutils.sha_string(b'A')
2114
target = self.make_branch_and_tree('target')
2116
self.addCleanup(target.unlock)
2117
state = target.current_dirstate()
2118
state._cutoff_time = time.time() + 60
2119
build_tree(source.basis_tree(), target, source)
2120
entry = state._get_entry(0, path_utf8=b'file1')
2121
self.assertEqual(sha1, entry[1][0][1])
2123
def test_build_tree_accelerator_tree_missing_file(self):
2124
source = self.create_ab_tree()
2125
os.unlink('source/file1')
2126
source.remove(['file2'])
2127
target = self.make_branch_and_tree('target')
2128
revision_tree = source.basis_tree()
2129
revision_tree.lock_read()
2130
self.addCleanup(revision_tree.unlock)
2131
build_tree(revision_tree, target, source)
2133
self.addCleanup(target.unlock)
2134
self.assertEqual([], list(target.iter_changes(revision_tree)))
2136
def test_build_tree_accelerator_wrong_kind(self):
2137
self.requireFeature(SymlinkFeature)
2138
source = self.make_branch_and_tree('source')
2139
self.build_tree_contents([('source/file1', b'')])
2140
self.build_tree_contents([('source/file2', b'')])
2141
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2142
source.commit('commit files')
2143
os.unlink('source/file2')
2144
self.build_tree_contents([('source/file2/', b'C')])
2145
os.unlink('source/file1')
2146
os.symlink('file2', 'source/file1')
2148
real_source_get_file = source.get_file
2152
return real_source_get_file(path)
2153
source.get_file = get_file
2154
target = self.make_branch_and_tree('target')
2155
revision_tree = source.basis_tree()
2156
revision_tree.lock_read()
2157
self.addCleanup(revision_tree.unlock)
2158
build_tree(revision_tree, target, source)
2159
self.assertEqual([], calls)
2161
self.addCleanup(target.unlock)
2162
self.assertEqual([], list(target.iter_changes(revision_tree)))
2164
def test_build_tree_hardlink(self):
2165
self.requireFeature(HardlinkFeature)
2166
source = self.create_ab_tree()
2167
target = self.make_branch_and_tree('target')
2168
revision_tree = source.basis_tree()
2169
revision_tree.lock_read()
2170
self.addCleanup(revision_tree.unlock)
2171
build_tree(revision_tree, target, source, hardlink=True)
2173
self.addCleanup(target.unlock)
2174
self.assertEqual([], list(target.iter_changes(revision_tree)))
2175
source_stat = os.stat('source/file1')
2176
target_stat = os.stat('target/file1')
2177
self.assertEqual(source_stat, target_stat)
2179
# Explicitly disallowing hardlinks should prevent them.
2180
target2 = self.make_branch_and_tree('target2')
2181
build_tree(revision_tree, target2, source, hardlink=False)
2183
self.addCleanup(target2.unlock)
2184
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2185
source_stat = os.stat('source/file1')
2186
target2_stat = os.stat('target2/file1')
2187
self.assertNotEqual(source_stat, target2_stat)
2189
def test_build_tree_accelerator_tree_moved(self):
2190
source = self.make_branch_and_tree('source')
2191
self.build_tree_contents([('source/file1', b'A')])
2192
source.add(['file1'], [b'file1-id'])
2193
source.commit('commit files')
2194
source.rename_one('file1', 'file2')
2196
self.addCleanup(source.unlock)
2197
target = self.make_branch_and_tree('target')
2198
revision_tree = source.basis_tree()
2199
revision_tree.lock_read()
2200
self.addCleanup(revision_tree.unlock)
2201
build_tree(revision_tree, target, source)
2203
self.addCleanup(target.unlock)
2204
self.assertEqual([], list(target.iter_changes(revision_tree)))
2206
def test_build_tree_hardlinks_preserve_execute(self):
2207
self.requireFeature(HardlinkFeature)
2208
source = self.create_ab_tree()
2209
tt = TreeTransform(source)
2210
trans_id = tt.trans_id_tree_path('file1')
2211
tt.set_executability(True, trans_id)
2213
self.assertTrue(source.is_executable('file1'))
2214
target = self.make_branch_and_tree('target')
2215
revision_tree = source.basis_tree()
2216
revision_tree.lock_read()
2217
self.addCleanup(revision_tree.unlock)
2218
build_tree(revision_tree, target, source, hardlink=True)
2220
self.addCleanup(target.unlock)
2221
self.assertEqual([], list(target.iter_changes(revision_tree)))
2222
self.assertTrue(source.is_executable('file1'))
2224
def install_rot13_content_filter(self, pattern):
2226
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2227
# below, but that looks a bit... hard to read even if it's exactly
2229
original_registry = filters._reset_registry()
2231
def restore_registry():
2232
filters._reset_registry(original_registry)
2233
self.addCleanup(restore_registry)
2235
def rot13(chunks, context=None):
2237
codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
2238
for chunk in chunks]
2239
rot13filter = filters.ContentFilter(rot13, rot13)
2240
filters.filter_stacks_registry.register(
2241
'rot13', {'yes': [rot13filter]}.get)
2242
os.mkdir(self.test_home_dir + '/.bazaar')
2243
rules_filename = self.test_home_dir + '/.bazaar/rules'
2244
with open(rules_filename, 'wb') as f:
2245
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2247
def uninstall_rules():
2248
os.remove(rules_filename)
2250
self.addCleanup(uninstall_rules)
2253
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2254
"""build_tree will not hardlink files that have content filtering rules
2255
applied to them (but will still hardlink other files from the same tree
2258
self.requireFeature(HardlinkFeature)
2259
self.install_rot13_content_filter(b'file1')
2260
source = self.create_ab_tree()
2261
target = self.make_branch_and_tree('target')
2262
revision_tree = source.basis_tree()
2263
revision_tree.lock_read()
2264
self.addCleanup(revision_tree.unlock)
2265
build_tree(revision_tree, target, source, hardlink=True)
2267
self.addCleanup(target.unlock)
2268
self.assertEqual([], list(target.iter_changes(revision_tree)))
2269
source_stat = os.stat('source/file1')
2270
target_stat = os.stat('target/file1')
2271
self.assertNotEqual(source_stat, target_stat)
2272
source_stat = os.stat('source/file2')
2273
target_stat = os.stat('target/file2')
2274
self.assertEqualStat(source_stat, target_stat)
2276
def test_case_insensitive_build_tree_inventory(self):
2277
if (features.CaseInsensitiveFilesystemFeature.available()
2278
or features.CaseInsCasePresFilenameFeature.available()):
2279
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2280
source = self.make_branch_and_tree('source')
2281
self.build_tree(['source/file', 'source/FILE'])
2282
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2283
source.commit('added files')
2284
# Don't try this at home, kids!
2285
# Force the tree to report that it is case insensitive
2286
target = self.make_branch_and_tree('target')
2287
target.case_sensitive = False
2288
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2289
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2290
self.assertEqual('FILE', target.id2path(b'upper-id'))
2292
def test_build_tree_observes_sha(self):
2293
source = self.make_branch_and_tree('source')
2294
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2295
source.add(['file1', 'dir', 'dir/file2'],
2296
[b'file1-id', b'dir-id', b'file2-id'])
2297
source.commit('new files')
2298
target = self.make_branch_and_tree('target')
2300
self.addCleanup(target.unlock)
2301
# We make use of the fact that DirState caches its cutoff time. So we
2302
# set the 'safe' time to one minute in the future.
2303
state = target.current_dirstate()
2304
state._cutoff_time = time.time() + 60
2305
build_tree(source.basis_tree(), target)
2306
entry1_sha = osutils.sha_file_by_name('source/file1')
2307
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2308
# entry[1] is the state information, entry[1][0] is the state of the
2309
# working tree, entry[1][0][1] is the sha value for the current working
2311
entry1 = state._get_entry(0, path_utf8=b'file1')
2312
self.assertEqual(entry1_sha, entry1[1][0][1])
2313
# The 'size' field must also be set.
2314
self.assertEqual(25, entry1[1][0][2])
2315
entry1_state = entry1[1][0]
2316
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2317
self.assertEqual(entry2_sha, entry2[1][0][1])
2318
self.assertEqual(29, entry2[1][0][2])
2319
entry2_state = entry2[1][0]
2320
# Now, make sure that we don't have to re-read the content. The
2321
# packed_stat should match exactly.
2322
self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
2323
self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
2324
self.assertEqual(entry1_state, entry1[1][0])
2325
self.assertEqual(entry2_state, entry2[1][0])
2328
class TestCommitTransform(tests.TestCaseWithTransport):
2330
def get_branch(self):
2331
tree = self.make_branch_and_tree('tree')
2333
self.addCleanup(tree.unlock)
2334
tree.commit('empty commit')
2337
def get_branch_and_transform(self):
2338
branch = self.get_branch()
2339
tt = TransformPreview(branch.basis_tree())
2340
self.addCleanup(tt.finalize)
2343
def test_commit_wrong_basis(self):
2344
branch = self.get_branch()
2345
basis = branch.repository.revision_tree(
2346
_mod_revision.NULL_REVISION)
2347
tt = TransformPreview(basis)
2348
self.addCleanup(tt.finalize)
2349
e = self.assertRaises(ValueError, tt.commit, branch, '')
2350
self.assertEqual('TreeTransform not based on branch basis: null:',
2353
def test_empy_commit(self):
2354
branch, tt = self.get_branch_and_transform()
2355
rev = tt.commit(branch, 'my message')
2356
self.assertEqual(2, branch.revno())
2357
repo = branch.repository
2358
self.assertEqual('my message', repo.get_revision(rev).message)
2360
def test_merge_parents(self):
2361
branch, tt = self.get_branch_and_transform()
2362
tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2363
self.assertEqual([b'rev1b', b'rev1c'],
2364
branch.basis_tree().get_parent_ids()[1:])
2366
def test_first_commit(self):
2367
branch = self.make_branch('branch')
2369
self.addCleanup(branch.unlock)
2370
tt = TransformPreview(branch.basis_tree())
2371
self.addCleanup(tt.finalize)
2372
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2373
tt.commit(branch, 'my message')
2374
self.assertEqual([], branch.basis_tree().get_parent_ids())
2375
self.assertNotEqual(_mod_revision.NULL_REVISION,
2376
branch.last_revision())
2378
def test_first_commit_with_merge_parents(self):
2379
branch = self.make_branch('branch')
2381
self.addCleanup(branch.unlock)
2382
tt = TransformPreview(branch.basis_tree())
2383
self.addCleanup(tt.finalize)
2384
e = self.assertRaises(ValueError, tt.commit, branch,
2385
'my message', [b'rev1b-id'])
2386
self.assertEqual('Cannot supply merge parents for first commit.',
2388
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2390
def test_add_files(self):
2391
branch, tt = self.get_branch_and_transform()
2392
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2393
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2394
if SymlinkFeature.available():
2395
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2396
tt.commit(branch, 'message')
2397
tree = branch.basis_tree()
2398
self.assertEqual('file', tree.id2path(b'file-id'))
2399
self.assertEqual(b'contents', tree.get_file_text('file'))
2400
self.assertEqual('dir', tree.id2path(b'dir-id'))
2401
if SymlinkFeature.available():
2402
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2403
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2405
def test_add_unversioned(self):
2406
branch, tt = self.get_branch_and_transform()
2407
tt.new_file('file', tt.root, [b'contents'])
2408
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2409
'message', strict=True)
2411
def test_modify_strict(self):
2412
branch, tt = self.get_branch_and_transform()
2413
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2414
tt.commit(branch, 'message', strict=True)
2415
tt = TransformPreview(branch.basis_tree())
2416
self.addCleanup(tt.finalize)
2417
trans_id = tt.trans_id_file_id(b'file-id')
2418
tt.delete_contents(trans_id)
2419
tt.create_file([b'contents'], trans_id)
2420
tt.commit(branch, 'message', strict=True)
2422
def test_commit_malformed(self):
2423
"""Committing a malformed transform should raise an exception.
2425
In this case, we are adding a file without adding its parent.
2427
branch, tt = self.get_branch_and_transform()
2428
parent_id = tt.trans_id_file_id(b'parent-id')
2429
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2430
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2433
def test_commit_rich_revision_data(self):
2434
branch, tt = self.get_branch_and_transform()
2435
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2436
committer='me <me@example.com>',
2437
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2438
authors=['Author1 <author1@example.com>',
2439
'Author2 <author2@example.com>',
2441
self.assertEqual(b'revid-1', rev_id)
2442
revision = branch.repository.get_revision(rev_id)
2443
self.assertEqual(1, revision.timestamp)
2444
self.assertEqual(43201, revision.timezone)
2445
self.assertEqual('me <me@example.com>', revision.committer)
2446
self.assertEqual(['Author1 <author1@example.com>',
2447
'Author2 <author2@example.com>'],
2448
revision.get_apparent_authors())
2449
del revision.properties['authors']
2450
self.assertEqual({'foo': 'bar',
2451
'branch-nick': 'tree'},
2452
revision.properties)
2454
def test_no_explicit_revprops(self):
2455
branch, tt = self.get_branch_and_transform()
2456
rev_id = tt.commit(branch, 'message', authors=[
2457
'Author1 <author1@example.com>',
2458
'Author2 <author2@example.com>', ])
2459
revision = branch.repository.get_revision(rev_id)
2460
self.assertEqual(['Author1 <author1@example.com>',
2461
'Author2 <author2@example.com>'],
2462
revision.get_apparent_authors())
2463
self.assertEqual('tree', revision.properties['branch-nick'])
2466
class TestFileMover(tests.TestCaseWithTransport):
2468
def test_file_mover(self):
2469
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2470
mover = _FileMover()
2471
mover.rename('a', 'q')
2472
self.assertPathExists('q')
2473
self.assertPathDoesNotExist('a')
2474
self.assertPathExists('q/b')
2475
self.assertPathExists('c')
2476
self.assertPathExists('c/d')
2478
def test_pre_delete_rollback(self):
2479
self.build_tree(['a/'])
2480
mover = _FileMover()
2481
mover.pre_delete('a', 'q')
2482
self.assertPathExists('q')
2483
self.assertPathDoesNotExist('a')
2485
self.assertPathDoesNotExist('q')
2486
self.assertPathExists('a')
2488
def test_apply_deletions(self):
2489
self.build_tree(['a/', 'b/'])
2490
mover = _FileMover()
2491
mover.pre_delete('a', 'q')
2492
mover.pre_delete('b', 'r')
2493
self.assertPathExists('q')
2494
self.assertPathExists('r')
2495
self.assertPathDoesNotExist('a')
2496
self.assertPathDoesNotExist('b')
2497
mover.apply_deletions()
2498
self.assertPathDoesNotExist('q')
2499
self.assertPathDoesNotExist('r')
2500
self.assertPathDoesNotExist('a')
2501
self.assertPathDoesNotExist('b')
2503
def test_file_mover_rollback(self):
2504
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2505
mover = _FileMover()
2506
mover.rename('c/d', 'c/f')
2507
mover.rename('c/e', 'c/d')
2509
mover.rename('a', 'c')
2510
except errors.FileExists:
2512
self.assertPathExists('a')
2513
self.assertPathExists('c/d')
2516
class Bogus(Exception):
2520
class TestTransformRollback(tests.TestCaseWithTransport):
2522
class ExceptionFileMover(_FileMover):
2524
def __init__(self, bad_source=None, bad_target=None):
2525
_FileMover.__init__(self)
2526
self.bad_source = bad_source
2527
self.bad_target = bad_target
2529
def rename(self, source, target):
2530
if (self.bad_source is not None and
2531
source.endswith(self.bad_source)):
2533
elif (self.bad_target is not None and
2534
target.endswith(self.bad_target)):
2537
_FileMover.rename(self, source, target)
2539
def test_rollback_rename(self):
2540
tree = self.make_branch_and_tree('.')
2541
self.build_tree(['a/', 'a/b'])
2542
tt = TreeTransform(tree)
2543
self.addCleanup(tt.finalize)
2544
a_id = tt.trans_id_tree_path('a')
2545
tt.adjust_path('c', tt.root, a_id)
2546
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2547
self.assertRaises(Bogus, tt.apply,
2548
_mover=self.ExceptionFileMover(bad_source='a'))
2549
self.assertPathExists('a')
2550
self.assertPathExists('a/b')
2552
self.assertPathExists('c')
2553
self.assertPathExists('c/d')
2555
def test_rollback_rename_into_place(self):
2556
tree = self.make_branch_and_tree('.')
2557
self.build_tree(['a/', 'a/b'])
2558
tt = TreeTransform(tree)
2559
self.addCleanup(tt.finalize)
2560
a_id = tt.trans_id_tree_path('a')
2561
tt.adjust_path('c', tt.root, a_id)
2562
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2563
self.assertRaises(Bogus, tt.apply,
2564
_mover=self.ExceptionFileMover(bad_target='c/d'))
2565
self.assertPathExists('a')
2566
self.assertPathExists('a/b')
2568
self.assertPathExists('c')
2569
self.assertPathExists('c/d')
2571
def test_rollback_deletion(self):
2572
tree = self.make_branch_and_tree('.')
2573
self.build_tree(['a/', 'a/b'])
2574
tt = TreeTransform(tree)
2575
self.addCleanup(tt.finalize)
2576
a_id = tt.trans_id_tree_path('a')
2577
tt.delete_contents(a_id)
2578
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2579
self.assertRaises(Bogus, tt.apply,
2580
_mover=self.ExceptionFileMover(bad_target='d'))
2581
self.assertPathExists('a')
2582
self.assertPathExists('a/b')
2585
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2586
"""Ensure treetransform creation errors can be safely cleaned up after"""
2588
def _override_globals_in_method(self, instance, method_name, globals):
2589
"""Replace method on instance with one with updated globals"""
2591
func = getattr(instance, method_name).__func__
2592
new_globals = dict(func.__globals__)
2593
new_globals.update(globals)
2594
new_func = types.FunctionType(func.__code__, new_globals,
2595
func.__name__, func.__defaults__)
2597
setattr(instance, method_name,
2598
types.MethodType(new_func, instance))
2600
setattr(instance, method_name,
2601
types.MethodType(new_func, instance, instance.__class__))
2602
self.addCleanup(delattr, instance, method_name)
2605
def _fake_open_raises_before(name, mode):
2606
"""Like open() but raises before doing anything"""
2610
def _fake_open_raises_after(name, mode):
2611
"""Like open() but raises after creating file without returning"""
2612
open(name, mode).close()
2615
def create_transform_and_root_trans_id(self):
2616
"""Setup a transform creating a file in limbo"""
2617
tree = self.make_branch_and_tree('.')
2618
tt = TreeTransform(tree)
2619
return tt, tt.create_path("a", tt.root)
2621
def create_transform_and_subdir_trans_id(self):
2622
"""Setup a transform creating a directory containing a file in limbo"""
2623
tree = self.make_branch_and_tree('.')
2624
tt = TreeTransform(tree)
2625
d_trans_id = tt.create_path("d", tt.root)
2626
tt.create_directory(d_trans_id)
2627
f_trans_id = tt.create_path("a", d_trans_id)
2628
tt.adjust_path("a", d_trans_id, f_trans_id)
2629
return tt, f_trans_id
2631
def test_root_create_file_open_raises_before_creation(self):
2632
tt, trans_id = self.create_transform_and_root_trans_id()
2633
self._override_globals_in_method(
2634
tt, "create_file", {"open": self._fake_open_raises_before})
2635
self.assertRaises(RuntimeError, tt.create_file,
2636
[b"contents"], trans_id)
2637
path = tt._limbo_name(trans_id)
2638
self.assertPathDoesNotExist(path)
2640
self.assertPathDoesNotExist(tt._limbodir)
2642
def test_root_create_file_open_raises_after_creation(self):
2643
tt, trans_id = self.create_transform_and_root_trans_id()
2644
self._override_globals_in_method(
2645
tt, "create_file", {"open": self._fake_open_raises_after})
2646
self.assertRaises(RuntimeError, tt.create_file,
2647
[b"contents"], trans_id)
2648
path = tt._limbo_name(trans_id)
2649
self.assertPathExists(path)
2651
self.assertPathDoesNotExist(path)
2652
self.assertPathDoesNotExist(tt._limbodir)
2654
def test_subdir_create_file_open_raises_before_creation(self):
2655
tt, trans_id = self.create_transform_and_subdir_trans_id()
2656
self._override_globals_in_method(
2657
tt, "create_file", {"open": self._fake_open_raises_before})
2658
self.assertRaises(RuntimeError, tt.create_file,
2659
[b"contents"], trans_id)
2660
path = tt._limbo_name(trans_id)
2661
self.assertPathDoesNotExist(path)
2663
self.assertPathDoesNotExist(tt._limbodir)
2665
def test_subdir_create_file_open_raises_after_creation(self):
2666
tt, trans_id = self.create_transform_and_subdir_trans_id()
2667
self._override_globals_in_method(
2668
tt, "create_file", {"open": self._fake_open_raises_after})
2669
self.assertRaises(RuntimeError, tt.create_file,
2670
[b"contents"], trans_id)
2671
path = tt._limbo_name(trans_id)
2672
self.assertPathExists(path)
2674
self.assertPathDoesNotExist(path)
2675
self.assertPathDoesNotExist(tt._limbodir)
2677
def test_rename_in_limbo_rename_raises_after_rename(self):
2678
tt, trans_id = self.create_transform_and_root_trans_id()
2679
parent1 = tt.new_directory('parent1', tt.root)
2680
child1 = tt.new_file('child1', parent1, [b'contents'])
2681
parent2 = tt.new_directory('parent2', tt.root)
2683
class FakeOSModule(object):
2684
def rename(self, old, new):
2687
self._override_globals_in_method(tt, "_rename_in_limbo",
2688
{"os": FakeOSModule()})
2690
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2691
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2692
self.assertPathExists(path)
2694
self.assertPathDoesNotExist(path)
2695
self.assertPathDoesNotExist(tt._limbodir)
2697
def test_rename_in_limbo_rename_raises_before_rename(self):
2698
tt, trans_id = self.create_transform_and_root_trans_id()
2699
parent1 = tt.new_directory('parent1', tt.root)
2700
child1 = tt.new_file('child1', parent1, [b'contents'])
2701
parent2 = tt.new_directory('parent2', tt.root)
2703
class FakeOSModule(object):
2704
def rename(self, old, new):
2706
self._override_globals_in_method(tt, "_rename_in_limbo",
2707
{"os": FakeOSModule()})
2709
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2710
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2711
self.assertPathExists(path)
2713
self.assertPathDoesNotExist(path)
2714
self.assertPathDoesNotExist(tt._limbodir)
2717
class TestTransformMissingParent(tests.TestCaseWithTransport):
2719
def make_tt_with_versioned_dir(self):
2720
wt = self.make_branch_and_tree('.')
2721
self.build_tree(['dir/', ])
2722
wt.add(['dir'], [b'dir-id'])
2723
wt.commit('Create dir')
2724
tt = TreeTransform(wt)
2725
self.addCleanup(tt.finalize)
2728
def test_resolve_create_parent_for_versioned_file(self):
2729
wt, tt = self.make_tt_with_versioned_dir()
2730
dir_tid = tt.trans_id_tree_path('dir')
2731
tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2732
tt.delete_contents(dir_tid)
2733
tt.unversion_file(dir_tid)
2734
conflicts = resolve_conflicts(tt)
2735
# one conflict for the missing directory, one for the unversioned
2737
self.assertLength(2, conflicts)
2739
def test_non_versioned_file_create_conflict(self):
2740
wt, tt = self.make_tt_with_versioned_dir()
2741
dir_tid = tt.trans_id_tree_path('dir')
2742
tt.new_file('file', dir_tid, [b'Contents'])
2743
tt.delete_contents(dir_tid)
2744
tt.unversion_file(dir_tid)
2745
conflicts = resolve_conflicts(tt)
2746
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2747
self.assertLength(1, conflicts)
2748
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2752
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2753
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2755
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2756
('', ''), ('directory', 'directory'), (False, False))
2759
class TestTransformPreview(tests.TestCaseWithTransport):
2761
def create_tree(self):
2762
tree = self.make_branch_and_tree('.')
2763
self.build_tree_contents([('a', b'content 1')])
2764
tree.set_root_id(b'TREE_ROOT')
2765
tree.add('a', b'a-id')
2766
tree.commit('rev1', rev_id=b'rev1')
2767
return tree.branch.repository.revision_tree(b'rev1')
2769
def get_empty_preview(self):
2770
repository = self.make_repository('repo')
2771
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2772
preview = TransformPreview(tree)
2773
self.addCleanup(preview.finalize)
2776
def test_transform_preview(self):
2777
revision_tree = self.create_tree()
2778
preview = TransformPreview(revision_tree)
2779
self.addCleanup(preview.finalize)
2781
def test_transform_preview_tree(self):
2782
revision_tree = self.create_tree()
2783
preview = TransformPreview(revision_tree)
2784
self.addCleanup(preview.finalize)
2785
preview.get_preview_tree()
2787
def test_transform_new_file(self):
2788
revision_tree = self.create_tree()
2789
preview = TransformPreview(revision_tree)
2790
self.addCleanup(preview.finalize)
2791
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2792
preview_tree = preview.get_preview_tree()
2793
self.assertEqual(preview_tree.kind('file2'), 'file')
2794
with preview_tree.get_file('file2') as f:
2795
self.assertEqual(f.read(), b'content B\n')
2797
def test_diff_preview_tree(self):
2798
revision_tree = self.create_tree()
2799
preview = TransformPreview(revision_tree)
2800
self.addCleanup(preview.finalize)
2801
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2802
preview_tree = preview.get_preview_tree()
2804
show_diff_trees(revision_tree, preview_tree, out)
2805
lines = out.getvalue().splitlines()
2806
self.assertEqual(lines[0], b"=== added file 'file2'")
2807
# 3 lines of diff administrivia
2808
self.assertEqual(lines[4], b"+content B")
2810
def test_transform_conflicts(self):
2811
revision_tree = self.create_tree()
2812
preview = TransformPreview(revision_tree)
2813
self.addCleanup(preview.finalize)
2814
preview.new_file('a', preview.root, [b'content 2'])
2815
resolve_conflicts(preview)
2816
trans_id = preview.trans_id_file_id(b'a-id')
2817
self.assertEqual('a.moved', preview.final_name(trans_id))
2819
def get_tree_and_preview_tree(self):
2820
revision_tree = self.create_tree()
2821
preview = TransformPreview(revision_tree)
2822
self.addCleanup(preview.finalize)
2823
a_trans_id = preview.trans_id_file_id(b'a-id')
2824
preview.delete_contents(a_trans_id)
2825
preview.create_file([b'b content'], a_trans_id)
2826
preview_tree = preview.get_preview_tree()
2827
return revision_tree, preview_tree
2829
def test_iter_changes(self):
2830
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2831
root = revision_tree.get_root_id()
2832
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2833
(root, root), ('a', 'a'), ('file', 'file'),
2835
list(preview_tree.iter_changes(revision_tree)))
2837
def test_include_unchanged_succeeds(self):
2838
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2839
changes = preview_tree.iter_changes(revision_tree,
2840
include_unchanged=True)
2841
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2843
def test_specific_files(self):
2844
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2845
changes = preview_tree.iter_changes(revision_tree,
2846
specific_files=[''])
2847
self.assertEqual([A_ENTRY], list(changes))
2849
def test_want_unversioned(self):
2850
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2851
changes = preview_tree.iter_changes(revision_tree,
2852
want_unversioned=True)
2853
self.assertEqual([A_ENTRY], list(changes))
2855
def test_ignore_extra_trees_no_specific_files(self):
2856
# extra_trees is harmless without specific_files, so we'll silently
2857
# accept it, even though we won't use it.
2858
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2859
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2861
def test_ignore_require_versioned_no_specific_files(self):
2862
# require_versioned is meaningless without specific_files.
2863
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2864
preview_tree.iter_changes(revision_tree, require_versioned=False)
2866
def test_ignore_pb(self):
2867
# pb could be supported, but TT.iter_changes doesn't support it.
2868
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2869
preview_tree.iter_changes(revision_tree)
2871
def test_kind(self):
2872
revision_tree = self.create_tree()
2873
preview = TransformPreview(revision_tree)
2874
self.addCleanup(preview.finalize)
2875
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2876
preview.new_directory('directory', preview.root, b'dir-id')
2877
preview_tree = preview.get_preview_tree()
2878
self.assertEqual('file', preview_tree.kind('file'))
2879
self.assertEqual('directory', preview_tree.kind('directory'))
2881
def test_get_file_mtime(self):
2882
preview = self.get_empty_preview()
2883
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2885
limbo_path = preview._limbo_name(file_trans_id)
2886
preview_tree = preview.get_preview_tree()
2887
self.assertEqual(os.stat(limbo_path).st_mtime,
2888
preview_tree.get_file_mtime('file'))
2890
def test_get_file_mtime_renamed(self):
2891
work_tree = self.make_branch_and_tree('tree')
2892
self.build_tree(['tree/file'])
2893
work_tree.add('file', b'file-id')
2894
preview = TransformPreview(work_tree)
2895
self.addCleanup(preview.finalize)
2896
file_trans_id = preview.trans_id_tree_path('file')
2897
preview.adjust_path('renamed', preview.root, file_trans_id)
2898
preview_tree = preview.get_preview_tree()
2899
preview_mtime = preview_tree.get_file_mtime('renamed')
2900
work_mtime = work_tree.get_file_mtime('file')
2902
def test_get_file_size(self):
2903
work_tree = self.make_branch_and_tree('tree')
2904
self.build_tree_contents([('tree/old', b'old')])
2905
work_tree.add('old', b'old-id')
2906
preview = TransformPreview(work_tree)
2907
self.addCleanup(preview.finalize)
2908
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2910
tree = preview.get_preview_tree()
2911
self.assertEqual(len('old'), tree.get_file_size('old'))
2912
self.assertEqual(len('contents'), tree.get_file_size('name'))
2914
def test_get_file(self):
2915
preview = self.get_empty_preview()
2916
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2917
preview_tree = preview.get_preview_tree()
2918
with preview_tree.get_file('file') as tree_file:
2919
self.assertEqual(b'contents', tree_file.read())
2921
def test_get_symlink_target(self):
2922
self.requireFeature(SymlinkFeature)
2923
preview = self.get_empty_preview()
2924
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2925
preview_tree = preview.get_preview_tree()
2926
self.assertEqual('target',
2927
preview_tree.get_symlink_target('symlink'))
2929
def test_all_file_ids(self):
2930
tree = self.make_branch_and_tree('tree')
2931
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2932
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2933
preview = TransformPreview(tree)
2934
self.addCleanup(preview.finalize)
2935
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2936
c_trans_id = preview.trans_id_file_id(b'c-id')
2937
preview.unversion_file(c_trans_id)
2938
preview.version_file(b'c-id', c_trans_id)
2939
preview_tree = preview.get_preview_tree()
2940
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2941
preview_tree.all_file_ids())
2943
def test_path2id_deleted_unchanged(self):
2944
tree = self.make_branch_and_tree('tree')
2945
self.build_tree(['tree/unchanged', 'tree/deleted'])
2946
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2947
preview = TransformPreview(tree)
2948
self.addCleanup(preview.finalize)
2949
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2950
preview_tree = preview.get_preview_tree()
2951
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2952
self.assertFalse(preview_tree.is_versioned('deleted'))
2954
def test_path2id_created(self):
2955
tree = self.make_branch_and_tree('tree')
2956
self.build_tree(['tree/unchanged'])
2957
tree.add(['unchanged'], [b'unchanged-id'])
2958
preview = TransformPreview(tree)
2959
self.addCleanup(preview.finalize)
2960
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2961
[b'contents'], b'new-id')
2962
preview_tree = preview.get_preview_tree()
2963
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
2965
def test_path2id_moved(self):
2966
tree = self.make_branch_and_tree('tree')
2967
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2968
tree.add(['old_parent', 'old_parent/child'],
2969
[b'old_parent-id', b'child-id'])
2970
preview = TransformPreview(tree)
2971
self.addCleanup(preview.finalize)
2972
new_parent = preview.new_directory('new_parent', preview.root,
2974
preview.adjust_path('child', new_parent,
2975
preview.trans_id_file_id(b'child-id'))
2976
preview_tree = preview.get_preview_tree()
2977
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
2978
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
2980
def test_path2id_renamed_parent(self):
2981
tree = self.make_branch_and_tree('tree')
2982
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2983
tree.add(['old_name', 'old_name/child'],
2984
[b'parent-id', b'child-id'])
2985
preview = TransformPreview(tree)
2986
self.addCleanup(preview.finalize)
2987
preview.adjust_path('new_name', preview.root,
2988
preview.trans_id_file_id(b'parent-id'))
2989
preview_tree = preview.get_preview_tree()
2990
self.assertFalse(preview_tree.is_versioned('old_name/child'))
2991
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
2993
def assertMatchingIterEntries(self, tt, specific_files=None):
2994
preview_tree = tt.get_preview_tree()
2995
preview_result = list(preview_tree.iter_entries_by_dir(
2996
specific_files=specific_files))
2999
actual_result = list(tree.iter_entries_by_dir(
3000
specific_files=specific_files))
3001
self.assertEqual(actual_result, preview_result)
3003
def test_iter_entries_by_dir_new(self):
3004
tree = self.make_branch_and_tree('tree')
3005
tt = TreeTransform(tree)
3006
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3007
self.assertMatchingIterEntries(tt)
3009
def test_iter_entries_by_dir_deleted(self):
3010
tree = self.make_branch_and_tree('tree')
3011
self.build_tree(['tree/deleted'])
3012
tree.add('deleted', b'deleted-id')
3013
tt = TreeTransform(tree)
3014
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3015
self.assertMatchingIterEntries(tt)
3017
def test_iter_entries_by_dir_unversioned(self):
3018
tree = self.make_branch_and_tree('tree')
3019
self.build_tree(['tree/removed'])
3020
tree.add('removed', b'removed-id')
3021
tt = TreeTransform(tree)
3022
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3023
self.assertMatchingIterEntries(tt)
3025
def test_iter_entries_by_dir_moved(self):
3026
tree = self.make_branch_and_tree('tree')
3027
self.build_tree(['tree/moved', 'tree/new_parent/'])
3028
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3029
tt = TreeTransform(tree)
3030
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3031
tt.trans_id_file_id(b'moved-id'))
3032
self.assertMatchingIterEntries(tt)
3034
def test_iter_entries_by_dir_specific_files(self):
3035
tree = self.make_branch_and_tree('tree')
3036
tree.set_root_id(b'tree-root-id')
3037
self.build_tree(['tree/parent/', 'tree/parent/child'])
3038
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3039
tt = TreeTransform(tree)
3040
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3042
def test_symlink_content_summary(self):
3043
self.requireFeature(SymlinkFeature)
3044
preview = self.get_empty_preview()
3045
preview.new_symlink('path', preview.root, 'target', b'path-id')
3046
summary = preview.get_preview_tree().path_content_summary('path')
3047
self.assertEqual(('symlink', None, None, 'target'), summary)
3049
def test_missing_content_summary(self):
3050
preview = self.get_empty_preview()
3051
summary = preview.get_preview_tree().path_content_summary('path')
3052
self.assertEqual(('missing', None, None, None), summary)
3054
def test_deleted_content_summary(self):
3055
tree = self.make_branch_and_tree('tree')
3056
self.build_tree(['tree/path/'])
3058
preview = TransformPreview(tree)
3059
self.addCleanup(preview.finalize)
3060
preview.delete_contents(preview.trans_id_tree_path('path'))
3061
summary = preview.get_preview_tree().path_content_summary('path')
3062
self.assertEqual(('missing', None, None, None), summary)
3064
def test_file_content_summary_executable(self):
3065
preview = self.get_empty_preview()
3066
path_id = preview.new_file('path', preview.root, [
3067
b'contents'], b'path-id')
3068
preview.set_executability(True, path_id)
3069
summary = preview.get_preview_tree().path_content_summary('path')
3070
self.assertEqual(4, len(summary))
3071
self.assertEqual('file', summary[0])
3072
# size must be known
3073
self.assertEqual(len('contents'), summary[1])
3075
self.assertEqual(True, summary[2])
3076
# will not have hash (not cheap to determine)
3077
self.assertIs(None, summary[3])
3079
def test_change_executability(self):
3080
tree = self.make_branch_and_tree('tree')
3081
self.build_tree(['tree/path'])
3083
preview = TransformPreview(tree)
3084
self.addCleanup(preview.finalize)
3085
path_id = preview.trans_id_tree_path('path')
3086
preview.set_executability(True, path_id)
3087
summary = preview.get_preview_tree().path_content_summary('path')
3088
self.assertEqual(True, summary[2])
3090
def test_file_content_summary_non_exec(self):
3091
preview = self.get_empty_preview()
3092
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3093
summary = preview.get_preview_tree().path_content_summary('path')
3094
self.assertEqual(4, len(summary))
3095
self.assertEqual('file', summary[0])
3096
# size must be known
3097
self.assertEqual(len('contents'), summary[1])
3099
self.assertEqual(False, summary[2])
3100
# will not have hash (not cheap to determine)
3101
self.assertIs(None, summary[3])
3103
def test_dir_content_summary(self):
3104
preview = self.get_empty_preview()
3105
preview.new_directory('path', preview.root, b'path-id')
3106
summary = preview.get_preview_tree().path_content_summary('path')
3107
self.assertEqual(('directory', None, None, None), summary)
3109
def test_tree_content_summary(self):
3110
preview = self.get_empty_preview()
3111
path = preview.new_directory('path', preview.root, b'path-id')
3112
preview.set_tree_reference(b'rev-1', path)
3113
summary = preview.get_preview_tree().path_content_summary('path')
3114
self.assertEqual(4, len(summary))
3115
self.assertEqual('tree-reference', summary[0])
3117
def test_annotate(self):
3118
tree = self.make_branch_and_tree('tree')
3119
self.build_tree_contents([('tree/file', b'a\n')])
3120
tree.add('file', b'file-id')
3121
tree.commit('a', rev_id=b'one')
3122
self.build_tree_contents([('tree/file', b'a\nb\n')])
3123
preview = TransformPreview(tree)
3124
self.addCleanup(preview.finalize)
3125
file_trans_id = preview.trans_id_file_id(b'file-id')
3126
preview.delete_contents(file_trans_id)
3127
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3128
preview_tree = preview.get_preview_tree()
3134
annotation = preview_tree.annotate_iter(
3135
'file', default_revision=b'me:')
3136
self.assertEqual(expected, annotation)
3138
def test_annotate_missing(self):
3139
preview = self.get_empty_preview()
3140
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3141
preview_tree = preview.get_preview_tree()
3147
annotation = preview_tree.annotate_iter(
3148
'file', default_revision=b'me:')
3149
self.assertEqual(expected, annotation)
3151
def test_annotate_rename(self):
3152
tree = self.make_branch_and_tree('tree')
3153
self.build_tree_contents([('tree/file', b'a\n')])
3154
tree.add('file', b'file-id')
3155
tree.commit('a', rev_id=b'one')
3156
preview = TransformPreview(tree)
3157
self.addCleanup(preview.finalize)
3158
file_trans_id = preview.trans_id_file_id(b'file-id')
3159
preview.adjust_path('newname', preview.root, file_trans_id)
3160
preview_tree = preview.get_preview_tree()
3164
annotation = preview_tree.annotate_iter(
3165
'file', default_revision=b'me:')
3166
self.assertEqual(expected, annotation)
3168
def test_annotate_deleted(self):
3169
tree = self.make_branch_and_tree('tree')
3170
self.build_tree_contents([('tree/file', b'a\n')])
3171
tree.add('file', b'file-id')
3172
tree.commit('a', rev_id=b'one')
3173
self.build_tree_contents([('tree/file', b'a\nb\n')])
3174
preview = TransformPreview(tree)
3175
self.addCleanup(preview.finalize)
3176
file_trans_id = preview.trans_id_file_id(b'file-id')
3177
preview.delete_contents(file_trans_id)
3178
preview_tree = preview.get_preview_tree()
3179
annotation = preview_tree.annotate_iter(
3180
'file', default_revision=b'me:')
3181
self.assertIs(None, annotation)
3183
def test_stored_kind(self):
3184
preview = self.get_empty_preview()
3185
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3186
preview_tree = preview.get_preview_tree()
3187
self.assertEqual('file', preview_tree.stored_kind('file'))
3189
def test_is_executable(self):
3190
preview = self.get_empty_preview()
3191
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3192
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3193
preview_tree = preview.get_preview_tree()
3194
self.assertEqual(True, preview_tree.is_executable('file'))
3196
def test_get_set_parent_ids(self):
3197
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3198
self.assertEqual([], preview_tree.get_parent_ids())
3199
preview_tree.set_parent_ids([b'rev-1'])
3200
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3202
def test_plan_file_merge(self):
3203
work_a = self.make_branch_and_tree('wta')
3204
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3205
work_a.add('file', b'file-id')
3206
base_id = work_a.commit('base version')
3207
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3208
preview = TransformPreview(work_a)
3209
self.addCleanup(preview.finalize)
3210
trans_id = preview.trans_id_file_id(b'file-id')
3211
preview.delete_contents(trans_id)
3212
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3213
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3214
tree_a = preview.get_preview_tree()
3215
tree_a.set_parent_ids([base_id])
3217
('killed-a', b'a\n'),
3218
('killed-b', b'b\n'),
3219
('unchanged', b'c\n'),
3220
('unchanged', b'd\n'),
3223
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3225
def test_plan_file_merge_revision_tree(self):
3226
work_a = self.make_branch_and_tree('wta')
3227
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3228
work_a.add('file', b'file-id')
3229
base_id = work_a.commit('base version')
3230
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3231
preview = TransformPreview(work_a.basis_tree())
3232
self.addCleanup(preview.finalize)
3233
trans_id = preview.trans_id_file_id(b'file-id')
3234
preview.delete_contents(trans_id)
3235
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3236
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3237
tree_a = preview.get_preview_tree()
3238
tree_a.set_parent_ids([base_id])
3240
('killed-a', b'a\n'),
3241
('killed-b', b'b\n'),
3242
('unchanged', b'c\n'),
3243
('unchanged', b'd\n'),
3246
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3248
def test_walkdirs(self):
3249
preview = self.get_empty_preview()
3250
preview.new_directory('', ROOT_PARENT, b'tree-root')
3251
# FIXME: new_directory should mark root.
3252
preview.fixup_new_roots()
3253
preview_tree = preview.get_preview_tree()
3254
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3255
expected = [(('', b'tree-root'),
3256
[('a', 'a', 'file', None, b'a-id', 'file')])]
3257
self.assertEqual(expected, list(preview_tree.walkdirs()))
3259
def test_extras(self):
3260
work_tree = self.make_branch_and_tree('tree')
3261
self.build_tree(['tree/removed-file', 'tree/existing-file',
3262
'tree/not-removed-file'])
3263
work_tree.add(['removed-file', 'not-removed-file'])
3264
preview = TransformPreview(work_tree)
3265
self.addCleanup(preview.finalize)
3266
preview.new_file('new-file', preview.root, [b'contents'])
3267
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3268
b'new-versioned-id')
3269
tree = preview.get_preview_tree()
3270
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3271
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3274
def test_merge_into_preview(self):
3275
work_tree = self.make_branch_and_tree('tree')
3276
self.build_tree_contents([('tree/file', b'b\n')])
3277
work_tree.add('file', b'file-id')
3278
work_tree.commit('first commit')
3279
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3280
self.build_tree_contents([('child/file', b'b\nc\n')])
3281
child_tree.commit('child commit')
3282
child_tree.lock_write()
3283
self.addCleanup(child_tree.unlock)
3284
work_tree.lock_write()
3285
self.addCleanup(work_tree.unlock)
3286
preview = TransformPreview(work_tree)
3287
self.addCleanup(preview.finalize)
3288
file_trans_id = preview.trans_id_file_id(b'file-id')
3289
preview.delete_contents(file_trans_id)
3290
preview.create_file([b'a\nb\n'], file_trans_id)
3291
preview_tree = preview.get_preview_tree()
3292
merger = Merger.from_revision_ids(preview_tree,
3293
child_tree.branch.last_revision(),
3294
other_branch=child_tree.branch,
3295
tree_branch=work_tree.branch)
3296
merger.merge_type = Merge3Merger
3297
tt = merger.make_merger().make_preview_transform()
3298
self.addCleanup(tt.finalize)
3299
final_tree = tt.get_preview_tree()
3302
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3304
def test_merge_preview_into_workingtree(self):
3305
tree = self.make_branch_and_tree('tree')
3306
tree.set_root_id(b'TREE_ROOT')
3307
tt = TransformPreview(tree)
3308
self.addCleanup(tt.finalize)
3309
tt.new_file('name', tt.root, [b'content'], b'file-id')
3310
tree2 = self.make_branch_and_tree('tree2')
3311
tree2.set_root_id(b'TREE_ROOT')
3312
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3314
merger.merge_type = Merge3Merger
3317
def test_merge_preview_into_workingtree_handles_conflicts(self):
3318
tree = self.make_branch_and_tree('tree')
3319
self.build_tree_contents([('tree/foo', b'bar')])
3320
tree.add('foo', b'foo-id')
3322
tt = TransformPreview(tree)
3323
self.addCleanup(tt.finalize)
3324
trans_id = tt.trans_id_file_id(b'foo-id')
3325
tt.delete_contents(trans_id)
3326
tt.create_file([b'baz'], trans_id)
3327
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3328
self.build_tree_contents([('tree2/foo', b'qux')])
3329
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3331
merger.merge_type = Merge3Merger
3334
def test_has_filename(self):
3335
wt = self.make_branch_and_tree('tree')
3336
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3337
tt = TransformPreview(wt)
3338
removed_id = tt.trans_id_tree_path('removed')
3339
tt.delete_contents(removed_id)
3340
tt.new_file('new', tt.root, [b'contents'])
3341
modified_id = tt.trans_id_tree_path('modified')
3342
tt.delete_contents(modified_id)
3343
tt.create_file([b'modified-contents'], modified_id)
3344
self.addCleanup(tt.finalize)
3345
tree = tt.get_preview_tree()
3346
self.assertTrue(tree.has_filename('unmodified'))
3347
self.assertFalse(tree.has_filename('not-present'))
3348
self.assertFalse(tree.has_filename('removed'))
3349
self.assertTrue(tree.has_filename('new'))
3350
self.assertTrue(tree.has_filename('modified'))
3352
def test_is_executable(self):
3353
tree = self.make_branch_and_tree('tree')
3354
preview = TransformPreview(tree)
3355
self.addCleanup(preview.finalize)
3356
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3357
preview_tree = preview.get_preview_tree()
3358
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3360
def test_commit_preview_tree(self):
3361
tree = self.make_branch_and_tree('tree')
3362
rev_id = tree.commit('rev1')
3363
tree.branch.lock_write()
3364
self.addCleanup(tree.branch.unlock)
3365
tt = TransformPreview(tree)
3366
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3367
self.addCleanup(tt.finalize)
3368
preview = tt.get_preview_tree()
3369
preview.set_parent_ids([rev_id])
3370
builder = tree.branch.get_commit_builder([rev_id])
3371
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3372
builder.finish_inventory()
3373
rev2_id = builder.commit('rev2')
3374
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3375
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3377
def test_ascii_limbo_paths(self):
3378
self.requireFeature(features.UnicodeFilenameFeature)
3379
branch = self.make_branch('any')
3380
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3381
tt = TransformPreview(tree)
3382
self.addCleanup(tt.finalize)
3383
foo_id = tt.new_directory('', ROOT_PARENT)
3384
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3385
limbo_path = tt._limbo_name(bar_id)
3386
self.assertEqual(limbo_path, limbo_path)
3389
class FakeSerializer(object):
3390
"""Serializer implementation that simply returns the input.
3392
The input is returned in the order used by pack.ContainerPushParser.
3395
def bytes_record(bytes, names):
3399
class TestSerializeTransform(tests.TestCaseWithTransport):
3401
_test_needs_features = [features.UnicodeFilenameFeature]
3403
def get_preview(self, tree=None):
3405
tree = self.make_branch_and_tree('tree')
3406
tt = TransformPreview(tree)
3407
self.addCleanup(tt.finalize)
3410
def assertSerializesTo(self, expected, tt):
3411
records = list(tt.serialize(FakeSerializer()))
3412
self.assertEqual(expected, records)
3415
def default_attribs():
3420
b'_new_executability': {},
3422
b'_tree_path_ids': {b'': b'new-0'},
3424
b'_removed_contents': [],
3425
b'_non_present_ids': {},
3428
def make_records(self, attribs, contents):
3430
((((b'attribs'),),), bencode.bencode(attribs))]
3431
records.extend([(((n, k),), c) for n, k, c in contents])
3434
def creation_records(self):
3435
attribs = self.default_attribs()
3436
attribs[b'_id_number'] = 3
3437
attribs[b'_new_name'] = {
3438
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3439
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3440
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3441
attribs[b'_new_executability'] = {b'new-1': 1}
3443
(b'new-1', b'file', b'i 1\nbar\n'),
3444
(b'new-2', b'directory', b''),
3446
return self.make_records(attribs, contents)
3448
def test_serialize_creation(self):
3449
tt = self.get_preview()
3450
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3451
tt.new_directory('qux', tt.root, b'quxx')
3452
self.assertSerializesTo(self.creation_records(), tt)
3454
def test_deserialize_creation(self):
3455
tt = self.get_preview()
3456
tt.deserialize(iter(self.creation_records()))
3457
self.assertEqual(3, tt._id_number)
3458
self.assertEqual({'new-1': u'foo\u1234',
3459
'new-2': 'qux'}, tt._new_name)
3460
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3461
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3462
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3463
self.assertEqual({'new-1': True}, tt._new_executability)
3464
self.assertEqual({'new-1': 'file',
3465
'new-2': 'directory'}, tt._new_contents)
3466
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3468
foo_content = foo_limbo.read()
3471
self.assertEqual(b'bar', foo_content)
3473
def symlink_creation_records(self):
3474
attribs = self.default_attribs()
3475
attribs[b'_id_number'] = 2
3476
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3477
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3478
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3479
return self.make_records(attribs, contents)
3481
def test_serialize_symlink_creation(self):
3482
self.requireFeature(features.SymlinkFeature)
3483
tt = self.get_preview()
3484
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3485
self.assertSerializesTo(self.symlink_creation_records(), tt)
3487
def test_deserialize_symlink_creation(self):
3488
self.requireFeature(features.SymlinkFeature)
3489
tt = self.get_preview()
3490
tt.deserialize(iter(self.symlink_creation_records()))
3491
abspath = tt._limbo_name('new-1')
3492
foo_content = osutils.readlink(abspath)
3493
self.assertEqual(u'bar\u1234', foo_content)
3495
def make_destruction_preview(self):
3496
tree = self.make_branch_and_tree('.')
3497
self.build_tree([u'foo\u1234', 'bar'])
3498
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3499
return self.get_preview(tree)
3501
def destruction_records(self):
3502
attribs = self.default_attribs()
3503
attribs[b'_id_number'] = 3
3504
attribs[b'_removed_id'] = [b'new-1']
3505
attribs[b'_removed_contents'] = [b'new-2']
3506
attribs[b'_tree_path_ids'] = {
3508
u'foo\u1234'.encode('utf-8'): b'new-1',
3511
return self.make_records(attribs, [])
3513
def test_serialize_destruction(self):
3514
tt = self.make_destruction_preview()
3515
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3516
tt.unversion_file(foo_trans_id)
3517
bar_trans_id = tt.trans_id_tree_path('bar')
3518
tt.delete_contents(bar_trans_id)
3519
self.assertSerializesTo(self.destruction_records(), tt)
3521
def test_deserialize_destruction(self):
3522
tt = self.make_destruction_preview()
3523
tt.deserialize(iter(self.destruction_records()))
3524
self.assertEqual({u'foo\u1234': 'new-1',
3526
'': tt.root}, tt._tree_path_ids)
3527
self.assertEqual({'new-1': u'foo\u1234',
3529
tt.root: ''}, tt._tree_id_paths)
3530
self.assertEqual({'new-1'}, tt._removed_id)
3531
self.assertEqual({'new-2'}, tt._removed_contents)
3533
def missing_records(self):
3534
attribs = self.default_attribs()
3535
attribs[b'_id_number'] = 2
3536
attribs[b'_non_present_ids'] = {
3538
return self.make_records(attribs, [])
3540
def test_serialize_missing(self):
3541
tt = self.get_preview()
3542
tt.trans_id_file_id(b'boo')
3543
self.assertSerializesTo(self.missing_records(), tt)
3545
def test_deserialize_missing(self):
3546
tt = self.get_preview()
3547
tt.deserialize(iter(self.missing_records()))
3548
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3550
def make_modification_preview(self):
3551
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3552
LINES_TWO = b'z\nbb\nx\ndd\n'
3553
tree = self.make_branch_and_tree('tree')
3554
self.build_tree_contents([('tree/file', LINES_ONE)])
3555
tree.add('file', b'file-id')
3556
return self.get_preview(tree), [LINES_TWO]
3558
def modification_records(self):
3559
attribs = self.default_attribs()
3560
attribs[b'_id_number'] = 2
3561
attribs[b'_tree_path_ids'] = {
3564
attribs[b'_removed_contents'] = [b'new-1']
3565
contents = [(b'new-1', b'file',
3566
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3567
return self.make_records(attribs, contents)
3569
def test_serialize_modification(self):
3570
tt, LINES = self.make_modification_preview()
3571
trans_id = tt.trans_id_file_id(b'file-id')
3572
tt.delete_contents(trans_id)
3573
tt.create_file(LINES, trans_id)
3574
self.assertSerializesTo(self.modification_records(), tt)
3576
def test_deserialize_modification(self):
3577
tt, LINES = self.make_modification_preview()
3578
tt.deserialize(iter(self.modification_records()))
3579
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3581
def make_kind_change_preview(self):
3582
LINES = b'a\nb\nc\nd\n'
3583
tree = self.make_branch_and_tree('tree')
3584
self.build_tree(['tree/foo/'])
3585
tree.add('foo', b'foo-id')
3586
return self.get_preview(tree), [LINES]
3588
def kind_change_records(self):
3589
attribs = self.default_attribs()
3590
attribs[b'_id_number'] = 2
3591
attribs[b'_tree_path_ids'] = {
3594
attribs[b'_removed_contents'] = [b'new-1']
3595
contents = [(b'new-1', b'file',
3596
b'i 4\na\nb\nc\nd\n\n')]
3597
return self.make_records(attribs, contents)
3599
def test_serialize_kind_change(self):
3600
tt, LINES = self.make_kind_change_preview()
3601
trans_id = tt.trans_id_file_id(b'foo-id')
3602
tt.delete_contents(trans_id)
3603
tt.create_file(LINES, trans_id)
3604
self.assertSerializesTo(self.kind_change_records(), tt)
3606
def test_deserialize_kind_change(self):
3607
tt, LINES = self.make_kind_change_preview()
3608
tt.deserialize(iter(self.kind_change_records()))
3609
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3611
def make_add_contents_preview(self):
3612
LINES = b'a\nb\nc\nd\n'
3613
tree = self.make_branch_and_tree('tree')
3614
self.build_tree(['tree/foo'])
3616
os.unlink('tree/foo')
3617
return self.get_preview(tree), LINES
3619
def add_contents_records(self):
3620
attribs = self.default_attribs()
3621
attribs[b'_id_number'] = 2
3622
attribs[b'_tree_path_ids'] = {
3625
contents = [(b'new-1', b'file',
3626
b'i 4\na\nb\nc\nd\n\n')]
3627
return self.make_records(attribs, contents)
3629
def test_serialize_add_contents(self):
3630
tt, LINES = self.make_add_contents_preview()
3631
trans_id = tt.trans_id_tree_path('foo')
3632
tt.create_file([LINES], trans_id)
3633
self.assertSerializesTo(self.add_contents_records(), tt)
3635
def test_deserialize_add_contents(self):
3636
tt, LINES = self.make_add_contents_preview()
3637
tt.deserialize(iter(self.add_contents_records()))
3638
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3640
def test_get_parents_lines(self):
3641
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3642
tree = self.make_branch_and_tree('tree')
3643
self.build_tree_contents([('tree/file', LINES_ONE)])
3644
tree.add('file', b'file-id')
3645
tt = self.get_preview(tree)
3646
trans_id = tt.trans_id_tree_path('file')
3647
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3648
tt._get_parents_lines(trans_id))
3650
def test_get_parents_texts(self):
3651
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3652
tree = self.make_branch_and_tree('tree')
3653
self.build_tree_contents([('tree/file', LINES_ONE)])
3654
tree.add('file', b'file-id')
3655
tt = self.get_preview(tree)
3656
trans_id = tt.trans_id_tree_path('file')
3657
self.assertEqual((LINES_ONE,),
3658
tt._get_parents_texts(trans_id))
3661
class TestOrphan(tests.TestCaseWithTransport):
3663
def test_no_orphan_for_transform_preview(self):
3664
tree = self.make_branch_and_tree('tree')
3665
tt = transform.TransformPreview(tree)
3666
self.addCleanup(tt.finalize)
3667
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3669
def _set_orphan_policy(self, wt, policy):
3670
wt.branch.get_config_stack().set('transform.orphan_policy',
3673
def _prepare_orphan(self, wt):
3674
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3675
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3676
wt.commit('add dir and file ignoring foo')
3677
tt = transform.TreeTransform(wt)
3678
self.addCleanup(tt.finalize)
3679
# dir and bar are deleted
3680
dir_tid = tt.trans_id_tree_path('dir')
3681
file_tid = tt.trans_id_tree_path('dir/file')
3682
orphan_tid = tt.trans_id_tree_path('dir/foo')
3683
tt.delete_contents(file_tid)
3684
tt.unversion_file(file_tid)
3685
tt.delete_contents(dir_tid)
3686
tt.unversion_file(dir_tid)
3687
# There should be a conflict because dir still contain foo
3688
raw_conflicts = tt.find_conflicts()
3689
self.assertLength(1, raw_conflicts)
3690
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3691
return tt, orphan_tid
3693
def test_new_orphan_created(self):
3694
wt = self.make_branch_and_tree('.')
3695
self._set_orphan_policy(wt, 'move')
3696
tt, orphan_tid = self._prepare_orphan(wt)
3700
warnings.append(args[0] % args[1:])
3701
self.overrideAttr(trace, 'warning', warning)
3702
remaining_conflicts = resolve_conflicts(tt)
3703
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3705
# Yeah for resolved conflicts !
3706
self.assertLength(0, remaining_conflicts)
3707
# We have a new orphan
3708
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3709
self.assertEqual('brz-orphans',
3710
tt.final_name(tt.final_parent(orphan_tid)))
3712
def test_never_orphan(self):
3713
wt = self.make_branch_and_tree('.')
3714
self._set_orphan_policy(wt, 'conflict')
3715
tt, orphan_tid = self._prepare_orphan(wt)
3716
remaining_conflicts = resolve_conflicts(tt)
3717
self.assertLength(1, remaining_conflicts)
3718
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3719
remaining_conflicts.pop())
3721
def test_orphan_error(self):
3722
def bogus_orphan(tt, orphan_id, parent_id):
3723
raise transform.OrphaningError(tt.final_name(orphan_id),
3724
tt.final_name(parent_id))
3725
transform.orphaning_registry.register('bogus', bogus_orphan,
3726
'Raise an error when orphaning')
3727
wt = self.make_branch_and_tree('.')
3728
self._set_orphan_policy(wt, 'bogus')
3729
tt, orphan_tid = self._prepare_orphan(wt)
3730
remaining_conflicts = resolve_conflicts(tt)
3731
self.assertLength(1, remaining_conflicts)
3732
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3733
remaining_conflicts.pop())
3735
def test_unknown_orphan_policy(self):
3736
wt = self.make_branch_and_tree('.')
3737
# Set a fictional policy nobody ever implemented
3738
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3739
tt, orphan_tid = self._prepare_orphan(wt)
3743
warnings.append(args[0] % args[1:])
3744
self.overrideAttr(trace, 'warning', warning)
3745
remaining_conflicts = resolve_conflicts(tt)
3746
# We fallback to the default policy which create a conflict
3747
self.assertLength(1, remaining_conflicts)
3748
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3749
remaining_conflicts.pop())
3750
self.assertLength(1, warnings)
3751
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3754
class TestTransformHooks(tests.TestCaseWithTransport):
3757
super(TestTransformHooks, self).setUp()
3758
self.wt = self.make_branch_and_tree('.')
3761
def get_transform(self):
3762
transform = TreeTransform(self.wt)
3763
self.addCleanup(transform.finalize)
3764
return transform, transform.root
3766
def test_pre_commit_hooks(self):
3769
def record_pre_transform(tree, tt):
3770
calls.append((tree, tt))
3771
MutableTree.hooks.install_named_hook(
3772
'pre_transform', record_pre_transform, "Pre transform")
3773
transform, root = self.get_transform()
3774
old_root_id = transform.tree_file_id(root)
3776
self.assertEqual(old_root_id, self.wt.get_root_id())
3777
self.assertEqual([(self.wt, transform)], calls)
3779
def test_post_commit_hooks(self):
3782
def record_post_transform(tree, tt):
3783
calls.append((tree, tt))
3784
MutableTree.hooks.install_named_hook(
3785
'post_transform', record_post_transform, "Post transform")
3786
transform, root = self.get_transform()
3787
old_root_id = transform.tree_file_id(root)
3789
self.assertEqual(old_root_id, self.wt.get_root_id())
3790
self.assertEqual([(self.wt, transform)], calls)
3793
class TestLinkTree(tests.TestCaseWithTransport):
3795
_test_needs_features = [HardlinkFeature]
3798
tests.TestCaseWithTransport.setUp(self)
3799
self.parent_tree = self.make_branch_and_tree('parent')
3800
self.parent_tree.lock_write()
3801
self.addCleanup(self.parent_tree.unlock)
3802
self.build_tree_contents([('parent/foo', b'bar')])
3803
self.parent_tree.add('foo')
3804
self.parent_tree.commit('added foo')
3805
child_controldir = self.parent_tree.controldir.sprout('child')
3806
self.child_tree = child_controldir.open_workingtree()
3808
def hardlinked(self):
3809
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3810
child_stat = os.lstat(self.child_tree.abspath('foo'))
3811
return parent_stat.st_ino == child_stat.st_ino
3813
def test_link_fails_if_modified(self):
3814
"""If the file to be linked has modified text, don't link."""
3815
self.build_tree_contents([('child/foo', b'baz')])
3816
transform.link_tree(self.child_tree, self.parent_tree)
3817
self.assertFalse(self.hardlinked())
3819
def test_link_fails_if_execute_bit_changed(self):
3820
"""If the file to be linked has modified execute bit, don't link."""
3821
tt = TreeTransform(self.child_tree)
3823
trans_id = tt.trans_id_tree_path('foo')
3824
tt.set_executability(True, trans_id)
3828
transform.link_tree(self.child_tree, self.parent_tree)
3829
self.assertFalse(self.hardlinked())
3831
def test_link_succeeds_if_unmodified(self):
3832
"""If the file to be linked is unmodified, link"""
3833
transform.link_tree(self.child_tree, self.parent_tree)
3834
self.assertTrue(self.hardlinked())