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
28
revision as _mod_revision,
38
from ..conflicts import (
47
from ..controldir import ControlDir
48
from ..diff import show_diff_trees
49
from ..errors import (
52
ExistingPendingDeletion,
54
ImmortalPendingDeletion,
59
from ..osutils import (
63
from ..merge import Merge3Merger, Merger
64
from ..mutabletree import MutableTree
65
from ..sixish import (
75
from .features import (
79
from ..transform import (
93
class TestTreeTransform(tests.TestCaseWithTransport):
96
super(TestTreeTransform, self).setUp()
97
self.wt = self.make_branch_and_tree('.', format='development-subtree')
100
def get_transform(self):
101
transform = TreeTransform(self.wt)
102
self.addCleanup(transform.finalize)
103
return transform, transform.root
105
def get_transform_for_sha1_test(self):
106
trans, root = self.get_transform()
107
self.wt.lock_tree_write()
108
self.addCleanup(self.wt.unlock)
109
contents = [b'just some content\n']
110
sha1 = osutils.sha_strings(contents)
111
# Roll back the clock
112
trans._creation_mtime = time.time() - 20.0
113
return trans, root, contents, sha1
115
def test_existing_limbo(self):
116
transform, root = self.get_transform()
117
limbo_name = transform._limbodir
118
deletion_path = transform._deletiondir
119
os.mkdir(pathjoin(limbo_name, 'hehe'))
120
self.assertRaises(ImmortalLimbo, transform.apply)
121
self.assertRaises(LockError, self.wt.unlock)
122
self.assertRaises(ExistingLimbo, self.get_transform)
123
self.assertRaises(LockError, self.wt.unlock)
124
os.rmdir(pathjoin(limbo_name, 'hehe'))
126
os.rmdir(deletion_path)
127
transform, root = self.get_transform()
130
def test_existing_pending_deletion(self):
131
transform, root = self.get_transform()
132
deletion_path = self._limbodir = urlutils.local_path_from_url(
133
transform._tree._transport.abspath('pending-deletion'))
134
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
135
self.assertRaises(ImmortalPendingDeletion, transform.apply)
136
self.assertRaises(LockError, self.wt.unlock)
137
self.assertRaises(ExistingPendingDeletion, self.get_transform)
139
def test_build(self):
140
transform, root = self.get_transform()
141
self.wt.lock_tree_write()
142
self.addCleanup(self.wt.unlock)
143
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
144
imaginary_id = transform.trans_id_tree_path('imaginary')
145
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
146
self.assertEqual(imaginary_id, imaginary_id2)
147
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
148
self.assertEqual('directory', transform.final_kind(root))
149
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
150
trans_id = transform.create_path('name', root)
151
self.assertIs(transform.final_file_id(trans_id), None)
152
self.assertIs(None, transform.final_kind(trans_id))
153
transform.create_file([b'contents'], trans_id)
154
transform.set_executability(True, trans_id)
155
transform.version_file(b'my_pretties', trans_id)
156
self.assertRaises(DuplicateKey, transform.version_file,
157
b'my_pretties', trans_id)
158
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
159
self.assertEqual(transform.final_parent(trans_id), root)
160
self.assertIs(transform.final_parent(root), ROOT_PARENT)
161
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
162
oz_id = transform.create_path('oz', root)
163
transform.create_directory(oz_id)
164
transform.version_file(b'ozzie', oz_id)
165
trans_id2 = transform.create_path('name2', root)
166
transform.create_file([b'contents'], trans_id2)
167
transform.set_executability(False, trans_id2)
168
transform.version_file(b'my_pretties2', trans_id2)
169
modified_paths = transform.apply().modified_paths
170
with self.wt.get_file('name') as f:
171
self.assertEqual(b'contents', f.read())
172
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
173
self.assertIs(self.wt.is_executable('name'), True)
174
self.assertIs(self.wt.is_executable('name2'), False)
175
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
176
self.assertEqual(len(modified_paths), 3)
177
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
178
(b'ozzie', b'my_pretties', b'my_pretties2')]
179
self.assertSubset(tree_mod_paths, modified_paths)
180
# is it safe to finalize repeatedly?
184
def test_apply_informs_tree_of_observed_sha1(self):
185
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
186
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
189
orig = self.wt._observed_sha1
191
def _observed_sha1(*args):
194
self.wt._observed_sha1 = _observed_sha1
196
self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
199
def test_create_file_caches_sha1(self):
200
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
201
trans_id = trans.create_path('file1', root)
202
trans.create_file(contents, trans_id, sha1=sha1)
203
st_val = osutils.lstat(trans._limbo_name(trans_id))
204
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
205
self.assertEqual(o_sha1, sha1)
206
self.assertEqualStat(o_st_val, st_val)
208
def test__apply_insertions_updates_sha1(self):
209
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
210
trans_id = trans.create_path('file1', root)
211
trans.create_file(contents, trans_id, sha1=sha1)
212
st_val = osutils.lstat(trans._limbo_name(trans_id))
213
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
214
self.assertEqual(o_sha1, sha1)
215
self.assertEqualStat(o_st_val, st_val)
216
creation_mtime = trans._creation_mtime + 10.0
217
# We fake a time difference from when the file was created until now it
218
# is being renamed by using os.utime. Note that the change we actually
219
# want to see is the real ctime change from 'os.rename()', but as long
220
# as we observe a new stat value, we should be fine.
221
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
223
new_st_val = osutils.lstat(self.wt.abspath('file1'))
224
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
225
self.assertEqual(o_sha1, sha1)
226
self.assertEqualStat(o_st_val, new_st_val)
227
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
229
def test_new_file_caches_sha1(self):
230
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
231
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
233
st_val = osutils.lstat(trans._limbo_name(trans_id))
234
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
235
self.assertEqual(o_sha1, sha1)
236
self.assertEqualStat(o_st_val, st_val)
238
def test_cancel_creation_removes_observed_sha1(self):
239
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
240
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
242
self.assertTrue(trans_id in trans._observed_sha1s)
243
trans.cancel_creation(trans_id)
244
self.assertFalse(trans_id in trans._observed_sha1s)
246
def test_create_files_same_timestamp(self):
247
transform, root = self.get_transform()
248
self.wt.lock_tree_write()
249
self.addCleanup(self.wt.unlock)
250
# Roll back the clock, so that we know everything is being set to the
252
transform._creation_mtime = creation_mtime = time.time() - 20.0
253
transform.create_file([b'content-one'],
254
transform.create_path('one', root))
255
time.sleep(1) # *ugly*
256
transform.create_file([b'content-two'],
257
transform.create_path('two', root))
259
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
261
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
263
# We only guarantee 2s resolution
265
abs(creation_mtime - st1.st_mtime) < 2.0,
266
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
267
# But if we have more than that, all files should get the same result
268
self.assertEqual(st1.st_mtime, st2.st_mtime)
270
def test_change_root_id(self):
271
transform, root = self.get_transform()
272
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
273
transform.new_directory('', ROOT_PARENT, b'new-root-id')
274
transform.delete_contents(root)
275
transform.unversion_file(root)
276
transform.fixup_new_roots()
278
self.assertEqual(b'new-root-id', self.wt.get_root_id())
280
def test_change_root_id_add_files(self):
281
transform, root = self.get_transform()
282
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
283
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
284
transform.new_file('file', new_trans_id, [b'new-contents\n'],
286
transform.delete_contents(root)
287
transform.unversion_file(root)
288
transform.fixup_new_roots()
290
self.assertEqual(b'new-root-id', self.wt.get_root_id())
291
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
292
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
294
def test_add_two_roots(self):
295
transform, root = self.get_transform()
296
transform.new_directory('', ROOT_PARENT, b'new-root-id')
297
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
298
self.assertRaises(ValueError, transform.fixup_new_roots)
300
def test_retain_existing_root(self):
301
tt, root = self.get_transform()
303
tt.new_directory('', ROOT_PARENT, b'new-root-id')
305
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
307
def test_retain_existing_root_added_file(self):
308
tt, root = self.get_transform()
309
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
310
child = tt.new_directory('child', new_trans_id, b'child-id')
312
self.assertEqual(tt.root, tt.final_parent(child))
314
def test_add_unversioned_root(self):
315
transform, root = self.get_transform()
316
transform.new_directory('', ROOT_PARENT, None)
317
transform.delete_contents(transform.root)
318
transform.fixup_new_roots()
319
self.assertNotIn(transform.root, transform._new_id)
321
def test_remove_root_fixup(self):
322
transform, root = self.get_transform()
323
old_root_id = self.wt.get_root_id()
324
self.assertNotEqual(b'new-root-id', old_root_id)
325
transform.delete_contents(root)
326
transform.unversion_file(root)
327
transform.fixup_new_roots()
329
self.assertEqual(old_root_id, self.wt.get_root_id())
331
transform, root = self.get_transform()
332
transform.new_directory('', ROOT_PARENT, b'new-root-id')
333
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
334
self.assertRaises(ValueError, transform.fixup_new_roots)
336
def test_fixup_new_roots_permits_empty_tree(self):
337
transform, root = self.get_transform()
338
transform.delete_contents(root)
339
transform.unversion_file(root)
340
transform.fixup_new_roots()
341
self.assertIs(None, transform.final_kind(root))
342
self.assertIs(None, transform.final_file_id(root))
344
def test_apply_retains_root_directory(self):
345
# Do not attempt to delete the physical root directory, because that
347
transform, root = self.get_transform()
349
transform.delete_contents(root)
350
e = self.assertRaises(AssertionError, self.assertRaises,
351
errors.TransformRenameFailed,
353
self.assertContainsRe('TransformRenameFailed not raised', str(e))
355
def test_apply_retains_file_id(self):
356
transform, root = self.get_transform()
357
old_root_id = transform.tree_file_id(root)
358
transform.unversion_file(root)
360
self.assertEqual(old_root_id, self.wt.get_root_id())
362
def test_hardlink(self):
363
self.requireFeature(HardlinkFeature)
364
transform, root = self.get_transform()
365
transform.new_file('file1', root, [b'contents'])
367
target = self.make_branch_and_tree('target')
368
target_transform = TreeTransform(target)
369
trans_id = target_transform.create_path('file1', target_transform.root)
370
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
371
target_transform.apply()
372
self.assertPathExists('target/file1')
373
source_stat = os.stat(self.wt.abspath('file1'))
374
target_stat = os.stat('target/file1')
375
self.assertEqual(source_stat, target_stat)
377
def test_convenience(self):
378
transform, root = self.get_transform()
379
self.wt.lock_tree_write()
380
self.addCleanup(self.wt.unlock)
381
transform.new_file('name', root, [b'contents'], b'my_pretties', True)
382
oz = transform.new_directory('oz', root, b'oz-id')
383
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
384
transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
387
self.assertEqual(len(transform.find_conflicts()), 0)
389
self.assertRaises(ReusingTransform, transform.find_conflicts)
390
with open(self.wt.abspath('name'), 'r') as f:
391
self.assertEqual('contents', f.read())
392
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
393
self.assertIs(self.wt.is_executable('name'), True)
394
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
395
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
396
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
398
self.assertEqual(b'toto-contents',
399
self.wt.get_file('oz/dorothy/toto').read())
400
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
402
def test_tree_reference(self):
403
transform, root = self.get_transform()
404
tree = transform._tree
405
trans_id = transform.new_directory('reference', root, b'subtree-id')
406
transform.set_tree_reference(b'subtree-revision', trans_id)
409
self.addCleanup(tree.unlock)
412
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
414
def test_conflicts(self):
415
transform, root = self.get_transform()
416
trans_id = transform.new_file('name', root, [b'contents'],
418
self.assertEqual(len(transform.find_conflicts()), 0)
419
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
420
self.assertEqual(transform.find_conflicts(),
421
[('duplicate', trans_id, trans_id2, 'name')])
422
self.assertRaises(MalformedTransform, transform.apply)
423
transform.adjust_path('name', trans_id, trans_id2)
424
self.assertEqual(transform.find_conflicts(),
425
[('non-directory parent', trans_id)])
426
tinman_id = transform.trans_id_tree_path('tinman')
427
transform.adjust_path('name', tinman_id, trans_id2)
428
self.assertEqual(transform.find_conflicts(),
429
[('unversioned parent', tinman_id),
430
('missing parent', tinman_id)])
431
lion_id = transform.create_path('lion', root)
432
self.assertEqual(transform.find_conflicts(),
433
[('unversioned parent', tinman_id),
434
('missing parent', tinman_id)])
435
transform.adjust_path('name', lion_id, trans_id2)
436
self.assertEqual(transform.find_conflicts(),
437
[('unversioned parent', lion_id),
438
('missing parent', lion_id)])
439
transform.version_file(b"Courage", lion_id)
440
self.assertEqual(transform.find_conflicts(),
441
[('missing parent', lion_id),
442
('versioning no contents', lion_id)])
443
transform.adjust_path('name2', root, trans_id2)
444
self.assertEqual(transform.find_conflicts(),
445
[('versioning no contents', lion_id)])
446
transform.create_file([b'Contents, okay?'], lion_id)
447
transform.adjust_path('name2', trans_id2, trans_id2)
448
self.assertEqual(transform.find_conflicts(),
449
[('parent loop', trans_id2),
450
('non-directory parent', trans_id2)])
451
transform.adjust_path('name2', root, trans_id2)
452
oz_id = transform.new_directory('oz', root)
453
transform.set_executability(True, oz_id)
454
self.assertEqual(transform.find_conflicts(),
455
[('unversioned executability', oz_id)])
456
transform.version_file(b'oz-id', oz_id)
457
self.assertEqual(transform.find_conflicts(),
458
[('non-file executability', oz_id)])
459
transform.set_executability(None, oz_id)
460
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
462
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
463
with open(self.wt.abspath('name'), 'rb') as f:
464
self.assertEqual(b'contents', f.read())
465
transform2, root = self.get_transform()
466
oz_id = transform2.trans_id_tree_path('oz')
467
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
468
result = transform2.find_conflicts()
469
fp = FinalPaths(transform2)
470
self.assertTrue('oz/tip' in transform2._tree_path_ids)
471
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
472
self.assertEqual(len(result), 2)
473
self.assertEqual((result[0][0], result[0][1]),
474
('duplicate', newtip))
475
self.assertEqual((result[1][0], result[1][2]),
476
('duplicate id', newtip))
477
transform2.finalize()
478
transform3 = TreeTransform(self.wt)
479
self.addCleanup(transform3.finalize)
480
oz_id = transform3.trans_id_tree_path('oz')
481
transform3.delete_contents(oz_id)
482
self.assertEqual(transform3.find_conflicts(),
483
[('missing parent', oz_id)])
484
root_id = transform3.root
485
tip_id = transform3.trans_id_tree_path('oz/tip')
486
transform3.adjust_path('tip', root_id, tip_id)
489
def test_conflict_on_case_insensitive(self):
490
tree = self.make_branch_and_tree('tree')
491
# Don't try this at home, kids!
492
# Force the tree to report that it is case sensitive, for conflict
494
tree.case_sensitive = True
495
transform = TreeTransform(tree)
496
self.addCleanup(transform.finalize)
497
transform.new_file('file', transform.root, [b'content'])
498
transform.new_file('FiLe', transform.root, [b'content'])
499
result = transform.find_conflicts()
500
self.assertEqual([], result)
502
# Force the tree to report that it is case insensitive, for conflict
504
tree.case_sensitive = False
505
transform = TreeTransform(tree)
506
self.addCleanup(transform.finalize)
507
transform.new_file('file', transform.root, [b'content'])
508
transform.new_file('FiLe', transform.root, [b'content'])
509
result = transform.find_conflicts()
510
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
512
def test_conflict_on_case_insensitive_existing(self):
513
tree = self.make_branch_and_tree('tree')
514
self.build_tree(['tree/FiLe'])
515
# Don't try this at home, kids!
516
# Force the tree to report that it is case sensitive, for conflict
518
tree.case_sensitive = True
519
transform = TreeTransform(tree)
520
self.addCleanup(transform.finalize)
521
transform.new_file('file', transform.root, [b'content'])
522
result = transform.find_conflicts()
523
self.assertEqual([], result)
525
# Force the tree to report that it is case insensitive, for conflict
527
tree.case_sensitive = False
528
transform = TreeTransform(tree)
529
self.addCleanup(transform.finalize)
530
transform.new_file('file', transform.root, [b'content'])
531
result = transform.find_conflicts()
532
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
534
def test_resolve_case_insensitive_conflict(self):
535
tree = self.make_branch_and_tree('tree')
536
# Don't try this at home, kids!
537
# Force the tree to report that it is case insensitive, for conflict
539
tree.case_sensitive = False
540
transform = TreeTransform(tree)
541
self.addCleanup(transform.finalize)
542
transform.new_file('file', transform.root, [b'content'])
543
transform.new_file('FiLe', transform.root, [b'content'])
544
resolve_conflicts(transform)
546
self.assertPathExists('tree/file')
547
self.assertPathExists('tree/FiLe.moved')
549
def test_resolve_checkout_case_conflict(self):
550
tree = self.make_branch_and_tree('tree')
551
# Don't try this at home, kids!
552
# Force the tree to report that it is case insensitive, for conflict
554
tree.case_sensitive = False
555
transform = TreeTransform(tree)
556
self.addCleanup(transform.finalize)
557
transform.new_file('file', transform.root, [b'content'])
558
transform.new_file('FiLe', transform.root, [b'content'])
559
resolve_conflicts(transform,
560
pass_func=lambda t, c: resolve_checkout(t, c, []))
562
self.assertPathExists('tree/file')
563
self.assertPathExists('tree/FiLe.moved')
565
def test_apply_case_conflict(self):
566
"""Ensure that a transform with case conflicts can always be applied"""
567
tree = self.make_branch_and_tree('tree')
568
transform = TreeTransform(tree)
569
self.addCleanup(transform.finalize)
570
transform.new_file('file', transform.root, [b'content'])
571
transform.new_file('FiLe', transform.root, [b'content'])
572
dir = transform.new_directory('dir', transform.root)
573
transform.new_file('dirfile', dir, [b'content'])
574
transform.new_file('dirFiLe', dir, [b'content'])
575
resolve_conflicts(transform)
577
self.assertPathExists('tree/file')
578
if not os.path.exists('tree/FiLe.moved'):
579
self.assertPathExists('tree/FiLe')
580
self.assertPathExists('tree/dir/dirfile')
581
if not os.path.exists('tree/dir/dirFiLe.moved'):
582
self.assertPathExists('tree/dir/dirFiLe')
584
def test_case_insensitive_limbo(self):
585
tree = self.make_branch_and_tree('tree')
586
# Don't try this at home, kids!
587
# Force the tree to report that it is case insensitive
588
tree.case_sensitive = False
589
transform = TreeTransform(tree)
590
self.addCleanup(transform.finalize)
591
dir = transform.new_directory('dir', transform.root)
592
first = transform.new_file('file', dir, [b'content'])
593
second = transform.new_file('FiLe', dir, [b'content'])
594
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
595
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
597
def test_adjust_path_updates_child_limbo_names(self):
598
tree = self.make_branch_and_tree('tree')
599
transform = TreeTransform(tree)
600
self.addCleanup(transform.finalize)
601
foo_id = transform.new_directory('foo', transform.root)
602
bar_id = transform.new_directory('bar', foo_id)
603
baz_id = transform.new_directory('baz', bar_id)
604
qux_id = transform.new_directory('qux', baz_id)
605
transform.adjust_path('quxx', foo_id, bar_id)
606
self.assertStartsWith(transform._limbo_name(qux_id),
607
transform._limbo_name(bar_id))
609
def test_add_del(self):
610
start, root = self.get_transform()
611
start.new_directory('a', root, b'a')
613
transform, root = self.get_transform()
614
transform.delete_versioned(transform.trans_id_tree_path('a'))
615
transform.new_directory('a', root, b'a')
618
def test_unversioning(self):
619
create_tree, root = self.get_transform()
620
parent_id = create_tree.new_directory('parent', root, b'parent-id')
621
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
623
unversion = TreeTransform(self.wt)
624
self.addCleanup(unversion.finalize)
625
parent = unversion.trans_id_tree_path('parent')
626
unversion.unversion_file(parent)
627
self.assertEqual(unversion.find_conflicts(),
628
[('unversioned parent', parent_id)])
629
file_id = unversion.trans_id_tree_path('parent/child')
630
unversion.unversion_file(file_id)
633
def test_name_invariants(self):
634
create_tree, root = self.get_transform()
636
root = create_tree.root
637
create_tree.new_file('name1', root, [b'hello1'], b'name1')
638
create_tree.new_file('name2', root, [b'hello2'], b'name2')
639
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
640
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
641
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
642
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
645
mangle_tree, root = self.get_transform()
646
root = mangle_tree.root
648
name1 = mangle_tree.trans_id_tree_path('name1')
649
name2 = mangle_tree.trans_id_tree_path('name2')
650
mangle_tree.adjust_path('name2', root, name1)
651
mangle_tree.adjust_path('name1', root, name2)
653
# tests for deleting parent directories
654
ddir = mangle_tree.trans_id_tree_path('dying_directory')
655
mangle_tree.delete_contents(ddir)
656
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
657
mangle_tree.delete_versioned(dfile)
658
mangle_tree.unversion_file(dfile)
659
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
660
mangle_tree.adjust_path('mfile', root, mfile)
662
# tests for adding parent directories
663
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
664
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
665
mangle_tree.adjust_path('mfile2', newdir, mfile2)
666
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
667
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
668
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
669
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
671
with open(self.wt.abspath('name1'), 'r') as f:
672
self.assertEqual(f.read(), 'hello2')
673
with open(self.wt.abspath('name2'), 'r') as f:
674
self.assertEqual(f.read(), 'hello1')
675
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
676
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
677
with open(mfile2_path, 'r') as f:
678
self.assertEqual(f.read(), 'later2')
679
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
680
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
681
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
682
with open(newfile_path, 'r') as f:
683
self.assertEqual(f.read(), 'hello3')
684
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
685
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
686
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
688
def test_both_rename(self):
689
create_tree, root = self.get_transform()
690
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
691
create_tree.new_file('blackbox.py', newdir, [
692
b'hello1'], b'blackbox-id')
694
mangle_tree, root = self.get_transform()
695
selftest = mangle_tree.trans_id_tree_path('selftest')
696
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
697
mangle_tree.adjust_path('test', root, selftest)
698
mangle_tree.adjust_path('test_too_much', root, selftest)
699
mangle_tree.set_executability(True, blackbox)
702
def test_both_rename2(self):
703
create_tree, root = self.get_transform()
704
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
705
tests = create_tree.new_directory('tests', breezy, b'tests-id')
706
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
707
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
710
mangle_tree, root = self.get_transform()
711
breezy = mangle_tree.trans_id_tree_path('breezy')
712
tests = mangle_tree.trans_id_tree_path('breezy/tests')
713
test_too_much = mangle_tree.trans_id_tree_path(
714
'breezy/tests/blackbox/test_too_much.py')
715
mangle_tree.adjust_path('selftest', breezy, tests)
716
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
717
mangle_tree.set_executability(True, test_too_much)
720
def test_both_rename3(self):
721
create_tree, root = self.get_transform()
722
tests = create_tree.new_directory('tests', root, b'tests-id')
723
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
726
mangle_tree, root = self.get_transform()
727
tests = mangle_tree.trans_id_tree_path('tests')
728
test_too_much = mangle_tree.trans_id_tree_path(
729
'tests/test_too_much.py')
730
mangle_tree.adjust_path('selftest', root, tests)
731
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
732
mangle_tree.set_executability(True, test_too_much)
735
def test_move_dangling_ie(self):
736
create_tree, root = self.get_transform()
738
root = create_tree.root
739
create_tree.new_file('name1', root, [b'hello1'], b'name1')
741
delete_contents, root = self.get_transform()
742
file = delete_contents.trans_id_tree_path('name1')
743
delete_contents.delete_contents(file)
744
delete_contents.apply()
745
move_id, root = self.get_transform()
746
name1 = move_id.trans_id_tree_path('name1')
747
newdir = move_id.new_directory('dir', root, b'newdir')
748
move_id.adjust_path('name2', newdir, name1)
751
def test_replace_dangling_ie(self):
752
create_tree, root = self.get_transform()
754
root = create_tree.root
755
create_tree.new_file('name1', root, [b'hello1'], b'name1')
757
delete_contents = TreeTransform(self.wt)
758
self.addCleanup(delete_contents.finalize)
759
file = delete_contents.trans_id_tree_path('name1')
760
delete_contents.delete_contents(file)
761
delete_contents.apply()
762
delete_contents.finalize()
763
replace = TreeTransform(self.wt)
764
self.addCleanup(replace.finalize)
765
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
766
conflicts = replace.find_conflicts()
767
name1 = replace.trans_id_tree_path('name1')
768
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
769
resolve_conflicts(replace)
772
def _test_symlinks(self, link_name1, link_target1,
773
link_name2, link_target2):
778
self.requireFeature(SymlinkFeature)
779
transform, root = self.get_transform()
780
oz_id = transform.new_directory('oz', root, b'oz-id')
781
transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
782
wiz_id = transform.create_path(link_name2, oz_id)
783
transform.create_symlink(link_target2, wiz_id)
784
transform.version_file(b'wiz-id2', wiz_id)
785
transform.set_executability(True, wiz_id)
786
self.assertEqual(transform.find_conflicts(),
787
[('non-file executability', wiz_id)])
788
transform.set_executability(None, wiz_id)
790
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
791
self.assertEqual('symlink',
792
file_kind(self.wt.abspath(ozpath(link_name1))))
793
self.assertEqual(link_target2,
794
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
795
self.assertEqual(link_target1,
796
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
798
def test_symlinks(self):
799
self._test_symlinks('wizard', 'wizard-target',
800
'wizard2', 'behind_curtain')
802
def test_symlinks_unicode(self):
803
self.requireFeature(features.UnicodeFilenameFeature)
804
self._test_symlinks(u'\N{Euro Sign}wizard',
805
u'wizard-targ\N{Euro Sign}t',
806
u'\N{Euro Sign}wizard2',
807
u'b\N{Euro Sign}hind_curtain')
809
def test_unable_create_symlink(self):
811
wt = self.make_branch_and_tree('.')
812
tt = TreeTransform(wt) # TreeTransform obtains write lock
814
tt.new_symlink('foo', tt.root, 'bar')
818
os_symlink = getattr(os, 'symlink', None)
821
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
823
"Unable to create symlink 'foo' on this platform",
827
os.symlink = os_symlink
829
def get_conflicted(self):
830
create, root = self.get_transform()
831
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
832
oz = create.new_directory('oz', root, b'oz-id')
833
create.new_directory('emeraldcity', oz, b'emerald-id')
835
conflicts, root = self.get_transform()
836
# set up duplicate entry, duplicate id
837
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
839
old_dorothy = conflicts.trans_id_tree_path('dorothy')
840
oz = conflicts.trans_id_tree_path('oz')
841
# set up DeletedParent parent conflict
842
conflicts.delete_versioned(oz)
843
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
844
# set up MissingParent conflict
845
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
846
conflicts.adjust_path('munchkincity', root, munchkincity)
847
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
849
conflicts.adjust_path('emeraldcity', emerald, emerald)
850
return conflicts, emerald, oz, old_dorothy, new_dorothy
852
def test_conflict_resolution(self):
853
conflicts, emerald, oz, old_dorothy, new_dorothy =\
854
self.get_conflicted()
855
resolve_conflicts(conflicts)
856
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
857
self.assertIs(conflicts.final_file_id(old_dorothy), None)
858
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
859
self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
860
self.assertEqual(conflicts.final_parent(emerald), oz)
863
def test_cook_conflicts(self):
864
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
865
raw_conflicts = resolve_conflicts(tt)
866
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
867
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
868
'dorothy', None, b'dorothy-id')
869
self.assertEqual(cooked_conflicts[0], duplicate)
870
duplicate_id = DuplicateID('Unversioned existing file',
871
'dorothy.moved', 'dorothy', None,
873
self.assertEqual(cooked_conflicts[1], duplicate_id)
874
missing_parent = MissingParent('Created directory', 'munchkincity',
876
deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
877
self.assertEqual(cooked_conflicts[2], missing_parent)
878
unversioned_parent = UnversionedParent('Versioned directory',
881
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
883
self.assertEqual(cooked_conflicts[3], unversioned_parent)
884
parent_loop = ParentLoop(
885
'Cancelled move', 'oz/emeraldcity',
886
'oz/emeraldcity', b'emerald-id', b'emerald-id')
887
self.assertEqual(cooked_conflicts[4], deleted_parent)
888
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
889
self.assertEqual(cooked_conflicts[6], parent_loop)
890
self.assertEqual(len(cooked_conflicts), 7)
893
def test_string_conflicts(self):
894
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
895
raw_conflicts = resolve_conflicts(tt)
896
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
898
conflicts_s = [text_type(c) for c in cooked_conflicts]
899
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
900
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
901
'Moved existing file to '
903
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
904
'Unversioned existing file '
906
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
907
' munchkincity. Created directory.')
908
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
909
' versioned, but has versioned'
910
' children. Versioned directory.')
911
self.assertEqualDiff(
912
conflicts_s[4], "Conflict: can't delete oz because it"
913
" is not empty. Not deleting.")
914
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
915
' versioned, but has versioned'
916
' children. Versioned directory.')
917
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
918
' oz/emeraldcity. Cancelled move.')
920
def prepare_wrong_parent_kind(self):
921
tt, root = self.get_transform()
922
tt.new_file('parent', root, [b'contents'], b'parent-id')
924
tt, root = self.get_transform()
925
parent_id = tt.trans_id_file_id(b'parent-id')
926
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
929
def test_find_conflicts_wrong_parent_kind(self):
930
tt = self.prepare_wrong_parent_kind()
933
def test_resolve_conflicts_wrong_existing_parent_kind(self):
934
tt = self.prepare_wrong_parent_kind()
935
raw_conflicts = resolve_conflicts(tt)
936
self.assertEqual({('non-directory parent', 'Created directory',
937
'new-3')}, raw_conflicts)
938
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
939
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
940
b'parent-id')], cooked_conflicts)
942
self.assertFalse(self.wt.is_versioned('parent'))
943
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
945
def test_resolve_conflicts_wrong_new_parent_kind(self):
946
tt, root = self.get_transform()
947
parent_id = tt.new_directory('parent', root, b'parent-id')
948
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
950
tt, root = self.get_transform()
951
parent_id = tt.trans_id_file_id(b'parent-id')
952
tt.delete_contents(parent_id)
953
tt.create_file([b'contents'], parent_id)
954
raw_conflicts = resolve_conflicts(tt)
955
self.assertEqual({('non-directory parent', 'Created directory',
956
'new-3')}, raw_conflicts)
958
self.assertFalse(self.wt.is_versioned('parent'))
959
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
961
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
962
tt, root = self.get_transform()
963
parent_id = tt.new_directory('parent', root)
964
tt.new_file('child,', parent_id, [b'contents2'])
966
tt, root = self.get_transform()
967
parent_id = tt.trans_id_tree_path('parent')
968
tt.delete_contents(parent_id)
969
tt.create_file([b'contents'], parent_id)
970
resolve_conflicts(tt)
972
self.assertFalse(self.wt.is_versioned('parent'))
973
self.assertFalse(self.wt.is_versioned('parent.new'))
975
def test_resolve_conflicts_missing_parent(self):
976
wt = self.make_branch_and_tree('.')
977
tt = TreeTransform(wt)
978
self.addCleanup(tt.finalize)
979
parent = tt.trans_id_file_id(b'parent-id')
980
tt.new_file('file', parent, [b'Contents'])
981
raw_conflicts = resolve_conflicts(tt)
982
# Since the directory doesn't exist it's seen as 'missing'. So
983
# 'resolve_conflicts' create a conflict asking for it to be created.
984
self.assertLength(1, raw_conflicts)
985
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
987
# apply fail since the missing directory doesn't exist
988
self.assertRaises(errors.NoFinalPath, tt.apply)
990
def test_moving_versioned_directories(self):
991
create, root = self.get_transform()
992
kansas = create.new_directory('kansas', root, b'kansas-id')
993
create.new_directory('house', kansas, b'house-id')
994
create.new_directory('oz', root, b'oz-id')
996
cyclone, root = self.get_transform()
997
oz = cyclone.trans_id_tree_path('oz')
998
house = cyclone.trans_id_tree_path('house')
999
cyclone.adjust_path('house', oz, house)
1002
def test_moving_root(self):
1003
create, root = self.get_transform()
1004
fun = create.new_directory('fun', root, b'fun-id')
1005
create.new_directory('sun', root, b'sun-id')
1006
create.new_directory('moon', root, b'moon')
1008
transform, root = self.get_transform()
1009
transform.adjust_root_path('oldroot', fun)
1010
new_root = transform.trans_id_tree_path('')
1011
transform.version_file(b'new-root', new_root)
1014
def test_renames(self):
1015
create, root = self.get_transform()
1016
old = create.new_directory('old-parent', root, b'old-id')
1017
intermediate = create.new_directory('intermediate', old, b'im-id')
1018
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1021
rename, root = self.get_transform()
1022
old = rename.trans_id_file_id(b'old-id')
1023
rename.adjust_path('new', root, old)
1024
myfile = rename.trans_id_file_id(b'myfile-id')
1025
rename.set_executability(True, myfile)
1028
def test_rename_fails(self):
1029
self.requireFeature(features.not_running_as_root)
1030
# see https://bugs.launchpad.net/bzr/+bug/491763
1031
create, root_id = self.get_transform()
1032
create.new_directory('first-dir', root_id, b'first-id')
1033
create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1035
if os.name == "posix" and sys.platform != "cygwin":
1036
# posix filesystems fail on renaming if the readonly bit is set
1037
osutils.make_readonly(self.wt.abspath('first-dir'))
1038
elif os.name == "nt":
1039
# windows filesystems fail on renaming open files
1040
self.addCleanup(open(self.wt.abspath('myfile')).close)
1042
self.skipTest("Can't force a permissions error on rename")
1043
# now transform to rename
1044
rename_transform, root_id = self.get_transform()
1045
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1046
dir_id = rename_transform.trans_id_file_id(b'first-id')
1047
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1048
e = self.assertRaises(errors.TransformRenameFailed,
1049
rename_transform.apply)
1050
# On nix looks like:
1051
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1052
# to .../first-dir/newname: [Errno 13] Permission denied"
1053
# On windows looks like:
1054
# "Failed to rename .../work/myfile to
1055
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1056
# This test isn't concerned with exactly what the error looks like,
1057
# and the strerror will vary across OS and locales, but the assert
1058
# that the exeception attributes are what we expect
1059
self.assertEqual(e.errno, errno.EACCES)
1060
if os.name == "posix":
1061
self.assertEndsWith(e.to_path, "/first-dir/newname")
1063
self.assertEqual(os.path.basename(e.from_path), "myfile")
1065
def test_set_executability_order(self):
1066
"""Ensure that executability behaves the same, no matter what order.
1068
- create file and set executability simultaneously
1069
- create file and set executability afterward
1070
- unsetting the executability of a file whose executability has not
1072
declared should throw an exception (this may happen when a
1073
merge attempts to create a file with a duplicate ID)
1075
transform, root = self.get_transform()
1076
wt = transform._tree
1078
self.addCleanup(wt.unlock)
1079
transform.new_file('set_on_creation', root, [b'Set on creation'],
1081
sac = transform.new_file('set_after_creation', root,
1082
[b'Set after creation'], b'sac')
1083
transform.set_executability(True, sac)
1084
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1086
self.assertRaises(KeyError, transform.set_executability, None, uws)
1088
self.assertTrue(wt.is_executable('set_on_creation'))
1089
self.assertTrue(wt.is_executable('set_after_creation'))
1091
def test_preserve_mode(self):
1092
"""File mode is preserved when replacing content"""
1093
if sys.platform == 'win32':
1094
raise TestSkipped('chmod has no effect on win32')
1095
transform, root = self.get_transform()
1096
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1098
self.wt.lock_write()
1099
self.addCleanup(self.wt.unlock)
1100
self.assertTrue(self.wt.is_executable('file1'))
1101
transform, root = self.get_transform()
1102
file1_id = transform.trans_id_tree_path('file1')
1103
transform.delete_contents(file1_id)
1104
transform.create_file([b'contents2'], file1_id)
1106
self.assertTrue(self.wt.is_executable('file1'))
1108
def test__set_mode_stats_correctly(self):
1109
"""_set_mode stats to determine file mode."""
1110
if sys.platform == 'win32':
1111
raise TestSkipped('chmod has no effect on win32')
1116
def instrumented_stat(path):
1117
stat_paths.append(path)
1118
return real_stat(path)
1120
transform, root = self.get_transform()
1122
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1123
file_id=b'bar-id-1', executable=False)
1126
transform, root = self.get_transform()
1127
bar1_id = transform.trans_id_tree_path('bar')
1128
bar2_id = transform.trans_id_tree_path('bar2')
1130
os.stat = instrumented_stat
1131
transform.create_file([b'bar2 contents\n'],
1132
bar2_id, mode_id=bar1_id)
1135
transform.finalize()
1137
bar1_abspath = self.wt.abspath('bar')
1138
self.assertEqual([bar1_abspath], stat_paths)
1140
def test_iter_changes(self):
1141
self.wt.set_root_id(b'eert_toor')
1142
transform, root = self.get_transform()
1143
transform.new_file('old', root, [b'blah'], b'id-1', True)
1145
transform, root = self.get_transform()
1147
self.assertEqual([], list(transform.iter_changes()))
1148
old = transform.trans_id_tree_path('old')
1149
transform.unversion_file(old)
1150
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1151
(b'eert_toor', b'eert_toor'),
1152
('old', 'old'), ('file', 'file'),
1153
(True, True))], list(transform.iter_changes()))
1154
transform.new_directory('new', root, b'id-1')
1155
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1156
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1157
('file', 'directory'),
1158
(True, False))], list(transform.iter_changes()))
1160
transform.finalize()
1162
def test_iter_changes_new(self):
1163
self.wt.set_root_id(b'eert_toor')
1164
transform, root = self.get_transform()
1165
transform.new_file('old', root, [b'blah'])
1167
transform, root = self.get_transform()
1169
old = transform.trans_id_tree_path('old')
1170
transform.version_file(b'id-1', old)
1171
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1172
(b'eert_toor', b'eert_toor'),
1173
('old', 'old'), ('file', 'file'),
1175
list(transform.iter_changes()))
1177
transform.finalize()
1179
def test_iter_changes_modifications(self):
1180
self.wt.set_root_id(b'eert_toor')
1181
transform, root = self.get_transform()
1182
transform.new_file('old', root, [b'blah'], b'id-1')
1183
transform.new_file('new', root, [b'blah'])
1184
transform.new_directory('subdir', root, b'subdir-id')
1186
transform, root = self.get_transform()
1188
old = transform.trans_id_tree_path('old')
1189
subdir = transform.trans_id_tree_path('subdir')
1190
new = transform.trans_id_tree_path('new')
1191
self.assertEqual([], list(transform.iter_changes()))
1194
transform.delete_contents(old)
1195
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1196
(b'eert_toor', b'eert_toor'),
1197
('old', 'old'), ('file', None),
1199
list(transform.iter_changes()))
1202
transform.create_file([b'blah'], old)
1203
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1204
(b'eert_toor', b'eert_toor'),
1205
('old', 'old'), ('file', 'file'),
1207
list(transform.iter_changes()))
1208
transform.cancel_deletion(old)
1209
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1210
(b'eert_toor', b'eert_toor'),
1211
('old', 'old'), ('file', 'file'),
1213
list(transform.iter_changes()))
1214
transform.cancel_creation(old)
1216
# move file_id to a different file
1217
self.assertEqual([], list(transform.iter_changes()))
1218
transform.unversion_file(old)
1219
transform.version_file(b'id-1', new)
1220
transform.adjust_path('old', root, new)
1221
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1222
(b'eert_toor', b'eert_toor'),
1223
('old', 'old'), ('file', 'file'),
1225
list(transform.iter_changes()))
1226
transform.cancel_versioning(new)
1227
transform._removed_id = set()
1230
self.assertEqual([], list(transform.iter_changes()))
1231
transform.set_executability(True, old)
1232
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1233
(b'eert_toor', b'eert_toor'),
1234
('old', 'old'), ('file', 'file'),
1236
list(transform.iter_changes()))
1237
transform.set_executability(None, old)
1240
self.assertEqual([], list(transform.iter_changes()))
1241
transform.adjust_path('new', root, old)
1242
transform._new_parent = {}
1243
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1244
(b'eert_toor', b'eert_toor'),
1245
('old', 'new'), ('file', 'file'),
1247
list(transform.iter_changes()))
1248
transform._new_name = {}
1251
self.assertEqual([], list(transform.iter_changes()))
1252
transform.adjust_path('new', subdir, old)
1253
transform._new_name = {}
1254
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1255
(True, True), (b'eert_toor',
1256
b'subdir-id'), ('old', 'old'),
1257
('file', 'file'), (False, False))],
1258
list(transform.iter_changes()))
1259
transform._new_path = {}
1262
transform.finalize()
1264
def test_iter_changes_modified_bleed(self):
1265
self.wt.set_root_id(b'eert_toor')
1266
"""Modified flag should not bleed from one change to another"""
1267
# unfortunately, we have no guarantee that file1 (which is modified)
1268
# will be applied before file2. And if it's applied after file2, it
1269
# obviously can't bleed into file2's change output. But for now, it
1271
transform, root = self.get_transform()
1272
transform.new_file('file1', root, [b'blah'], b'id-1')
1273
transform.new_file('file2', root, [b'blah'], b'id-2')
1275
transform, root = self.get_transform()
1277
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1278
transform.set_executability(True,
1279
transform.trans_id_file_id(b'id-2'))
1281
[(b'id-1', (u'file1', u'file1'), True, (True, True),
1282
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1283
('file', None), (False, False)),
1284
(b'id-2', (u'file2', u'file2'), False, (True, True),
1285
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1286
('file', 'file'), (False, True))],
1287
list(transform.iter_changes()))
1289
transform.finalize()
1291
def test_iter_changes_move_missing(self):
1292
"""Test moving ids with no files around"""
1293
self.wt.set_root_id(b'toor_eert')
1294
# Need two steps because versioning a non-existant file is a conflict.
1295
transform, root = self.get_transform()
1296
transform.new_directory('floater', root, b'floater-id')
1298
transform, root = self.get_transform()
1299
transform.delete_contents(transform.trans_id_tree_path('floater'))
1301
transform, root = self.get_transform()
1302
floater = transform.trans_id_tree_path('floater')
1304
transform.adjust_path('flitter', root, floater)
1305
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1307
(b'toor_eert', b'toor_eert'),
1308
('floater', 'flitter'),
1309
(None, None), (False, False))],
1310
list(transform.iter_changes()))
1312
transform.finalize()
1314
def test_iter_changes_pointless(self):
1315
"""Ensure that no-ops are not treated as modifications"""
1316
self.wt.set_root_id(b'eert_toor')
1317
transform, root = self.get_transform()
1318
transform.new_file('old', root, [b'blah'], b'id-1')
1319
transform.new_directory('subdir', root, b'subdir-id')
1321
transform, root = self.get_transform()
1323
old = transform.trans_id_tree_path('old')
1324
subdir = transform.trans_id_tree_path('subdir')
1325
self.assertEqual([], list(transform.iter_changes()))
1326
transform.delete_contents(subdir)
1327
transform.create_directory(subdir)
1328
transform.set_executability(False, old)
1329
transform.unversion_file(old)
1330
transform.version_file(b'id-1', old)
1331
transform.adjust_path('old', root, old)
1332
self.assertEqual([], list(transform.iter_changes()))
1334
transform.finalize()
1336
def test_rename_count(self):
1337
transform, root = self.get_transform()
1338
transform.new_file('name1', root, [b'contents'])
1339
self.assertEqual(transform.rename_count, 0)
1341
self.assertEqual(transform.rename_count, 1)
1342
transform2, root = self.get_transform()
1343
transform2.adjust_path('name2', root,
1344
transform2.trans_id_tree_path('name1'))
1345
self.assertEqual(transform2.rename_count, 0)
1347
self.assertEqual(transform2.rename_count, 2)
1349
def test_change_parent(self):
1350
"""Ensure that after we change a parent, the results are still right.
1352
Renames and parent changes on pending transforms can happen as part
1353
of conflict resolution, and are explicitly permitted by the
1356
This test ensures they work correctly with the rename-avoidance
1359
transform, root = self.get_transform()
1360
parent1 = transform.new_directory('parent1', root)
1361
child1 = transform.new_file('child1', parent1, [b'contents'])
1362
parent2 = transform.new_directory('parent2', root)
1363
transform.adjust_path('child1', parent2, child1)
1365
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1366
self.assertPathExists(self.wt.abspath('parent2/child1'))
1367
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1368
# no rename for child1 (counting only renames during apply)
1369
self.assertEqual(2, transform.rename_count)
1371
def test_cancel_parent(self):
1372
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1374
This is like the test_change_parent, except that we cancel the parent
1375
before adjusting the path. The transform must detect that the
1376
directory is non-empty, and move children to safe locations.
1378
transform, root = self.get_transform()
1379
parent1 = transform.new_directory('parent1', root)
1380
child1 = transform.new_file('child1', parent1, [b'contents'])
1381
child2 = transform.new_file('child2', parent1, [b'contents'])
1383
transform.cancel_creation(parent1)
1385
self.fail('Failed to move child1 before deleting parent1')
1386
transform.cancel_creation(child2)
1387
transform.create_directory(parent1)
1389
transform.cancel_creation(parent1)
1390
# If the transform incorrectly believes that child2 is still in
1391
# parent1's limbo directory, it will try to rename it and fail
1392
# because was already moved by the first cancel_creation.
1394
self.fail('Transform still thinks child2 is a child of parent1')
1395
parent2 = transform.new_directory('parent2', root)
1396
transform.adjust_path('child1', parent2, child1)
1398
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1399
self.assertPathExists(self.wt.abspath('parent2/child1'))
1400
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1401
self.assertEqual(2, transform.rename_count)
1403
def test_adjust_and_cancel(self):
1404
"""Make sure adjust_path keeps track of limbo children properly"""
1405
transform, root = self.get_transform()
1406
parent1 = transform.new_directory('parent1', root)
1407
child1 = transform.new_file('child1', parent1, [b'contents'])
1408
parent2 = transform.new_directory('parent2', root)
1409
transform.adjust_path('child1', parent2, child1)
1410
transform.cancel_creation(child1)
1412
transform.cancel_creation(parent1)
1413
# if the transform thinks child1 is still in parent1's limbo
1414
# directory, it will attempt to move it and fail.
1416
self.fail('Transform still thinks child1 is a child of parent1')
1417
transform.finalize()
1419
def test_noname_contents(self):
1420
"""TreeTransform should permit deferring naming files."""
1421
transform, root = self.get_transform()
1422
parent = transform.trans_id_file_id(b'parent-id')
1424
transform.create_directory(parent)
1426
self.fail("Can't handle contents with no name")
1427
transform.finalize()
1429
def test_noname_contents_nested(self):
1430
"""TreeTransform should permit deferring naming files."""
1431
transform, root = self.get_transform()
1432
parent = transform.trans_id_file_id(b'parent-id')
1434
transform.create_directory(parent)
1436
self.fail("Can't handle contents with no name")
1437
transform.new_directory('child', parent)
1438
transform.adjust_path('parent', root, parent)
1440
self.assertPathExists(self.wt.abspath('parent/child'))
1441
self.assertEqual(1, transform.rename_count)
1443
def test_reuse_name(self):
1444
"""Avoid reusing the same limbo name for different files"""
1445
transform, root = self.get_transform()
1446
parent = transform.new_directory('parent', root)
1447
transform.new_directory('child', parent)
1449
child2 = transform.new_directory('child', parent)
1451
self.fail('Tranform tried to use the same limbo name twice')
1452
transform.adjust_path('child2', parent, child2)
1454
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1455
# child2 is put into top-level limbo because child1 has already
1456
# claimed the direct limbo path when child2 is created. There is no
1457
# advantage in renaming files once they're in top-level limbo, except
1459
self.assertEqual(2, transform.rename_count)
1461
def test_reuse_when_first_moved(self):
1462
"""Don't avoid direct paths when it is safe to use them"""
1463
transform, root = self.get_transform()
1464
parent = transform.new_directory('parent', root)
1465
child1 = transform.new_directory('child', parent)
1466
transform.adjust_path('child1', parent, child1)
1467
transform.new_directory('child', parent)
1469
# limbo/new-1 => parent
1470
self.assertEqual(1, transform.rename_count)
1472
def test_reuse_after_cancel(self):
1473
"""Don't avoid direct paths when it is safe to use them"""
1474
transform, root = self.get_transform()
1475
parent2 = transform.new_directory('parent2', root)
1476
child1 = transform.new_directory('child1', parent2)
1477
transform.cancel_creation(parent2)
1478
transform.create_directory(parent2)
1479
transform.new_directory('child1', parent2)
1480
transform.adjust_path('child2', parent2, child1)
1482
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1483
self.assertEqual(2, transform.rename_count)
1485
def test_finalize_order(self):
1486
"""Finalize must be done in child-to-parent order"""
1487
transform, root = self.get_transform()
1488
parent = transform.new_directory('parent', root)
1489
transform.new_directory('child', parent)
1491
transform.finalize()
1493
self.fail('Tried to remove parent before child1')
1495
def test_cancel_with_cancelled_child_should_succeed(self):
1496
transform, root = self.get_transform()
1497
parent = transform.new_directory('parent', root)
1498
child = transform.new_directory('child', parent)
1499
transform.cancel_creation(child)
1500
transform.cancel_creation(parent)
1501
transform.finalize()
1503
def test_rollback_on_directory_clash(self):
1505
wt = self.make_branch_and_tree('.')
1506
tt = TreeTransform(wt) # TreeTransform obtains write lock
1508
foo = tt.new_directory('foo', tt.root)
1509
tt.new_file('bar', foo, [b'foobar'])
1510
baz = tt.new_directory('baz', tt.root)
1511
tt.new_file('qux', baz, [b'quux'])
1512
# Ask for a rename 'foo' -> 'baz'
1513
tt.adjust_path('baz', tt.root, foo)
1514
# Lie to tt that we've already resolved all conflicts.
1515
tt.apply(no_conflicts=True)
1516
except BaseException:
1519
# The rename will fail because the target directory is not empty (but
1520
# raises FileExists anyway).
1521
err = self.assertRaises(errors.FileExists, tt_helper)
1522
self.assertEndsWith(err.path, "/baz")
1524
def test_two_directories_clash(self):
1526
wt = self.make_branch_and_tree('.')
1527
tt = TreeTransform(wt) # TreeTransform obtains write lock
1529
foo_1 = tt.new_directory('foo', tt.root)
1530
tt.new_directory('bar', foo_1)
1531
# Adding the same directory with a different content
1532
foo_2 = tt.new_directory('foo', tt.root)
1533
tt.new_directory('baz', foo_2)
1534
# Lie to tt that we've already resolved all conflicts.
1535
tt.apply(no_conflicts=True)
1536
except BaseException:
1539
err = self.assertRaises(errors.FileExists, tt_helper)
1540
self.assertEndsWith(err.path, "/foo")
1542
def test_two_directories_clash_finalize(self):
1544
wt = self.make_branch_and_tree('.')
1545
tt = TreeTransform(wt) # TreeTransform obtains write lock
1547
foo_1 = tt.new_directory('foo', tt.root)
1548
tt.new_directory('bar', foo_1)
1549
# Adding the same directory with a different content
1550
foo_2 = tt.new_directory('foo', tt.root)
1551
tt.new_directory('baz', foo_2)
1552
# Lie to tt that we've already resolved all conflicts.
1553
tt.apply(no_conflicts=True)
1554
except BaseException:
1557
err = self.assertRaises(errors.FileExists, tt_helper)
1558
self.assertEndsWith(err.path, "/foo")
1560
def test_file_to_directory(self):
1561
wt = self.make_branch_and_tree('.')
1562
self.build_tree(['foo'])
1565
tt = TreeTransform(wt)
1566
self.addCleanup(tt.finalize)
1567
foo_trans_id = tt.trans_id_tree_path("foo")
1568
tt.delete_contents(foo_trans_id)
1569
tt.create_directory(foo_trans_id)
1570
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1571
tt.create_file([b"aa\n"], bar_trans_id)
1572
tt.version_file(b"bar-1", bar_trans_id)
1574
self.assertPathExists("foo/bar")
1577
self.assertEqual(wt.kind("foo"), "directory")
1581
changes = wt.changes_from(wt.basis_tree())
1582
self.assertFalse(changes.has_changed(), changes)
1584
def test_file_to_symlink(self):
1585
self.requireFeature(SymlinkFeature)
1586
wt = self.make_branch_and_tree('.')
1587
self.build_tree(['foo'])
1590
tt = TreeTransform(wt)
1591
self.addCleanup(tt.finalize)
1592
foo_trans_id = tt.trans_id_tree_path("foo")
1593
tt.delete_contents(foo_trans_id)
1594
tt.create_symlink("bar", foo_trans_id)
1596
self.assertPathExists("foo")
1598
self.addCleanup(wt.unlock)
1599
self.assertEqual(wt.kind("foo"), "symlink")
1601
def test_dir_to_file(self):
1602
wt = self.make_branch_and_tree('.')
1603
self.build_tree(['foo/', 'foo/bar'])
1604
wt.add(['foo', 'foo/bar'])
1606
tt = TreeTransform(wt)
1607
self.addCleanup(tt.finalize)
1608
foo_trans_id = tt.trans_id_tree_path("foo")
1609
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1610
tt.delete_contents(foo_trans_id)
1611
tt.delete_versioned(bar_trans_id)
1612
tt.create_file([b"aa\n"], foo_trans_id)
1614
self.assertPathExists("foo")
1616
self.addCleanup(wt.unlock)
1617
self.assertEqual(wt.kind("foo"), "file")
1619
def test_dir_to_hardlink(self):
1620
self.requireFeature(HardlinkFeature)
1621
wt = self.make_branch_and_tree('.')
1622
self.build_tree(['foo/', 'foo/bar'])
1623
wt.add(['foo', 'foo/bar'])
1625
tt = TreeTransform(wt)
1626
self.addCleanup(tt.finalize)
1627
foo_trans_id = tt.trans_id_tree_path("foo")
1628
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1629
tt.delete_contents(foo_trans_id)
1630
tt.delete_versioned(bar_trans_id)
1631
self.build_tree(['baz'])
1632
tt.create_hardlink("baz", foo_trans_id)
1634
self.assertPathExists("foo")
1635
self.assertPathExists("baz")
1637
self.addCleanup(wt.unlock)
1638
self.assertEqual(wt.kind("foo"), "file")
1640
def test_no_final_path(self):
1641
transform, root = self.get_transform()
1642
trans_id = transform.trans_id_file_id(b'foo')
1643
transform.create_file([b'bar'], trans_id)
1644
transform.cancel_creation(trans_id)
1647
def test_create_from_tree(self):
1648
tree1 = self.make_branch_and_tree('tree1')
1649
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1650
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1651
tree2 = self.make_branch_and_tree('tree2')
1652
tt = TreeTransform(tree2)
1653
foo_trans_id = tt.create_path('foo', tt.root)
1654
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1655
bar_trans_id = tt.create_path('bar', tt.root)
1656
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id=b'bar-id')
1658
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1659
self.assertFileEqual(b'baz', 'tree2/bar')
1661
def test_create_from_tree_bytes(self):
1662
"""Provided lines are used instead of tree content."""
1663
tree1 = self.make_branch_and_tree('tree1')
1664
self.build_tree_contents([('tree1/foo', b'bar'), ])
1665
tree1.add('foo', b'foo-id')
1666
tree2 = self.make_branch_and_tree('tree2')
1667
tt = TreeTransform(tree2)
1668
foo_trans_id = tt.create_path('foo', tt.root)
1669
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id',
1672
self.assertFileEqual(b'qux', 'tree2/foo')
1674
def test_create_from_tree_symlink(self):
1675
self.requireFeature(SymlinkFeature)
1676
tree1 = self.make_branch_and_tree('tree1')
1677
os.symlink('bar', 'tree1/foo')
1678
tree1.add('foo', b'foo-id')
1679
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1680
foo_trans_id = tt.create_path('foo', tt.root)
1681
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1683
self.assertEqual('bar', os.readlink('tree2/foo'))
1686
class TransformGroup(object):
1688
def __init__(self, dirname, root_id):
1691
self.wt = ControlDir.create_standalone_workingtree(dirname)
1692
self.wt.set_root_id(root_id)
1693
self.b = self.wt.branch
1694
self.tt = TreeTransform(self.wt)
1695
self.root = self.tt.trans_id_tree_path('')
1698
def conflict_text(tree, merge):
1699
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1700
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1703
class TestInventoryAltered(tests.TestCaseWithTransport):
1705
def test_inventory_altered_unchanged(self):
1706
tree = self.make_branch_and_tree('tree')
1707
self.build_tree(['tree/foo'])
1708
tree.add('foo', b'foo-id')
1709
with TransformPreview(tree) as tt:
1710
self.assertEqual([], tt._inventory_altered())
1712
def test_inventory_altered_changed_parent_id(self):
1713
tree = self.make_branch_and_tree('tree')
1714
self.build_tree(['tree/foo'])
1715
tree.add('foo', b'foo-id')
1716
with TransformPreview(tree) as tt:
1717
tt.unversion_file(tt.root)
1718
tt.version_file(b'new-id', tt.root)
1719
foo_trans_id = tt.trans_id_tree_path('foo')
1720
foo_tuple = ('foo', foo_trans_id)
1721
root_tuple = ('', tt.root)
1722
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1724
def test_inventory_altered_noop_changed_parent_id(self):
1725
tree = self.make_branch_and_tree('tree')
1726
self.build_tree(['tree/foo'])
1727
tree.add('foo', b'foo-id')
1728
with TransformPreview(tree) as tt:
1729
tt.unversion_file(tt.root)
1730
tt.version_file(tree.get_root_id(), tt.root)
1731
tt.trans_id_tree_path('foo')
1732
self.assertEqual([], tt._inventory_altered())
1735
class TestTransformMerge(TestCaseInTempDir):
1737
def test_text_merge(self):
1738
root_id = generate_ids.gen_root_id()
1739
base = TransformGroup("base", root_id)
1740
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1741
base.tt.new_file('b', base.root, [b'b1'], b'b')
1742
base.tt.new_file('c', base.root, [b'c'], b'c')
1743
base.tt.new_file('d', base.root, [b'd'], b'd')
1744
base.tt.new_file('e', base.root, [b'e'], b'e')
1745
base.tt.new_file('f', base.root, [b'f'], b'f')
1746
base.tt.new_directory('g', base.root, b'g')
1747
base.tt.new_directory('h', base.root, b'h')
1749
other = TransformGroup("other", root_id)
1750
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1751
other.tt.new_file('b', other.root, [b'b2'], b'b')
1752
other.tt.new_file('c', other.root, [b'c2'], b'c')
1753
other.tt.new_file('d', other.root, [b'd'], b'd')
1754
other.tt.new_file('e', other.root, [b'e2'], b'e')
1755
other.tt.new_file('f', other.root, [b'f'], b'f')
1756
other.tt.new_file('g', other.root, [b'g'], b'g')
1757
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1758
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
1760
this = TransformGroup("this", root_id)
1761
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1762
this.tt.new_file('b', this.root, [b'b'], b'b')
1763
this.tt.new_file('c', this.root, [b'c'], b'c')
1764
this.tt.new_file('d', this.root, [b'd2'], b'd')
1765
this.tt.new_file('e', this.root, [b'e2'], b'e')
1766
this.tt.new_file('f', this.root, [b'f'], b'f')
1767
this.tt.new_file('g', this.root, [b'g'], b'g')
1768
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1769
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
1771
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1774
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1775
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
1776
# three-way text conflict
1777
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1778
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
1780
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
1782
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
1783
# Ambigious clean merge
1784
self.assertEqual(this.wt.get_file(this.wt.id2path(b'e')).read(), b'e2')
1786
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1787
# Correct correct results when THIS == OTHER
1788
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
1789
# Text conflict when THIS & OTHER are text and BASE is dir
1790
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1791
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1792
self.assertEqual(this.wt.get_file('h.THIS').read(),
1794
self.assertEqual(this.wt.get_file('h.OTHER').read(),
1796
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1797
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1798
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1799
self.assertEqual(this.wt.get_file('i.THIS').read(),
1801
self.assertEqual(this.wt.get_file('i.OTHER').read(),
1803
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1804
modified = [b'a', b'b', b'c', b'h', b'i']
1805
merge_modified = this.wt.merge_modified()
1806
self.assertSubset(merge_modified, modified)
1807
self.assertEqual(len(merge_modified), len(modified))
1808
with open(this.wt.abspath(this.wt.id2path(b'a')), 'wb') as f:
1811
merge_modified = this.wt.merge_modified()
1812
self.assertSubset(merge_modified, modified)
1813
self.assertEqual(len(merge_modified), len(modified))
1817
def test_file_merge(self):
1818
self.requireFeature(SymlinkFeature)
1819
root_id = generate_ids.gen_root_id()
1820
base = TransformGroup("BASE", root_id)
1821
this = TransformGroup("THIS", root_id)
1822
other = TransformGroup("OTHER", root_id)
1823
for tg in this, base, other:
1824
tg.tt.new_directory('a', tg.root, b'a')
1825
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1826
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1827
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1828
targets = ((base, 'base-e', 'base-f', None, None),
1829
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1830
(other, 'other-e', None, 'other-g', 'other-h'))
1831
for tg, e_target, f_target, g_target, h_target in targets:
1832
for link, target in (('e', e_target), ('f', f_target),
1833
('g', g_target), ('h', h_target)):
1834
if target is not None:
1835
tg.tt.new_symlink(link, tg.root, target,
1836
link.encode('ascii'))
1838
for tg in this, base, other:
1840
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1841
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1842
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1843
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1844
for suffix in ('THIS', 'BASE', 'OTHER'):
1845
self.assertEqual(os.readlink(
1846
this.wt.abspath('d.' + suffix)), suffix)
1847
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1848
self.assertEqual(this.wt.id2path(b'd'), 'd.OTHER')
1849
self.assertEqual(this.wt.id2path(b'f'), 'f.THIS')
1850
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1851
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1852
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1853
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1854
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1855
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1856
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1857
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1858
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1859
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1861
def test_filename_merge(self):
1862
root_id = generate_ids.gen_root_id()
1863
base = TransformGroup("BASE", root_id)
1864
this = TransformGroup("THIS", root_id)
1865
other = TransformGroup("OTHER", root_id)
1866
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1867
for t in [base, this, other]]
1868
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1869
for t in [base, this, other]]
1870
base.tt.new_directory('c', base_a, b'c')
1871
this.tt.new_directory('c1', this_a, b'c')
1872
other.tt.new_directory('c', other_b, b'c')
1874
base.tt.new_directory('d', base_a, b'd')
1875
this.tt.new_directory('d1', this_b, b'd')
1876
other.tt.new_directory('d', other_a, b'd')
1878
base.tt.new_directory('e', base_a, b'e')
1879
this.tt.new_directory('e', this_a, b'e')
1880
other.tt.new_directory('e1', other_b, b'e')
1882
base.tt.new_directory('f', base_a, b'f')
1883
this.tt.new_directory('f1', this_b, b'f')
1884
other.tt.new_directory('f1', other_b, b'f')
1886
for tg in [this, base, other]:
1888
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1889
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1890
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1891
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1892
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
1894
def test_filename_merge_conflicts(self):
1895
root_id = generate_ids.gen_root_id()
1896
base = TransformGroup("BASE", root_id)
1897
this = TransformGroup("THIS", root_id)
1898
other = TransformGroup("OTHER", root_id)
1899
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1900
for t in [base, this, other]]
1901
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1902
for t in [base, this, other]]
1904
base.tt.new_file('g', base_a, [b'g'], b'g')
1905
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1907
base.tt.new_file('h', base_a, [b'h'], b'h')
1908
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1910
base.tt.new_file('i', base.root, [b'i'], b'i')
1911
other.tt.new_directory('i1', this_b, b'i')
1913
for tg in [this, base, other]:
1915
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1917
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
1918
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1919
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1920
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
1921
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1922
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1923
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1926
class TestBuildTree(tests.TestCaseWithTransport):
1928
def test_build_tree_with_symlinks(self):
1929
self.requireFeature(SymlinkFeature)
1931
a = ControlDir.create_standalone_workingtree('a')
1933
with open('a/foo/bar', 'wb') as f:
1934
f.write(b'contents')
1935
os.symlink('a/foo/bar', 'a/foo/baz')
1936
a.add(['foo', 'foo/bar', 'foo/baz'])
1937
a.commit('initial commit')
1938
b = ControlDir.create_standalone_workingtree('b')
1939
basis = a.basis_tree()
1941
self.addCleanup(basis.unlock)
1942
build_tree(basis, b)
1943
self.assertIs(os.path.isdir('b/foo'), True)
1944
with open('b/foo/bar', 'rb') as f:
1945
self.assertEqual(f.read(), b"contents")
1946
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1948
def test_build_with_references(self):
1949
tree = self.make_branch_and_tree('source',
1950
format='development-subtree')
1951
subtree = self.make_branch_and_tree('source/subtree',
1952
format='development-subtree')
1953
tree.add_reference(subtree)
1954
tree.commit('a revision')
1955
tree.branch.create_checkout('target')
1956
self.assertPathExists('target')
1957
self.assertPathExists('target/subtree')
1959
def test_file_conflict_handling(self):
1960
"""Ensure that when building trees, conflict handling is done"""
1961
source = self.make_branch_and_tree('source')
1962
target = self.make_branch_and_tree('target')
1963
self.build_tree(['source/file', 'target/file'])
1964
source.add('file', b'new-file')
1965
source.commit('added file')
1966
build_tree(source.basis_tree(), target)
1968
[DuplicateEntry('Moved existing file to', 'file.moved',
1969
'file', None, 'new-file')],
1971
target2 = self.make_branch_and_tree('target2')
1972
with open('target2/file', 'wb') as target_file, \
1973
open('source/file', 'rb') as source_file:
1974
target_file.write(source_file.read())
1975
build_tree(source.basis_tree(), target2)
1976
self.assertEqual([], target2.conflicts())
1978
def test_symlink_conflict_handling(self):
1979
"""Ensure that when building trees, conflict handling is done"""
1980
self.requireFeature(SymlinkFeature)
1981
source = self.make_branch_and_tree('source')
1982
os.symlink('foo', 'source/symlink')
1983
source.add('symlink', b'new-symlink')
1984
source.commit('added file')
1985
target = self.make_branch_and_tree('target')
1986
os.symlink('bar', 'target/symlink')
1987
build_tree(source.basis_tree(), target)
1989
[DuplicateEntry('Moved existing file to', 'symlink.moved',
1990
'symlink', None, 'new-symlink')],
1992
target = self.make_branch_and_tree('target2')
1993
os.symlink('foo', 'target2/symlink')
1994
build_tree(source.basis_tree(), target)
1995
self.assertEqual([], target.conflicts())
1997
def test_directory_conflict_handling(self):
1998
"""Ensure that when building trees, conflict handling is done"""
1999
source = self.make_branch_and_tree('source')
2000
target = self.make_branch_and_tree('target')
2001
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
2002
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
2003
source.commit('added file')
2004
build_tree(source.basis_tree(), target)
2005
self.assertEqual([], target.conflicts())
2006
self.assertPathExists('target/dir1/file')
2008
# Ensure contents are merged
2009
target = self.make_branch_and_tree('target2')
2010
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
2011
build_tree(source.basis_tree(), target)
2012
self.assertEqual([], target.conflicts())
2013
self.assertPathExists('target2/dir1/file2')
2014
self.assertPathExists('target2/dir1/file')
2016
# Ensure new contents are suppressed for existing branches
2017
target = self.make_branch_and_tree('target3')
2018
self.make_branch('target3/dir1')
2019
self.build_tree(['target3/dir1/file2'])
2020
build_tree(source.basis_tree(), target)
2021
self.assertPathDoesNotExist('target3/dir1/file')
2022
self.assertPathExists('target3/dir1/file2')
2023
self.assertPathExists('target3/dir1.diverted/file')
2025
[DuplicateEntry('Diverted to', 'dir1.diverted',
2026
'dir1', 'new-dir1', None)],
2029
target = self.make_branch_and_tree('target4')
2030
self.build_tree(['target4/dir1/'])
2031
self.make_branch('target4/dir1/file')
2032
build_tree(source.basis_tree(), target)
2033
self.assertPathExists('target4/dir1/file')
2034
self.assertEqual('directory', file_kind('target4/dir1/file'))
2035
self.assertPathExists('target4/dir1/file.diverted')
2037
[DuplicateEntry('Diverted to', 'dir1/file.diverted',
2038
'dir1/file', 'new-file', None)],
2041
def test_mixed_conflict_handling(self):
2042
"""Ensure that when building trees, conflict handling is done"""
2043
source = self.make_branch_and_tree('source')
2044
target = self.make_branch_and_tree('target')
2045
self.build_tree(['source/name', 'target/name/'])
2046
source.add('name', b'new-name')
2047
source.commit('added file')
2048
build_tree(source.basis_tree(), target)
2050
[DuplicateEntry('Moved existing file to',
2051
'name.moved', 'name', None, 'new-name')],
2054
def test_raises_in_populated(self):
2055
source = self.make_branch_and_tree('source')
2056
self.build_tree(['source/name'])
2058
source.commit('added name')
2059
target = self.make_branch_and_tree('target')
2060
self.build_tree(['target/name'])
2062
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2063
build_tree, source.basis_tree(), target)
2065
def test_build_tree_rename_count(self):
2066
source = self.make_branch_and_tree('source')
2067
self.build_tree(['source/file1', 'source/dir1/'])
2068
source.add(['file1', 'dir1'])
2069
source.commit('add1')
2070
target1 = self.make_branch_and_tree('target1')
2071
transform_result = build_tree(source.basis_tree(), target1)
2072
self.assertEqual(2, transform_result.rename_count)
2074
self.build_tree(['source/dir1/file2'])
2075
source.add(['dir1/file2'])
2076
source.commit('add3')
2077
target2 = self.make_branch_and_tree('target2')
2078
transform_result = build_tree(source.basis_tree(), target2)
2079
# children of non-root directories should not be renamed
2080
self.assertEqual(2, transform_result.rename_count)
2082
def create_ab_tree(self):
2083
"""Create a committed test tree with two files"""
2084
source = self.make_branch_and_tree('source')
2085
self.build_tree_contents([('source/file1', b'A')])
2086
self.build_tree_contents([('source/file2', b'B')])
2087
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2088
source.commit('commit files')
2090
self.addCleanup(source.unlock)
2093
def test_build_tree_accelerator_tree(self):
2094
source = self.create_ab_tree()
2095
self.build_tree_contents([('source/file2', b'C')])
2097
real_source_get_file = source.get_file
2101
return real_source_get_file(path)
2102
source.get_file = get_file
2103
target = self.make_branch_and_tree('target')
2104
revision_tree = source.basis_tree()
2105
revision_tree.lock_read()
2106
self.addCleanup(revision_tree.unlock)
2107
build_tree(revision_tree, target, source)
2108
self.assertEqual(['file1'], calls)
2110
self.addCleanup(target.unlock)
2111
self.assertEqual([], list(target.iter_changes(revision_tree)))
2113
def test_build_tree_accelerator_tree_observes_sha1(self):
2114
source = self.create_ab_tree()
2115
sha1 = osutils.sha_string(b'A')
2116
target = self.make_branch_and_tree('target')
2118
self.addCleanup(target.unlock)
2119
state = target.current_dirstate()
2120
state._cutoff_time = time.time() + 60
2121
build_tree(source.basis_tree(), target, source)
2122
entry = state._get_entry(0, path_utf8=b'file1')
2123
self.assertEqual(sha1, entry[1][0][1])
2125
def test_build_tree_accelerator_tree_missing_file(self):
2126
source = self.create_ab_tree()
2127
os.unlink('source/file1')
2128
source.remove(['file2'])
2129
target = self.make_branch_and_tree('target')
2130
revision_tree = source.basis_tree()
2131
revision_tree.lock_read()
2132
self.addCleanup(revision_tree.unlock)
2133
build_tree(revision_tree, target, source)
2135
self.addCleanup(target.unlock)
2136
self.assertEqual([], list(target.iter_changes(revision_tree)))
2138
def test_build_tree_accelerator_wrong_kind(self):
2139
self.requireFeature(SymlinkFeature)
2140
source = self.make_branch_and_tree('source')
2141
self.build_tree_contents([('source/file1', b'')])
2142
self.build_tree_contents([('source/file2', b'')])
2143
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2144
source.commit('commit files')
2145
os.unlink('source/file2')
2146
self.build_tree_contents([('source/file2/', b'C')])
2147
os.unlink('source/file1')
2148
os.symlink('file2', 'source/file1')
2150
real_source_get_file = source.get_file
2154
return real_source_get_file(path)
2155
source.get_file = get_file
2156
target = self.make_branch_and_tree('target')
2157
revision_tree = source.basis_tree()
2158
revision_tree.lock_read()
2159
self.addCleanup(revision_tree.unlock)
2160
build_tree(revision_tree, target, source)
2161
self.assertEqual([], calls)
2163
self.addCleanup(target.unlock)
2164
self.assertEqual([], list(target.iter_changes(revision_tree)))
2166
def test_build_tree_hardlink(self):
2167
self.requireFeature(HardlinkFeature)
2168
source = self.create_ab_tree()
2169
target = self.make_branch_and_tree('target')
2170
revision_tree = source.basis_tree()
2171
revision_tree.lock_read()
2172
self.addCleanup(revision_tree.unlock)
2173
build_tree(revision_tree, target, source, hardlink=True)
2175
self.addCleanup(target.unlock)
2176
self.assertEqual([], list(target.iter_changes(revision_tree)))
2177
source_stat = os.stat('source/file1')
2178
target_stat = os.stat('target/file1')
2179
self.assertEqual(source_stat, target_stat)
2181
# Explicitly disallowing hardlinks should prevent them.
2182
target2 = self.make_branch_and_tree('target2')
2183
build_tree(revision_tree, target2, source, hardlink=False)
2185
self.addCleanup(target2.unlock)
2186
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2187
source_stat = os.stat('source/file1')
2188
target2_stat = os.stat('target2/file1')
2189
self.assertNotEqual(source_stat, target2_stat)
2191
def test_build_tree_accelerator_tree_moved(self):
2192
source = self.make_branch_and_tree('source')
2193
self.build_tree_contents([('source/file1', b'A')])
2194
source.add(['file1'], [b'file1-id'])
2195
source.commit('commit files')
2196
source.rename_one('file1', 'file2')
2198
self.addCleanup(source.unlock)
2199
target = self.make_branch_and_tree('target')
2200
revision_tree = source.basis_tree()
2201
revision_tree.lock_read()
2202
self.addCleanup(revision_tree.unlock)
2203
build_tree(revision_tree, target, source)
2205
self.addCleanup(target.unlock)
2206
self.assertEqual([], list(target.iter_changes(revision_tree)))
2208
def test_build_tree_hardlinks_preserve_execute(self):
2209
self.requireFeature(HardlinkFeature)
2210
source = self.create_ab_tree()
2211
tt = TreeTransform(source)
2212
trans_id = tt.trans_id_tree_path('file1')
2213
tt.set_executability(True, trans_id)
2215
self.assertTrue(source.is_executable('file1'))
2216
target = self.make_branch_and_tree('target')
2217
revision_tree = source.basis_tree()
2218
revision_tree.lock_read()
2219
self.addCleanup(revision_tree.unlock)
2220
build_tree(revision_tree, target, source, hardlink=True)
2222
self.addCleanup(target.unlock)
2223
self.assertEqual([], list(target.iter_changes(revision_tree)))
2224
self.assertTrue(source.is_executable('file1'))
2226
def install_rot13_content_filter(self, pattern):
2228
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2229
# below, but that looks a bit... hard to read even if it's exactly
2231
original_registry = filters._reset_registry()
2233
def restore_registry():
2234
filters._reset_registry(original_registry)
2235
self.addCleanup(restore_registry)
2237
def rot13(chunks, context=None):
2239
codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
2240
for chunk in chunks]
2241
rot13filter = filters.ContentFilter(rot13, rot13)
2242
filters.filter_stacks_registry.register(
2243
'rot13', {'yes': [rot13filter]}.get)
2244
os.mkdir(self.test_home_dir + '/.bazaar')
2245
rules_filename = self.test_home_dir + '/.bazaar/rules'
2246
with open(rules_filename, 'wb') as f:
2247
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2249
def uninstall_rules():
2250
os.remove(rules_filename)
2252
self.addCleanup(uninstall_rules)
2255
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2256
"""build_tree will not hardlink files that have content filtering rules
2257
applied to them (but will still hardlink other files from the same tree
2260
self.requireFeature(HardlinkFeature)
2261
self.install_rot13_content_filter(b'file1')
2262
source = self.create_ab_tree()
2263
target = self.make_branch_and_tree('target')
2264
revision_tree = source.basis_tree()
2265
revision_tree.lock_read()
2266
self.addCleanup(revision_tree.unlock)
2267
build_tree(revision_tree, target, source, hardlink=True)
2269
self.addCleanup(target.unlock)
2270
self.assertEqual([], list(target.iter_changes(revision_tree)))
2271
source_stat = os.stat('source/file1')
2272
target_stat = os.stat('target/file1')
2273
self.assertNotEqual(source_stat, target_stat)
2274
source_stat = os.stat('source/file2')
2275
target_stat = os.stat('target/file2')
2276
self.assertEqualStat(source_stat, target_stat)
2278
def test_case_insensitive_build_tree_inventory(self):
2279
if (features.CaseInsensitiveFilesystemFeature.available()
2280
or features.CaseInsCasePresFilenameFeature.available()):
2281
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2282
source = self.make_branch_and_tree('source')
2283
self.build_tree(['source/file', 'source/FILE'])
2284
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2285
source.commit('added files')
2286
# Don't try this at home, kids!
2287
# Force the tree to report that it is case insensitive
2288
target = self.make_branch_and_tree('target')
2289
target.case_sensitive = False
2290
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2291
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2292
self.assertEqual('FILE', target.id2path(b'upper-id'))
2294
def test_build_tree_observes_sha(self):
2295
source = self.make_branch_and_tree('source')
2296
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2297
source.add(['file1', 'dir', 'dir/file2'],
2298
[b'file1-id', b'dir-id', b'file2-id'])
2299
source.commit('new files')
2300
target = self.make_branch_and_tree('target')
2302
self.addCleanup(target.unlock)
2303
# We make use of the fact that DirState caches its cutoff time. So we
2304
# set the 'safe' time to one minute in the future.
2305
state = target.current_dirstate()
2306
state._cutoff_time = time.time() + 60
2307
build_tree(source.basis_tree(), target)
2308
entry1_sha = osutils.sha_file_by_name('source/file1')
2309
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2310
# entry[1] is the state information, entry[1][0] is the state of the
2311
# working tree, entry[1][0][1] is the sha value for the current working
2313
entry1 = state._get_entry(0, path_utf8=b'file1')
2314
self.assertEqual(entry1_sha, entry1[1][0][1])
2315
# The 'size' field must also be set.
2316
self.assertEqual(25, entry1[1][0][2])
2317
entry1_state = entry1[1][0]
2318
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2319
self.assertEqual(entry2_sha, entry2[1][0][1])
2320
self.assertEqual(29, entry2[1][0][2])
2321
entry2_state = entry2[1][0]
2322
# Now, make sure that we don't have to re-read the content. The
2323
# packed_stat should match exactly.
2324
self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
2325
self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
2326
self.assertEqual(entry1_state, entry1[1][0])
2327
self.assertEqual(entry2_state, entry2[1][0])
2330
class TestCommitTransform(tests.TestCaseWithTransport):
2332
def get_branch(self):
2333
tree = self.make_branch_and_tree('tree')
2335
self.addCleanup(tree.unlock)
2336
tree.commit('empty commit')
2339
def get_branch_and_transform(self):
2340
branch = self.get_branch()
2341
tt = TransformPreview(branch.basis_tree())
2342
self.addCleanup(tt.finalize)
2345
def test_commit_wrong_basis(self):
2346
branch = self.get_branch()
2347
basis = branch.repository.revision_tree(
2348
_mod_revision.NULL_REVISION)
2349
tt = TransformPreview(basis)
2350
self.addCleanup(tt.finalize)
2351
e = self.assertRaises(ValueError, tt.commit, branch, '')
2352
self.assertEqual('TreeTransform not based on branch basis: null:',
2355
def test_empy_commit(self):
2356
branch, tt = self.get_branch_and_transform()
2357
rev = tt.commit(branch, 'my message')
2358
self.assertEqual(2, branch.revno())
2359
repo = branch.repository
2360
self.assertEqual('my message', repo.get_revision(rev).message)
2362
def test_merge_parents(self):
2363
branch, tt = self.get_branch_and_transform()
2364
tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2365
self.assertEqual([b'rev1b', b'rev1c'],
2366
branch.basis_tree().get_parent_ids()[1:])
2368
def test_first_commit(self):
2369
branch = self.make_branch('branch')
2371
self.addCleanup(branch.unlock)
2372
tt = TransformPreview(branch.basis_tree())
2373
self.addCleanup(tt.finalize)
2374
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2375
tt.commit(branch, 'my message')
2376
self.assertEqual([], branch.basis_tree().get_parent_ids())
2377
self.assertNotEqual(_mod_revision.NULL_REVISION,
2378
branch.last_revision())
2380
def test_first_commit_with_merge_parents(self):
2381
branch = self.make_branch('branch')
2383
self.addCleanup(branch.unlock)
2384
tt = TransformPreview(branch.basis_tree())
2385
self.addCleanup(tt.finalize)
2386
e = self.assertRaises(ValueError, tt.commit, branch,
2387
'my message', [b'rev1b-id'])
2388
self.assertEqual('Cannot supply merge parents for first commit.',
2390
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2392
def test_add_files(self):
2393
branch, tt = self.get_branch_and_transform()
2394
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2395
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2396
if SymlinkFeature.available():
2397
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2398
tt.commit(branch, 'message')
2399
tree = branch.basis_tree()
2400
self.assertEqual('file', tree.id2path(b'file-id'))
2401
self.assertEqual(b'contents', tree.get_file_text('file'))
2402
self.assertEqual('dir', tree.id2path(b'dir-id'))
2403
if SymlinkFeature.available():
2404
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2405
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2407
def test_add_unversioned(self):
2408
branch, tt = self.get_branch_and_transform()
2409
tt.new_file('file', tt.root, [b'contents'])
2410
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2411
'message', strict=True)
2413
def test_modify_strict(self):
2414
branch, tt = self.get_branch_and_transform()
2415
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2416
tt.commit(branch, 'message', strict=True)
2417
tt = TransformPreview(branch.basis_tree())
2418
self.addCleanup(tt.finalize)
2419
trans_id = tt.trans_id_file_id(b'file-id')
2420
tt.delete_contents(trans_id)
2421
tt.create_file([b'contents'], trans_id)
2422
tt.commit(branch, 'message', strict=True)
2424
def test_commit_malformed(self):
2425
"""Committing a malformed transform should raise an exception.
2427
In this case, we are adding a file without adding its parent.
2429
branch, tt = self.get_branch_and_transform()
2430
parent_id = tt.trans_id_file_id(b'parent-id')
2431
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2432
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2435
def test_commit_rich_revision_data(self):
2436
branch, tt = self.get_branch_and_transform()
2437
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2438
committer='me <me@example.com>',
2439
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2440
authors=['Author1 <author1@example.com>',
2441
'Author2 <author2@example.com>',
2443
self.assertEqual(b'revid-1', rev_id)
2444
revision = branch.repository.get_revision(rev_id)
2445
self.assertEqual(1, revision.timestamp)
2446
self.assertEqual(43201, revision.timezone)
2447
self.assertEqual('me <me@example.com>', revision.committer)
2448
self.assertEqual(['Author1 <author1@example.com>',
2449
'Author2 <author2@example.com>'],
2450
revision.get_apparent_authors())
2451
del revision.properties['authors']
2452
self.assertEqual({'foo': 'bar',
2453
'branch-nick': 'tree'},
2454
revision.properties)
2456
def test_no_explicit_revprops(self):
2457
branch, tt = self.get_branch_and_transform()
2458
rev_id = tt.commit(branch, 'message', authors=[
2459
'Author1 <author1@example.com>',
2460
'Author2 <author2@example.com>', ])
2461
revision = branch.repository.get_revision(rev_id)
2462
self.assertEqual(['Author1 <author1@example.com>',
2463
'Author2 <author2@example.com>'],
2464
revision.get_apparent_authors())
2465
self.assertEqual('tree', revision.properties['branch-nick'])
2468
class TestFileMover(tests.TestCaseWithTransport):
2470
def test_file_mover(self):
2471
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2472
mover = _FileMover()
2473
mover.rename('a', 'q')
2474
self.assertPathExists('q')
2475
self.assertPathDoesNotExist('a')
2476
self.assertPathExists('q/b')
2477
self.assertPathExists('c')
2478
self.assertPathExists('c/d')
2480
def test_pre_delete_rollback(self):
2481
self.build_tree(['a/'])
2482
mover = _FileMover()
2483
mover.pre_delete('a', 'q')
2484
self.assertPathExists('q')
2485
self.assertPathDoesNotExist('a')
2487
self.assertPathDoesNotExist('q')
2488
self.assertPathExists('a')
2490
def test_apply_deletions(self):
2491
self.build_tree(['a/', 'b/'])
2492
mover = _FileMover()
2493
mover.pre_delete('a', 'q')
2494
mover.pre_delete('b', 'r')
2495
self.assertPathExists('q')
2496
self.assertPathExists('r')
2497
self.assertPathDoesNotExist('a')
2498
self.assertPathDoesNotExist('b')
2499
mover.apply_deletions()
2500
self.assertPathDoesNotExist('q')
2501
self.assertPathDoesNotExist('r')
2502
self.assertPathDoesNotExist('a')
2503
self.assertPathDoesNotExist('b')
2505
def test_file_mover_rollback(self):
2506
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2507
mover = _FileMover()
2508
mover.rename('c/d', 'c/f')
2509
mover.rename('c/e', 'c/d')
2511
mover.rename('a', 'c')
2512
except errors.FileExists:
2514
self.assertPathExists('a')
2515
self.assertPathExists('c/d')
2518
class Bogus(Exception):
2522
class TestTransformRollback(tests.TestCaseWithTransport):
2524
class ExceptionFileMover(_FileMover):
2526
def __init__(self, bad_source=None, bad_target=None):
2527
_FileMover.__init__(self)
2528
self.bad_source = bad_source
2529
self.bad_target = bad_target
2531
def rename(self, source, target):
2532
if (self.bad_source is not None and
2533
source.endswith(self.bad_source)):
2535
elif (self.bad_target is not None and
2536
target.endswith(self.bad_target)):
2539
_FileMover.rename(self, source, target)
2541
def test_rollback_rename(self):
2542
tree = self.make_branch_and_tree('.')
2543
self.build_tree(['a/', 'a/b'])
2544
tt = TreeTransform(tree)
2545
self.addCleanup(tt.finalize)
2546
a_id = tt.trans_id_tree_path('a')
2547
tt.adjust_path('c', tt.root, a_id)
2548
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2549
self.assertRaises(Bogus, tt.apply,
2550
_mover=self.ExceptionFileMover(bad_source='a'))
2551
self.assertPathExists('a')
2552
self.assertPathExists('a/b')
2554
self.assertPathExists('c')
2555
self.assertPathExists('c/d')
2557
def test_rollback_rename_into_place(self):
2558
tree = self.make_branch_and_tree('.')
2559
self.build_tree(['a/', 'a/b'])
2560
tt = TreeTransform(tree)
2561
self.addCleanup(tt.finalize)
2562
a_id = tt.trans_id_tree_path('a')
2563
tt.adjust_path('c', tt.root, a_id)
2564
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2565
self.assertRaises(Bogus, tt.apply,
2566
_mover=self.ExceptionFileMover(bad_target='c/d'))
2567
self.assertPathExists('a')
2568
self.assertPathExists('a/b')
2570
self.assertPathExists('c')
2571
self.assertPathExists('c/d')
2573
def test_rollback_deletion(self):
2574
tree = self.make_branch_and_tree('.')
2575
self.build_tree(['a/', 'a/b'])
2576
tt = TreeTransform(tree)
2577
self.addCleanup(tt.finalize)
2578
a_id = tt.trans_id_tree_path('a')
2579
tt.delete_contents(a_id)
2580
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2581
self.assertRaises(Bogus, tt.apply,
2582
_mover=self.ExceptionFileMover(bad_target='d'))
2583
self.assertPathExists('a')
2584
self.assertPathExists('a/b')
2587
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2588
"""Ensure treetransform creation errors can be safely cleaned up after"""
2590
def _override_globals_in_method(self, instance, method_name, globals):
2591
"""Replace method on instance with one with updated globals"""
2593
func = getattr(instance, method_name).__func__
2594
new_globals = dict(func.__globals__)
2595
new_globals.update(globals)
2596
new_func = types.FunctionType(func.__code__, new_globals,
2597
func.__name__, func.__defaults__)
2599
setattr(instance, method_name,
2600
types.MethodType(new_func, instance))
2602
setattr(instance, method_name,
2603
types.MethodType(new_func, instance, instance.__class__))
2604
self.addCleanup(delattr, instance, method_name)
2607
def _fake_open_raises_before(name, mode):
2608
"""Like open() but raises before doing anything"""
2612
def _fake_open_raises_after(name, mode):
2613
"""Like open() but raises after creating file without returning"""
2614
open(name, mode).close()
2617
def create_transform_and_root_trans_id(self):
2618
"""Setup a transform creating a file in limbo"""
2619
tree = self.make_branch_and_tree('.')
2620
tt = TreeTransform(tree)
2621
return tt, tt.create_path("a", tt.root)
2623
def create_transform_and_subdir_trans_id(self):
2624
"""Setup a transform creating a directory containing a file in limbo"""
2625
tree = self.make_branch_and_tree('.')
2626
tt = TreeTransform(tree)
2627
d_trans_id = tt.create_path("d", tt.root)
2628
tt.create_directory(d_trans_id)
2629
f_trans_id = tt.create_path("a", d_trans_id)
2630
tt.adjust_path("a", d_trans_id, f_trans_id)
2631
return tt, f_trans_id
2633
def test_root_create_file_open_raises_before_creation(self):
2634
tt, trans_id = self.create_transform_and_root_trans_id()
2635
self._override_globals_in_method(
2636
tt, "create_file", {"open": self._fake_open_raises_before})
2637
self.assertRaises(RuntimeError, tt.create_file,
2638
[b"contents"], trans_id)
2639
path = tt._limbo_name(trans_id)
2640
self.assertPathDoesNotExist(path)
2642
self.assertPathDoesNotExist(tt._limbodir)
2644
def test_root_create_file_open_raises_after_creation(self):
2645
tt, trans_id = self.create_transform_and_root_trans_id()
2646
self._override_globals_in_method(
2647
tt, "create_file", {"open": self._fake_open_raises_after})
2648
self.assertRaises(RuntimeError, tt.create_file,
2649
[b"contents"], trans_id)
2650
path = tt._limbo_name(trans_id)
2651
self.assertPathExists(path)
2653
self.assertPathDoesNotExist(path)
2654
self.assertPathDoesNotExist(tt._limbodir)
2656
def test_subdir_create_file_open_raises_before_creation(self):
2657
tt, trans_id = self.create_transform_and_subdir_trans_id()
2658
self._override_globals_in_method(
2659
tt, "create_file", {"open": self._fake_open_raises_before})
2660
self.assertRaises(RuntimeError, tt.create_file,
2661
[b"contents"], trans_id)
2662
path = tt._limbo_name(trans_id)
2663
self.assertPathDoesNotExist(path)
2665
self.assertPathDoesNotExist(tt._limbodir)
2667
def test_subdir_create_file_open_raises_after_creation(self):
2668
tt, trans_id = self.create_transform_and_subdir_trans_id()
2669
self._override_globals_in_method(
2670
tt, "create_file", {"open": self._fake_open_raises_after})
2671
self.assertRaises(RuntimeError, tt.create_file,
2672
[b"contents"], trans_id)
2673
path = tt._limbo_name(trans_id)
2674
self.assertPathExists(path)
2676
self.assertPathDoesNotExist(path)
2677
self.assertPathDoesNotExist(tt._limbodir)
2679
def test_rename_in_limbo_rename_raises_after_rename(self):
2680
tt, trans_id = self.create_transform_and_root_trans_id()
2681
parent1 = tt.new_directory('parent1', tt.root)
2682
child1 = tt.new_file('child1', parent1, [b'contents'])
2683
parent2 = tt.new_directory('parent2', tt.root)
2685
class FakeOSModule(object):
2686
def rename(self, old, new):
2689
self._override_globals_in_method(tt, "_rename_in_limbo",
2690
{"os": FakeOSModule()})
2692
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2693
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2694
self.assertPathExists(path)
2696
self.assertPathDoesNotExist(path)
2697
self.assertPathDoesNotExist(tt._limbodir)
2699
def test_rename_in_limbo_rename_raises_before_rename(self):
2700
tt, trans_id = self.create_transform_and_root_trans_id()
2701
parent1 = tt.new_directory('parent1', tt.root)
2702
child1 = tt.new_file('child1', parent1, [b'contents'])
2703
parent2 = tt.new_directory('parent2', tt.root)
2705
class FakeOSModule(object):
2706
def rename(self, old, new):
2708
self._override_globals_in_method(tt, "_rename_in_limbo",
2709
{"os": FakeOSModule()})
2711
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2712
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2713
self.assertPathExists(path)
2715
self.assertPathDoesNotExist(path)
2716
self.assertPathDoesNotExist(tt._limbodir)
2719
class TestTransformMissingParent(tests.TestCaseWithTransport):
2721
def make_tt_with_versioned_dir(self):
2722
wt = self.make_branch_and_tree('.')
2723
self.build_tree(['dir/', ])
2724
wt.add(['dir'], [b'dir-id'])
2725
wt.commit('Create dir')
2726
tt = TreeTransform(wt)
2727
self.addCleanup(tt.finalize)
2730
def test_resolve_create_parent_for_versioned_file(self):
2731
wt, tt = self.make_tt_with_versioned_dir()
2732
dir_tid = tt.trans_id_tree_path('dir')
2733
tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2734
tt.delete_contents(dir_tid)
2735
tt.unversion_file(dir_tid)
2736
conflicts = resolve_conflicts(tt)
2737
# one conflict for the missing directory, one for the unversioned
2739
self.assertLength(2, conflicts)
2741
def test_non_versioned_file_create_conflict(self):
2742
wt, tt = self.make_tt_with_versioned_dir()
2743
dir_tid = tt.trans_id_tree_path('dir')
2744
tt.new_file('file', dir_tid, [b'Contents'])
2745
tt.delete_contents(dir_tid)
2746
tt.unversion_file(dir_tid)
2747
conflicts = resolve_conflicts(tt)
2748
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2749
self.assertLength(1, conflicts)
2750
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2754
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2755
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2757
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2758
('', ''), ('directory', 'directory'), (False, False))
2761
class TestTransformPreview(tests.TestCaseWithTransport):
2763
def create_tree(self):
2764
tree = self.make_branch_and_tree('.')
2765
self.build_tree_contents([('a', b'content 1')])
2766
tree.set_root_id(b'TREE_ROOT')
2767
tree.add('a', b'a-id')
2768
tree.commit('rev1', rev_id=b'rev1')
2769
return tree.branch.repository.revision_tree(b'rev1')
2771
def get_empty_preview(self):
2772
repository = self.make_repository('repo')
2773
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2774
preview = TransformPreview(tree)
2775
self.addCleanup(preview.finalize)
2778
def test_transform_preview(self):
2779
revision_tree = self.create_tree()
2780
preview = TransformPreview(revision_tree)
2781
self.addCleanup(preview.finalize)
2783
def test_transform_preview_tree(self):
2784
revision_tree = self.create_tree()
2785
preview = TransformPreview(revision_tree)
2786
self.addCleanup(preview.finalize)
2787
preview.get_preview_tree()
2789
def test_transform_new_file(self):
2790
revision_tree = self.create_tree()
2791
preview = TransformPreview(revision_tree)
2792
self.addCleanup(preview.finalize)
2793
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2794
preview_tree = preview.get_preview_tree()
2795
self.assertEqual(preview_tree.kind('file2'), 'file')
2796
with preview_tree.get_file('file2') as f:
2797
self.assertEqual(f.read(), b'content B\n')
2799
def test_diff_preview_tree(self):
2800
revision_tree = self.create_tree()
2801
preview = TransformPreview(revision_tree)
2802
self.addCleanup(preview.finalize)
2803
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2804
preview_tree = preview.get_preview_tree()
2806
show_diff_trees(revision_tree, preview_tree, out)
2807
lines = out.getvalue().splitlines()
2808
self.assertEqual(lines[0], b"=== added file 'file2'")
2809
# 3 lines of diff administrivia
2810
self.assertEqual(lines[4], b"+content B")
2812
def test_transform_conflicts(self):
2813
revision_tree = self.create_tree()
2814
preview = TransformPreview(revision_tree)
2815
self.addCleanup(preview.finalize)
2816
preview.new_file('a', preview.root, [b'content 2'])
2817
resolve_conflicts(preview)
2818
trans_id = preview.trans_id_file_id(b'a-id')
2819
self.assertEqual('a.moved', preview.final_name(trans_id))
2821
def get_tree_and_preview_tree(self):
2822
revision_tree = self.create_tree()
2823
preview = TransformPreview(revision_tree)
2824
self.addCleanup(preview.finalize)
2825
a_trans_id = preview.trans_id_file_id(b'a-id')
2826
preview.delete_contents(a_trans_id)
2827
preview.create_file([b'b content'], a_trans_id)
2828
preview_tree = preview.get_preview_tree()
2829
return revision_tree, preview_tree
2831
def test_iter_changes(self):
2832
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2833
root = revision_tree.get_root_id()
2834
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2835
(root, root), ('a', 'a'), ('file', 'file'),
2837
list(preview_tree.iter_changes(revision_tree)))
2839
def test_include_unchanged_succeeds(self):
2840
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2841
changes = preview_tree.iter_changes(revision_tree,
2842
include_unchanged=True)
2843
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2845
def test_specific_files(self):
2846
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2847
changes = preview_tree.iter_changes(revision_tree,
2848
specific_files=[''])
2849
self.assertEqual([A_ENTRY], list(changes))
2851
def test_want_unversioned(self):
2852
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2853
changes = preview_tree.iter_changes(revision_tree,
2854
want_unversioned=True)
2855
self.assertEqual([A_ENTRY], list(changes))
2857
def test_ignore_extra_trees_no_specific_files(self):
2858
# extra_trees is harmless without specific_files, so we'll silently
2859
# accept it, even though we won't use it.
2860
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2861
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2863
def test_ignore_require_versioned_no_specific_files(self):
2864
# require_versioned is meaningless without specific_files.
2865
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2866
preview_tree.iter_changes(revision_tree, require_versioned=False)
2868
def test_ignore_pb(self):
2869
# pb could be supported, but TT.iter_changes doesn't support it.
2870
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2871
preview_tree.iter_changes(revision_tree)
2873
def test_kind(self):
2874
revision_tree = self.create_tree()
2875
preview = TransformPreview(revision_tree)
2876
self.addCleanup(preview.finalize)
2877
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2878
preview.new_directory('directory', preview.root, b'dir-id')
2879
preview_tree = preview.get_preview_tree()
2880
self.assertEqual('file', preview_tree.kind('file'))
2881
self.assertEqual('directory', preview_tree.kind('directory'))
2883
def test_get_file_mtime(self):
2884
preview = self.get_empty_preview()
2885
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2887
limbo_path = preview._limbo_name(file_trans_id)
2888
preview_tree = preview.get_preview_tree()
2889
self.assertEqual(os.stat(limbo_path).st_mtime,
2890
preview_tree.get_file_mtime('file'))
2892
def test_get_file_mtime_renamed(self):
2893
work_tree = self.make_branch_and_tree('tree')
2894
self.build_tree(['tree/file'])
2895
work_tree.add('file', b'file-id')
2896
preview = TransformPreview(work_tree)
2897
self.addCleanup(preview.finalize)
2898
file_trans_id = preview.trans_id_tree_path('file')
2899
preview.adjust_path('renamed', preview.root, file_trans_id)
2900
preview_tree = preview.get_preview_tree()
2901
preview_mtime = preview_tree.get_file_mtime('renamed')
2902
work_mtime = work_tree.get_file_mtime('file')
2904
def test_get_file_size(self):
2905
work_tree = self.make_branch_and_tree('tree')
2906
self.build_tree_contents([('tree/old', b'old')])
2907
work_tree.add('old', b'old-id')
2908
preview = TransformPreview(work_tree)
2909
self.addCleanup(preview.finalize)
2910
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2912
tree = preview.get_preview_tree()
2913
self.assertEqual(len('old'), tree.get_file_size('old'))
2914
self.assertEqual(len('contents'), tree.get_file_size('name'))
2916
def test_get_file(self):
2917
preview = self.get_empty_preview()
2918
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2919
preview_tree = preview.get_preview_tree()
2920
with preview_tree.get_file('file') as tree_file:
2921
self.assertEqual(b'contents', tree_file.read())
2923
def test_get_symlink_target(self):
2924
self.requireFeature(SymlinkFeature)
2925
preview = self.get_empty_preview()
2926
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2927
preview_tree = preview.get_preview_tree()
2928
self.assertEqual('target',
2929
preview_tree.get_symlink_target('symlink'))
2931
def test_all_file_ids(self):
2932
tree = self.make_branch_and_tree('tree')
2933
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2934
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2935
preview = TransformPreview(tree)
2936
self.addCleanup(preview.finalize)
2937
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2938
c_trans_id = preview.trans_id_file_id(b'c-id')
2939
preview.unversion_file(c_trans_id)
2940
preview.version_file(b'c-id', c_trans_id)
2941
preview_tree = preview.get_preview_tree()
2942
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2943
preview_tree.all_file_ids())
2945
def test_path2id_deleted_unchanged(self):
2946
tree = self.make_branch_and_tree('tree')
2947
self.build_tree(['tree/unchanged', 'tree/deleted'])
2948
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2949
preview = TransformPreview(tree)
2950
self.addCleanup(preview.finalize)
2951
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2952
preview_tree = preview.get_preview_tree()
2953
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2954
self.assertFalse(preview_tree.is_versioned('deleted'))
2956
def test_path2id_created(self):
2957
tree = self.make_branch_and_tree('tree')
2958
self.build_tree(['tree/unchanged'])
2959
tree.add(['unchanged'], [b'unchanged-id'])
2960
preview = TransformPreview(tree)
2961
self.addCleanup(preview.finalize)
2962
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2963
[b'contents'], b'new-id')
2964
preview_tree = preview.get_preview_tree()
2965
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
2967
def test_path2id_moved(self):
2968
tree = self.make_branch_and_tree('tree')
2969
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2970
tree.add(['old_parent', 'old_parent/child'],
2971
[b'old_parent-id', b'child-id'])
2972
preview = TransformPreview(tree)
2973
self.addCleanup(preview.finalize)
2974
new_parent = preview.new_directory('new_parent', preview.root,
2976
preview.adjust_path('child', new_parent,
2977
preview.trans_id_file_id(b'child-id'))
2978
preview_tree = preview.get_preview_tree()
2979
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
2980
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
2982
def test_path2id_renamed_parent(self):
2983
tree = self.make_branch_and_tree('tree')
2984
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2985
tree.add(['old_name', 'old_name/child'],
2986
[b'parent-id', b'child-id'])
2987
preview = TransformPreview(tree)
2988
self.addCleanup(preview.finalize)
2989
preview.adjust_path('new_name', preview.root,
2990
preview.trans_id_file_id(b'parent-id'))
2991
preview_tree = preview.get_preview_tree()
2992
self.assertFalse(preview_tree.is_versioned('old_name/child'))
2993
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
2995
def assertMatchingIterEntries(self, tt, specific_files=None):
2996
preview_tree = tt.get_preview_tree()
2997
preview_result = list(preview_tree.iter_entries_by_dir(
2998
specific_files=specific_files))
3001
actual_result = list(tree.iter_entries_by_dir(
3002
specific_files=specific_files))
3003
self.assertEqual(actual_result, preview_result)
3005
def test_iter_entries_by_dir_new(self):
3006
tree = self.make_branch_and_tree('tree')
3007
tt = TreeTransform(tree)
3008
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3009
self.assertMatchingIterEntries(tt)
3011
def test_iter_entries_by_dir_deleted(self):
3012
tree = self.make_branch_and_tree('tree')
3013
self.build_tree(['tree/deleted'])
3014
tree.add('deleted', b'deleted-id')
3015
tt = TreeTransform(tree)
3016
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3017
self.assertMatchingIterEntries(tt)
3019
def test_iter_entries_by_dir_unversioned(self):
3020
tree = self.make_branch_and_tree('tree')
3021
self.build_tree(['tree/removed'])
3022
tree.add('removed', b'removed-id')
3023
tt = TreeTransform(tree)
3024
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3025
self.assertMatchingIterEntries(tt)
3027
def test_iter_entries_by_dir_moved(self):
3028
tree = self.make_branch_and_tree('tree')
3029
self.build_tree(['tree/moved', 'tree/new_parent/'])
3030
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3031
tt = TreeTransform(tree)
3032
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3033
tt.trans_id_file_id(b'moved-id'))
3034
self.assertMatchingIterEntries(tt)
3036
def test_iter_entries_by_dir_specific_files(self):
3037
tree = self.make_branch_and_tree('tree')
3038
tree.set_root_id(b'tree-root-id')
3039
self.build_tree(['tree/parent/', 'tree/parent/child'])
3040
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3041
tt = TreeTransform(tree)
3042
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3044
def test_symlink_content_summary(self):
3045
self.requireFeature(SymlinkFeature)
3046
preview = self.get_empty_preview()
3047
preview.new_symlink('path', preview.root, 'target', b'path-id')
3048
summary = preview.get_preview_tree().path_content_summary('path')
3049
self.assertEqual(('symlink', None, None, 'target'), summary)
3051
def test_missing_content_summary(self):
3052
preview = self.get_empty_preview()
3053
summary = preview.get_preview_tree().path_content_summary('path')
3054
self.assertEqual(('missing', None, None, None), summary)
3056
def test_deleted_content_summary(self):
3057
tree = self.make_branch_and_tree('tree')
3058
self.build_tree(['tree/path/'])
3060
preview = TransformPreview(tree)
3061
self.addCleanup(preview.finalize)
3062
preview.delete_contents(preview.trans_id_tree_path('path'))
3063
summary = preview.get_preview_tree().path_content_summary('path')
3064
self.assertEqual(('missing', None, None, None), summary)
3066
def test_file_content_summary_executable(self):
3067
preview = self.get_empty_preview()
3068
path_id = preview.new_file('path', preview.root, [
3069
b'contents'], b'path-id')
3070
preview.set_executability(True, path_id)
3071
summary = preview.get_preview_tree().path_content_summary('path')
3072
self.assertEqual(4, len(summary))
3073
self.assertEqual('file', summary[0])
3074
# size must be known
3075
self.assertEqual(len('contents'), summary[1])
3077
self.assertEqual(True, summary[2])
3078
# will not have hash (not cheap to determine)
3079
self.assertIs(None, summary[3])
3081
def test_change_executability(self):
3082
tree = self.make_branch_and_tree('tree')
3083
self.build_tree(['tree/path'])
3085
preview = TransformPreview(tree)
3086
self.addCleanup(preview.finalize)
3087
path_id = preview.trans_id_tree_path('path')
3088
preview.set_executability(True, path_id)
3089
summary = preview.get_preview_tree().path_content_summary('path')
3090
self.assertEqual(True, summary[2])
3092
def test_file_content_summary_non_exec(self):
3093
preview = self.get_empty_preview()
3094
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3095
summary = preview.get_preview_tree().path_content_summary('path')
3096
self.assertEqual(4, len(summary))
3097
self.assertEqual('file', summary[0])
3098
# size must be known
3099
self.assertEqual(len('contents'), summary[1])
3101
self.assertEqual(False, summary[2])
3102
# will not have hash (not cheap to determine)
3103
self.assertIs(None, summary[3])
3105
def test_dir_content_summary(self):
3106
preview = self.get_empty_preview()
3107
preview.new_directory('path', preview.root, b'path-id')
3108
summary = preview.get_preview_tree().path_content_summary('path')
3109
self.assertEqual(('directory', None, None, None), summary)
3111
def test_tree_content_summary(self):
3112
preview = self.get_empty_preview()
3113
path = preview.new_directory('path', preview.root, b'path-id')
3114
preview.set_tree_reference(b'rev-1', path)
3115
summary = preview.get_preview_tree().path_content_summary('path')
3116
self.assertEqual(4, len(summary))
3117
self.assertEqual('tree-reference', summary[0])
3119
def test_annotate(self):
3120
tree = self.make_branch_and_tree('tree')
3121
self.build_tree_contents([('tree/file', b'a\n')])
3122
tree.add('file', b'file-id')
3123
tree.commit('a', rev_id=b'one')
3124
self.build_tree_contents([('tree/file', b'a\nb\n')])
3125
preview = TransformPreview(tree)
3126
self.addCleanup(preview.finalize)
3127
file_trans_id = preview.trans_id_file_id(b'file-id')
3128
preview.delete_contents(file_trans_id)
3129
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3130
preview_tree = preview.get_preview_tree()
3136
annotation = preview_tree.annotate_iter(
3137
'file', default_revision=b'me:')
3138
self.assertEqual(expected, annotation)
3140
def test_annotate_missing(self):
3141
preview = self.get_empty_preview()
3142
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3143
preview_tree = preview.get_preview_tree()
3149
annotation = preview_tree.annotate_iter(
3150
'file', default_revision=b'me:')
3151
self.assertEqual(expected, annotation)
3153
def test_annotate_rename(self):
3154
tree = self.make_branch_and_tree('tree')
3155
self.build_tree_contents([('tree/file', b'a\n')])
3156
tree.add('file', b'file-id')
3157
tree.commit('a', rev_id=b'one')
3158
preview = TransformPreview(tree)
3159
self.addCleanup(preview.finalize)
3160
file_trans_id = preview.trans_id_file_id(b'file-id')
3161
preview.adjust_path('newname', preview.root, file_trans_id)
3162
preview_tree = preview.get_preview_tree()
3166
annotation = preview_tree.annotate_iter(
3167
'file', default_revision=b'me:')
3168
self.assertEqual(expected, annotation)
3170
def test_annotate_deleted(self):
3171
tree = self.make_branch_and_tree('tree')
3172
self.build_tree_contents([('tree/file', b'a\n')])
3173
tree.add('file', b'file-id')
3174
tree.commit('a', rev_id=b'one')
3175
self.build_tree_contents([('tree/file', b'a\nb\n')])
3176
preview = TransformPreview(tree)
3177
self.addCleanup(preview.finalize)
3178
file_trans_id = preview.trans_id_file_id(b'file-id')
3179
preview.delete_contents(file_trans_id)
3180
preview_tree = preview.get_preview_tree()
3181
annotation = preview_tree.annotate_iter(
3182
'file', default_revision=b'me:')
3183
self.assertIs(None, annotation)
3185
def test_stored_kind(self):
3186
preview = self.get_empty_preview()
3187
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3188
preview_tree = preview.get_preview_tree()
3189
self.assertEqual('file', preview_tree.stored_kind('file'))
3191
def test_is_executable(self):
3192
preview = self.get_empty_preview()
3193
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3194
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3195
preview_tree = preview.get_preview_tree()
3196
self.assertEqual(True, preview_tree.is_executable('file'))
3198
def test_get_set_parent_ids(self):
3199
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3200
self.assertEqual([], preview_tree.get_parent_ids())
3201
preview_tree.set_parent_ids([b'rev-1'])
3202
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3204
def test_plan_file_merge(self):
3205
work_a = self.make_branch_and_tree('wta')
3206
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3207
work_a.add('file', b'file-id')
3208
base_id = work_a.commit('base version')
3209
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3210
preview = TransformPreview(work_a)
3211
self.addCleanup(preview.finalize)
3212
trans_id = preview.trans_id_file_id(b'file-id')
3213
preview.delete_contents(trans_id)
3214
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3215
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3216
tree_a = preview.get_preview_tree()
3217
tree_a.set_parent_ids([base_id])
3219
('killed-a', b'a\n'),
3220
('killed-b', b'b\n'),
3221
('unchanged', b'c\n'),
3222
('unchanged', b'd\n'),
3225
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3227
def test_plan_file_merge_revision_tree(self):
3228
work_a = self.make_branch_and_tree('wta')
3229
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3230
work_a.add('file', b'file-id')
3231
base_id = work_a.commit('base version')
3232
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3233
preview = TransformPreview(work_a.basis_tree())
3234
self.addCleanup(preview.finalize)
3235
trans_id = preview.trans_id_file_id(b'file-id')
3236
preview.delete_contents(trans_id)
3237
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3238
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3239
tree_a = preview.get_preview_tree()
3240
tree_a.set_parent_ids([base_id])
3242
('killed-a', b'a\n'),
3243
('killed-b', b'b\n'),
3244
('unchanged', b'c\n'),
3245
('unchanged', b'd\n'),
3248
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3250
def test_walkdirs(self):
3251
preview = self.get_empty_preview()
3252
preview.new_directory('', ROOT_PARENT, b'tree-root')
3253
# FIXME: new_directory should mark root.
3254
preview.fixup_new_roots()
3255
preview_tree = preview.get_preview_tree()
3256
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3257
expected = [(('', b'tree-root'),
3258
[('a', 'a', 'file', None, b'a-id', 'file')])]
3259
self.assertEqual(expected, list(preview_tree.walkdirs()))
3261
def test_extras(self):
3262
work_tree = self.make_branch_and_tree('tree')
3263
self.build_tree(['tree/removed-file', 'tree/existing-file',
3264
'tree/not-removed-file'])
3265
work_tree.add(['removed-file', 'not-removed-file'])
3266
preview = TransformPreview(work_tree)
3267
self.addCleanup(preview.finalize)
3268
preview.new_file('new-file', preview.root, [b'contents'])
3269
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3270
b'new-versioned-id')
3271
tree = preview.get_preview_tree()
3272
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3273
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3276
def test_merge_into_preview(self):
3277
work_tree = self.make_branch_and_tree('tree')
3278
self.build_tree_contents([('tree/file', b'b\n')])
3279
work_tree.add('file', b'file-id')
3280
work_tree.commit('first commit')
3281
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3282
self.build_tree_contents([('child/file', b'b\nc\n')])
3283
child_tree.commit('child commit')
3284
child_tree.lock_write()
3285
self.addCleanup(child_tree.unlock)
3286
work_tree.lock_write()
3287
self.addCleanup(work_tree.unlock)
3288
preview = TransformPreview(work_tree)
3289
self.addCleanup(preview.finalize)
3290
file_trans_id = preview.trans_id_file_id(b'file-id')
3291
preview.delete_contents(file_trans_id)
3292
preview.create_file([b'a\nb\n'], file_trans_id)
3293
preview_tree = preview.get_preview_tree()
3294
merger = Merger.from_revision_ids(preview_tree,
3295
child_tree.branch.last_revision(),
3296
other_branch=child_tree.branch,
3297
tree_branch=work_tree.branch)
3298
merger.merge_type = Merge3Merger
3299
tt = merger.make_merger().make_preview_transform()
3300
self.addCleanup(tt.finalize)
3301
final_tree = tt.get_preview_tree()
3304
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3306
def test_merge_preview_into_workingtree(self):
3307
tree = self.make_branch_and_tree('tree')
3308
tree.set_root_id(b'TREE_ROOT')
3309
tt = TransformPreview(tree)
3310
self.addCleanup(tt.finalize)
3311
tt.new_file('name', tt.root, [b'content'], b'file-id')
3312
tree2 = self.make_branch_and_tree('tree2')
3313
tree2.set_root_id(b'TREE_ROOT')
3314
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3316
merger.merge_type = Merge3Merger
3319
def test_merge_preview_into_workingtree_handles_conflicts(self):
3320
tree = self.make_branch_and_tree('tree')
3321
self.build_tree_contents([('tree/foo', b'bar')])
3322
tree.add('foo', b'foo-id')
3324
tt = TransformPreview(tree)
3325
self.addCleanup(tt.finalize)
3326
trans_id = tt.trans_id_file_id(b'foo-id')
3327
tt.delete_contents(trans_id)
3328
tt.create_file([b'baz'], trans_id)
3329
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3330
self.build_tree_contents([('tree2/foo', b'qux')])
3331
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3333
merger.merge_type = Merge3Merger
3336
def test_has_filename(self):
3337
wt = self.make_branch_and_tree('tree')
3338
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3339
tt = TransformPreview(wt)
3340
removed_id = tt.trans_id_tree_path('removed')
3341
tt.delete_contents(removed_id)
3342
tt.new_file('new', tt.root, [b'contents'])
3343
modified_id = tt.trans_id_tree_path('modified')
3344
tt.delete_contents(modified_id)
3345
tt.create_file([b'modified-contents'], modified_id)
3346
self.addCleanup(tt.finalize)
3347
tree = tt.get_preview_tree()
3348
self.assertTrue(tree.has_filename('unmodified'))
3349
self.assertFalse(tree.has_filename('not-present'))
3350
self.assertFalse(tree.has_filename('removed'))
3351
self.assertTrue(tree.has_filename('new'))
3352
self.assertTrue(tree.has_filename('modified'))
3354
def test_is_executable(self):
3355
tree = self.make_branch_and_tree('tree')
3356
preview = TransformPreview(tree)
3357
self.addCleanup(preview.finalize)
3358
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3359
preview_tree = preview.get_preview_tree()
3360
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3362
def test_commit_preview_tree(self):
3363
tree = self.make_branch_and_tree('tree')
3364
rev_id = tree.commit('rev1')
3365
tree.branch.lock_write()
3366
self.addCleanup(tree.branch.unlock)
3367
tt = TransformPreview(tree)
3368
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3369
self.addCleanup(tt.finalize)
3370
preview = tt.get_preview_tree()
3371
preview.set_parent_ids([rev_id])
3372
builder = tree.branch.get_commit_builder([rev_id])
3373
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3374
builder.finish_inventory()
3375
rev2_id = builder.commit('rev2')
3376
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3377
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3379
def test_ascii_limbo_paths(self):
3380
self.requireFeature(features.UnicodeFilenameFeature)
3381
branch = self.make_branch('any')
3382
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3383
tt = TransformPreview(tree)
3384
self.addCleanup(tt.finalize)
3385
foo_id = tt.new_directory('', ROOT_PARENT)
3386
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3387
limbo_path = tt._limbo_name(bar_id)
3388
self.assertEqual(limbo_path, limbo_path)
3391
class FakeSerializer(object):
3392
"""Serializer implementation that simply returns the input.
3394
The input is returned in the order used by pack.ContainerPushParser.
3397
def bytes_record(bytes, names):
3401
class TestSerializeTransform(tests.TestCaseWithTransport):
3403
_test_needs_features = [features.UnicodeFilenameFeature]
3405
def get_preview(self, tree=None):
3407
tree = self.make_branch_and_tree('tree')
3408
tt = TransformPreview(tree)
3409
self.addCleanup(tt.finalize)
3412
def assertSerializesTo(self, expected, tt):
3413
records = list(tt.serialize(FakeSerializer()))
3414
self.assertEqual(expected, records)
3417
def default_attribs():
3422
b'_new_executability': {},
3424
b'_tree_path_ids': {b'': b'new-0'},
3426
b'_removed_contents': [],
3427
b'_non_present_ids': {},
3430
def make_records(self, attribs, contents):
3432
((((b'attribs'),),), bencode.bencode(attribs))]
3433
records.extend([(((n, k),), c) for n, k, c in contents])
3436
def creation_records(self):
3437
attribs = self.default_attribs()
3438
attribs[b'_id_number'] = 3
3439
attribs[b'_new_name'] = {
3440
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3441
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3442
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3443
attribs[b'_new_executability'] = {b'new-1': 1}
3445
(b'new-1', b'file', b'i 1\nbar\n'),
3446
(b'new-2', b'directory', b''),
3448
return self.make_records(attribs, contents)
3450
def test_serialize_creation(self):
3451
tt = self.get_preview()
3452
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3453
tt.new_directory('qux', tt.root, b'quxx')
3454
self.assertSerializesTo(self.creation_records(), tt)
3456
def test_deserialize_creation(self):
3457
tt = self.get_preview()
3458
tt.deserialize(iter(self.creation_records()))
3459
self.assertEqual(3, tt._id_number)
3460
self.assertEqual({'new-1': u'foo\u1234',
3461
'new-2': 'qux'}, tt._new_name)
3462
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3463
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3464
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3465
self.assertEqual({'new-1': True}, tt._new_executability)
3466
self.assertEqual({'new-1': 'file',
3467
'new-2': 'directory'}, tt._new_contents)
3468
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3470
foo_content = foo_limbo.read()
3473
self.assertEqual(b'bar', foo_content)
3475
def symlink_creation_records(self):
3476
attribs = self.default_attribs()
3477
attribs[b'_id_number'] = 2
3478
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3479
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3480
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3481
return self.make_records(attribs, contents)
3483
def test_serialize_symlink_creation(self):
3484
self.requireFeature(features.SymlinkFeature)
3485
tt = self.get_preview()
3486
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3487
self.assertSerializesTo(self.symlink_creation_records(), tt)
3489
def test_deserialize_symlink_creation(self):
3490
self.requireFeature(features.SymlinkFeature)
3491
tt = self.get_preview()
3492
tt.deserialize(iter(self.symlink_creation_records()))
3493
abspath = tt._limbo_name('new-1')
3494
foo_content = osutils.readlink(abspath)
3495
self.assertEqual(u'bar\u1234', foo_content)
3497
def make_destruction_preview(self):
3498
tree = self.make_branch_and_tree('.')
3499
self.build_tree([u'foo\u1234', 'bar'])
3500
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3501
return self.get_preview(tree)
3503
def destruction_records(self):
3504
attribs = self.default_attribs()
3505
attribs[b'_id_number'] = 3
3506
attribs[b'_removed_id'] = [b'new-1']
3507
attribs[b'_removed_contents'] = [b'new-2']
3508
attribs[b'_tree_path_ids'] = {
3510
u'foo\u1234'.encode('utf-8'): b'new-1',
3513
return self.make_records(attribs, [])
3515
def test_serialize_destruction(self):
3516
tt = self.make_destruction_preview()
3517
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3518
tt.unversion_file(foo_trans_id)
3519
bar_trans_id = tt.trans_id_tree_path('bar')
3520
tt.delete_contents(bar_trans_id)
3521
self.assertSerializesTo(self.destruction_records(), tt)
3523
def test_deserialize_destruction(self):
3524
tt = self.make_destruction_preview()
3525
tt.deserialize(iter(self.destruction_records()))
3526
self.assertEqual({u'foo\u1234': 'new-1',
3528
'': tt.root}, tt._tree_path_ids)
3529
self.assertEqual({'new-1': u'foo\u1234',
3531
tt.root: ''}, tt._tree_id_paths)
3532
self.assertEqual({'new-1'}, tt._removed_id)
3533
self.assertEqual({'new-2'}, tt._removed_contents)
3535
def missing_records(self):
3536
attribs = self.default_attribs()
3537
attribs[b'_id_number'] = 2
3538
attribs[b'_non_present_ids'] = {
3540
return self.make_records(attribs, [])
3542
def test_serialize_missing(self):
3543
tt = self.get_preview()
3544
tt.trans_id_file_id(b'boo')
3545
self.assertSerializesTo(self.missing_records(), tt)
3547
def test_deserialize_missing(self):
3548
tt = self.get_preview()
3549
tt.deserialize(iter(self.missing_records()))
3550
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3552
def make_modification_preview(self):
3553
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3554
LINES_TWO = b'z\nbb\nx\ndd\n'
3555
tree = self.make_branch_and_tree('tree')
3556
self.build_tree_contents([('tree/file', LINES_ONE)])
3557
tree.add('file', b'file-id')
3558
return self.get_preview(tree), [LINES_TWO]
3560
def modification_records(self):
3561
attribs = self.default_attribs()
3562
attribs[b'_id_number'] = 2
3563
attribs[b'_tree_path_ids'] = {
3566
attribs[b'_removed_contents'] = [b'new-1']
3567
contents = [(b'new-1', b'file',
3568
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3569
return self.make_records(attribs, contents)
3571
def test_serialize_modification(self):
3572
tt, LINES = self.make_modification_preview()
3573
trans_id = tt.trans_id_file_id(b'file-id')
3574
tt.delete_contents(trans_id)
3575
tt.create_file(LINES, trans_id)
3576
self.assertSerializesTo(self.modification_records(), tt)
3578
def test_deserialize_modification(self):
3579
tt, LINES = self.make_modification_preview()
3580
tt.deserialize(iter(self.modification_records()))
3581
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3583
def make_kind_change_preview(self):
3584
LINES = b'a\nb\nc\nd\n'
3585
tree = self.make_branch_and_tree('tree')
3586
self.build_tree(['tree/foo/'])
3587
tree.add('foo', b'foo-id')
3588
return self.get_preview(tree), [LINES]
3590
def kind_change_records(self):
3591
attribs = self.default_attribs()
3592
attribs[b'_id_number'] = 2
3593
attribs[b'_tree_path_ids'] = {
3596
attribs[b'_removed_contents'] = [b'new-1']
3597
contents = [(b'new-1', b'file',
3598
b'i 4\na\nb\nc\nd\n\n')]
3599
return self.make_records(attribs, contents)
3601
def test_serialize_kind_change(self):
3602
tt, LINES = self.make_kind_change_preview()
3603
trans_id = tt.trans_id_file_id(b'foo-id')
3604
tt.delete_contents(trans_id)
3605
tt.create_file(LINES, trans_id)
3606
self.assertSerializesTo(self.kind_change_records(), tt)
3608
def test_deserialize_kind_change(self):
3609
tt, LINES = self.make_kind_change_preview()
3610
tt.deserialize(iter(self.kind_change_records()))
3611
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3613
def make_add_contents_preview(self):
3614
LINES = b'a\nb\nc\nd\n'
3615
tree = self.make_branch_and_tree('tree')
3616
self.build_tree(['tree/foo'])
3618
os.unlink('tree/foo')
3619
return self.get_preview(tree), LINES
3621
def add_contents_records(self):
3622
attribs = self.default_attribs()
3623
attribs[b'_id_number'] = 2
3624
attribs[b'_tree_path_ids'] = {
3627
contents = [(b'new-1', b'file',
3628
b'i 4\na\nb\nc\nd\n\n')]
3629
return self.make_records(attribs, contents)
3631
def test_serialize_add_contents(self):
3632
tt, LINES = self.make_add_contents_preview()
3633
trans_id = tt.trans_id_tree_path('foo')
3634
tt.create_file([LINES], trans_id)
3635
self.assertSerializesTo(self.add_contents_records(), tt)
3637
def test_deserialize_add_contents(self):
3638
tt, LINES = self.make_add_contents_preview()
3639
tt.deserialize(iter(self.add_contents_records()))
3640
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3642
def test_get_parents_lines(self):
3643
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3644
tree = self.make_branch_and_tree('tree')
3645
self.build_tree_contents([('tree/file', LINES_ONE)])
3646
tree.add('file', b'file-id')
3647
tt = self.get_preview(tree)
3648
trans_id = tt.trans_id_tree_path('file')
3649
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3650
tt._get_parents_lines(trans_id))
3652
def test_get_parents_texts(self):
3653
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3654
tree = self.make_branch_and_tree('tree')
3655
self.build_tree_contents([('tree/file', LINES_ONE)])
3656
tree.add('file', b'file-id')
3657
tt = self.get_preview(tree)
3658
trans_id = tt.trans_id_tree_path('file')
3659
self.assertEqual((LINES_ONE,),
3660
tt._get_parents_texts(trans_id))
3663
class TestOrphan(tests.TestCaseWithTransport):
3665
def test_no_orphan_for_transform_preview(self):
3666
tree = self.make_branch_and_tree('tree')
3667
tt = transform.TransformPreview(tree)
3668
self.addCleanup(tt.finalize)
3669
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3671
def _set_orphan_policy(self, wt, policy):
3672
wt.branch.get_config_stack().set('transform.orphan_policy',
3675
def _prepare_orphan(self, wt):
3676
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3677
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3678
wt.commit('add dir and file ignoring foo')
3679
tt = transform.TreeTransform(wt)
3680
self.addCleanup(tt.finalize)
3681
# dir and bar are deleted
3682
dir_tid = tt.trans_id_tree_path('dir')
3683
file_tid = tt.trans_id_tree_path('dir/file')
3684
orphan_tid = tt.trans_id_tree_path('dir/foo')
3685
tt.delete_contents(file_tid)
3686
tt.unversion_file(file_tid)
3687
tt.delete_contents(dir_tid)
3688
tt.unversion_file(dir_tid)
3689
# There should be a conflict because dir still contain foo
3690
raw_conflicts = tt.find_conflicts()
3691
self.assertLength(1, raw_conflicts)
3692
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3693
return tt, orphan_tid
3695
def test_new_orphan_created(self):
3696
wt = self.make_branch_and_tree('.')
3697
self._set_orphan_policy(wt, 'move')
3698
tt, orphan_tid = self._prepare_orphan(wt)
3702
warnings.append(args[0] % args[1:])
3703
self.overrideAttr(trace, 'warning', warning)
3704
remaining_conflicts = resolve_conflicts(tt)
3705
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3707
# Yeah for resolved conflicts !
3708
self.assertLength(0, remaining_conflicts)
3709
# We have a new orphan
3710
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3711
self.assertEqual('brz-orphans',
3712
tt.final_name(tt.final_parent(orphan_tid)))
3714
def test_never_orphan(self):
3715
wt = self.make_branch_and_tree('.')
3716
self._set_orphan_policy(wt, 'conflict')
3717
tt, orphan_tid = self._prepare_orphan(wt)
3718
remaining_conflicts = resolve_conflicts(tt)
3719
self.assertLength(1, remaining_conflicts)
3720
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3721
remaining_conflicts.pop())
3723
def test_orphan_error(self):
3724
def bogus_orphan(tt, orphan_id, parent_id):
3725
raise transform.OrphaningError(tt.final_name(orphan_id),
3726
tt.final_name(parent_id))
3727
transform.orphaning_registry.register('bogus', bogus_orphan,
3728
'Raise an error when orphaning')
3729
wt = self.make_branch_and_tree('.')
3730
self._set_orphan_policy(wt, 'bogus')
3731
tt, orphan_tid = self._prepare_orphan(wt)
3732
remaining_conflicts = resolve_conflicts(tt)
3733
self.assertLength(1, remaining_conflicts)
3734
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3735
remaining_conflicts.pop())
3737
def test_unknown_orphan_policy(self):
3738
wt = self.make_branch_and_tree('.')
3739
# Set a fictional policy nobody ever implemented
3740
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3741
tt, orphan_tid = self._prepare_orphan(wt)
3745
warnings.append(args[0] % args[1:])
3746
self.overrideAttr(trace, 'warning', warning)
3747
remaining_conflicts = resolve_conflicts(tt)
3748
# We fallback to the default policy which create a conflict
3749
self.assertLength(1, remaining_conflicts)
3750
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3751
remaining_conflicts.pop())
3752
self.assertLength(1, warnings)
3753
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3756
class TestTransformHooks(tests.TestCaseWithTransport):
3759
super(TestTransformHooks, self).setUp()
3760
self.wt = self.make_branch_and_tree('.')
3763
def get_transform(self):
3764
transform = TreeTransform(self.wt)
3765
self.addCleanup(transform.finalize)
3766
return transform, transform.root
3768
def test_pre_commit_hooks(self):
3771
def record_pre_transform(tree, tt):
3772
calls.append((tree, tt))
3773
MutableTree.hooks.install_named_hook(
3774
'pre_transform', record_pre_transform, "Pre transform")
3775
transform, root = self.get_transform()
3776
old_root_id = transform.tree_file_id(root)
3778
self.assertEqual(old_root_id, self.wt.get_root_id())
3779
self.assertEqual([(self.wt, transform)], calls)
3781
def test_post_commit_hooks(self):
3784
def record_post_transform(tree, tt):
3785
calls.append((tree, tt))
3786
MutableTree.hooks.install_named_hook(
3787
'post_transform', record_post_transform, "Post transform")
3788
transform, root = self.get_transform()
3789
old_root_id = transform.tree_file_id(root)
3791
self.assertEqual(old_root_id, self.wt.get_root_id())
3792
self.assertEqual([(self.wt, transform)], calls)
3795
class TestLinkTree(tests.TestCaseWithTransport):
3797
_test_needs_features = [HardlinkFeature]
3800
tests.TestCaseWithTransport.setUp(self)
3801
self.parent_tree = self.make_branch_and_tree('parent')
3802
self.parent_tree.lock_write()
3803
self.addCleanup(self.parent_tree.unlock)
3804
self.build_tree_contents([('parent/foo', b'bar')])
3805
self.parent_tree.add('foo')
3806
self.parent_tree.commit('added foo')
3807
child_controldir = self.parent_tree.controldir.sprout('child')
3808
self.child_tree = child_controldir.open_workingtree()
3810
def hardlinked(self):
3811
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3812
child_stat = os.lstat(self.child_tree.abspath('foo'))
3813
return parent_stat.st_ino == child_stat.st_ino
3815
def test_link_fails_if_modified(self):
3816
"""If the file to be linked has modified text, don't link."""
3817
self.build_tree_contents([('child/foo', b'baz')])
3818
transform.link_tree(self.child_tree, self.parent_tree)
3819
self.assertFalse(self.hardlinked())
3821
def test_link_fails_if_execute_bit_changed(self):
3822
"""If the file to be linked has modified execute bit, don't link."""
3823
tt = TreeTransform(self.child_tree)
3825
trans_id = tt.trans_id_tree_path('foo')
3826
tt.set_executability(True, trans_id)
3830
transform.link_tree(self.child_tree, self.parent_tree)
3831
self.assertFalse(self.hardlinked())
3833
def test_link_succeeds_if_unmodified(self):
3834
"""If the file to be linked is unmodified, link"""
3835
transform.link_tree(self.child_tree, self.parent_tree)
3836
self.assertTrue(self.hardlinked())