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,
35
from ..conflicts import (
44
from ..controldir import ControlDir
45
from ..diff import show_diff_trees
46
from ..errors import (
49
ExistingPendingDeletion,
51
ImmortalPendingDeletion,
56
from ..osutils import (
60
from ..merge import Merge3Merger, Merger
61
from ..mutabletree import MutableTree
62
from ..sixish import (
71
from .features import (
75
from ..transform import (
89
class TestTreeTransform(tests.TestCaseWithTransport):
92
super(TestTreeTransform, self).setUp()
93
self.wt = self.make_branch_and_tree('.', format='development-subtree')
96
def get_transform(self):
97
transform = TreeTransform(self.wt)
98
self.addCleanup(transform.finalize)
99
return transform, transform.root
101
def get_transform_for_sha1_test(self):
102
trans, root = self.get_transform()
103
self.wt.lock_tree_write()
104
self.addCleanup(self.wt.unlock)
105
contents = [b'just some content\n']
106
sha1 = osutils.sha_strings(contents)
107
# Roll back the clock
108
trans._creation_mtime = time.time() - 20.0
109
return trans, root, contents, sha1
111
def test_existing_limbo(self):
112
transform, root = self.get_transform()
113
limbo_name = transform._limbodir
114
deletion_path = transform._deletiondir
115
os.mkdir(pathjoin(limbo_name, 'hehe'))
116
self.assertRaises(ImmortalLimbo, transform.apply)
117
self.assertRaises(LockError, self.wt.unlock)
118
self.assertRaises(ExistingLimbo, self.get_transform)
119
self.assertRaises(LockError, self.wt.unlock)
120
os.rmdir(pathjoin(limbo_name, 'hehe'))
122
os.rmdir(deletion_path)
123
transform, root = self.get_transform()
126
def test_existing_pending_deletion(self):
127
transform, root = self.get_transform()
128
deletion_path = self._limbodir = urlutils.local_path_from_url(
129
transform._tree._transport.abspath('pending-deletion'))
130
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
131
self.assertRaises(ImmortalPendingDeletion, transform.apply)
132
self.assertRaises(LockError, self.wt.unlock)
133
self.assertRaises(ExistingPendingDeletion, self.get_transform)
135
def test_build(self):
136
transform, root = self.get_transform()
137
self.wt.lock_tree_write()
138
self.addCleanup(self.wt.unlock)
139
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
140
imaginary_id = transform.trans_id_tree_path('imaginary')
141
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
142
self.assertEqual(imaginary_id, imaginary_id2)
143
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
144
self.assertEqual('directory', transform.final_kind(root))
145
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
146
trans_id = transform.create_path('name', root)
147
self.assertIs(transform.final_file_id(trans_id), None)
148
self.assertIs(None, transform.final_kind(trans_id))
149
transform.create_file([b'contents'], trans_id)
150
transform.set_executability(True, trans_id)
151
transform.version_file(b'my_pretties', trans_id)
152
self.assertRaises(DuplicateKey, transform.version_file,
153
b'my_pretties', trans_id)
154
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
155
self.assertEqual(transform.final_parent(trans_id), root)
156
self.assertIs(transform.final_parent(root), ROOT_PARENT)
157
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
158
oz_id = transform.create_path('oz', root)
159
transform.create_directory(oz_id)
160
transform.version_file(b'ozzie', oz_id)
161
trans_id2 = transform.create_path('name2', root)
162
transform.create_file([b'contents'], trans_id2)
163
transform.set_executability(False, trans_id2)
164
transform.version_file(b'my_pretties2', trans_id2)
165
modified_paths = transform.apply().modified_paths
166
with self.wt.get_file('name') as f:
167
self.assertEqual(b'contents', f.read())
168
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
169
self.assertIs(self.wt.is_executable('name'), True)
170
self.assertIs(self.wt.is_executable('name2'), False)
171
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
172
self.assertEqual(len(modified_paths), 3)
173
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
174
(b'ozzie', b'my_pretties', b'my_pretties2')]
175
self.assertSubset(tree_mod_paths, modified_paths)
176
# is it safe to finalize repeatedly?
180
def test_apply_informs_tree_of_observed_sha1(self):
181
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
182
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
185
orig = self.wt._observed_sha1
186
def _observed_sha1(*args):
189
self.wt._observed_sha1 = _observed_sha1
191
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
194
def test_create_file_caches_sha1(self):
195
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
196
trans_id = trans.create_path('file1', root)
197
trans.create_file(contents, trans_id, sha1=sha1)
198
st_val = osutils.lstat(trans._limbo_name(trans_id))
199
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
200
self.assertEqual(o_sha1, sha1)
201
self.assertEqualStat(o_st_val, st_val)
203
def test__apply_insertions_updates_sha1(self):
204
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
205
trans_id = trans.create_path('file1', root)
206
trans.create_file(contents, trans_id, sha1=sha1)
207
st_val = osutils.lstat(trans._limbo_name(trans_id))
208
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
209
self.assertEqual(o_sha1, sha1)
210
self.assertEqualStat(o_st_val, st_val)
211
creation_mtime = trans._creation_mtime + 10.0
212
# We fake a time difference from when the file was created until now it
213
# is being renamed by using os.utime. Note that the change we actually
214
# want to see is the real ctime change from 'os.rename()', but as long
215
# as we observe a new stat value, we should be fine.
216
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
218
new_st_val = osutils.lstat(self.wt.abspath('file1'))
219
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
220
self.assertEqual(o_sha1, sha1)
221
self.assertEqualStat(o_st_val, new_st_val)
222
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
224
def test_new_file_caches_sha1(self):
225
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
226
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
228
st_val = osutils.lstat(trans._limbo_name(trans_id))
229
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
230
self.assertEqual(o_sha1, sha1)
231
self.assertEqualStat(o_st_val, st_val)
233
def test_cancel_creation_removes_observed_sha1(self):
234
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
235
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
237
self.assertTrue(trans_id in trans._observed_sha1s)
238
trans.cancel_creation(trans_id)
239
self.assertFalse(trans_id in trans._observed_sha1s)
241
def test_create_files_same_timestamp(self):
242
transform, root = self.get_transform()
243
self.wt.lock_tree_write()
244
self.addCleanup(self.wt.unlock)
245
# Roll back the clock, so that we know everything is being set to the
247
transform._creation_mtime = creation_mtime = time.time() - 20.0
248
transform.create_file([b'content-one'],
249
transform.create_path('one', root))
250
time.sleep(1) # *ugly*
251
transform.create_file([b'content-two'],
252
transform.create_path('two', root))
254
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
256
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
258
# We only guarantee 2s resolution
259
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
260
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
261
# But if we have more than that, all files should get the same result
262
self.assertEqual(st1.st_mtime, st2.st_mtime)
264
def test_change_root_id(self):
265
transform, root = self.get_transform()
266
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
267
transform.new_directory('', ROOT_PARENT, b'new-root-id')
268
transform.delete_contents(root)
269
transform.unversion_file(root)
270
transform.fixup_new_roots()
272
self.assertEqual(b'new-root-id', self.wt.get_root_id())
274
def test_change_root_id_add_files(self):
275
transform, root = self.get_transform()
276
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
277
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
278
transform.new_file('file', new_trans_id, [b'new-contents\n'],
280
transform.delete_contents(root)
281
transform.unversion_file(root)
282
transform.fixup_new_roots()
284
self.assertEqual(b'new-root-id', self.wt.get_root_id())
285
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
286
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
288
def test_add_two_roots(self):
289
transform, root = self.get_transform()
290
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
291
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
292
self.assertRaises(ValueError, transform.fixup_new_roots)
294
def test_retain_existing_root(self):
295
tt, root = self.get_transform()
297
tt.new_directory('', ROOT_PARENT, b'new-root-id')
299
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
301
def test_retain_existing_root_added_file(self):
302
tt, root = self.get_transform()
303
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
304
child = tt.new_directory('child', new_trans_id, b'child-id')
306
self.assertEqual(tt.root, tt.final_parent(child))
308
def test_add_unversioned_root(self):
309
transform, root = self.get_transform()
310
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
311
transform.delete_contents(transform.root)
312
transform.fixup_new_roots()
313
self.assertNotIn(transform.root, transform._new_id)
315
def test_remove_root_fixup(self):
316
transform, root = self.get_transform()
317
old_root_id = self.wt.get_root_id()
318
self.assertNotEqual(b'new-root-id', old_root_id)
319
transform.delete_contents(root)
320
transform.unversion_file(root)
321
transform.fixup_new_roots()
323
self.assertEqual(old_root_id, self.wt.get_root_id())
325
transform, root = self.get_transform()
326
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
327
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
328
self.assertRaises(ValueError, transform.fixup_new_roots)
330
def test_fixup_new_roots_permits_empty_tree(self):
331
transform, root = self.get_transform()
332
transform.delete_contents(root)
333
transform.unversion_file(root)
334
transform.fixup_new_roots()
335
self.assertIs(None, transform.final_kind(root))
336
self.assertIs(None, transform.final_file_id(root))
338
def test_apply_retains_root_directory(self):
339
# Do not attempt to delete the physical root directory, because that
341
transform, root = self.get_transform()
343
transform.delete_contents(root)
344
e = self.assertRaises(AssertionError, self.assertRaises,
345
errors.TransformRenameFailed,
347
self.assertContainsRe('TransformRenameFailed not raised', str(e))
349
def test_apply_retains_file_id(self):
350
transform, root = self.get_transform()
351
old_root_id = transform.tree_file_id(root)
352
transform.unversion_file(root)
354
self.assertEqual(old_root_id, self.wt.get_root_id())
356
def test_hardlink(self):
357
self.requireFeature(HardlinkFeature)
358
transform, root = self.get_transform()
359
transform.new_file('file1', root, [b'contents'])
361
target = self.make_branch_and_tree('target')
362
target_transform = TreeTransform(target)
363
trans_id = target_transform.create_path('file1', target_transform.root)
364
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
365
target_transform.apply()
366
self.assertPathExists('target/file1')
367
source_stat = os.stat(self.wt.abspath('file1'))
368
target_stat = os.stat('target/file1')
369
self.assertEqual(source_stat, target_stat)
371
def test_convenience(self):
372
transform, root = self.get_transform()
373
self.wt.lock_tree_write()
374
self.addCleanup(self.wt.unlock)
375
trans_id = transform.new_file('name', root, [b'contents'],
376
b'my_pretties', True)
377
oz = transform.new_directory('oz', root, b'oz-id')
378
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
379
toto = transform.new_file('toto', dorothy, [b'toto-contents'],
382
self.assertEqual(len(transform.find_conflicts()), 0)
384
self.assertRaises(ReusingTransform, transform.find_conflicts)
385
with open(self.wt.abspath('name'), 'r') as f:
386
self.assertEqual('contents', f.read())
387
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
388
self.assertIs(self.wt.is_executable('name'), True)
389
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
390
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
391
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
393
self.assertEqual(b'toto-contents',
394
self.wt.get_file('oz/dorothy/toto').read())
395
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
397
def test_tree_reference(self):
398
transform, root = self.get_transform()
399
tree = transform._tree
400
trans_id = transform.new_directory('reference', root, b'subtree-id')
401
transform.set_tree_reference(b'subtree-revision', trans_id)
404
self.addCleanup(tree.unlock)
405
self.assertEqual(b'subtree-revision',
406
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
408
def test_conflicts(self):
409
transform, root = self.get_transform()
410
trans_id = transform.new_file('name', root, [b'contents'],
412
self.assertEqual(len(transform.find_conflicts()), 0)
413
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
414
self.assertEqual(transform.find_conflicts(),
415
[('duplicate', trans_id, trans_id2, 'name')])
416
self.assertRaises(MalformedTransform, transform.apply)
417
transform.adjust_path('name', trans_id, trans_id2)
418
self.assertEqual(transform.find_conflicts(),
419
[('non-directory parent', trans_id)])
420
tinman_id = transform.trans_id_tree_path('tinman')
421
transform.adjust_path('name', tinman_id, trans_id2)
422
self.assertEqual(transform.find_conflicts(),
423
[('unversioned parent', tinman_id),
424
('missing parent', tinman_id)])
425
lion_id = transform.create_path('lion', root)
426
self.assertEqual(transform.find_conflicts(),
427
[('unversioned parent', tinman_id),
428
('missing parent', tinman_id)])
429
transform.adjust_path('name', lion_id, trans_id2)
430
self.assertEqual(transform.find_conflicts(),
431
[('unversioned parent', lion_id),
432
('missing parent', lion_id)])
433
transform.version_file(b"Courage", lion_id)
434
self.assertEqual(transform.find_conflicts(),
435
[('missing parent', lion_id),
436
('versioning no contents', lion_id)])
437
transform.adjust_path('name2', root, trans_id2)
438
self.assertEqual(transform.find_conflicts(),
439
[('versioning no contents', lion_id)])
440
transform.create_file([b'Contents, okay?'], lion_id)
441
transform.adjust_path('name2', trans_id2, trans_id2)
442
self.assertEqual(transform.find_conflicts(),
443
[('parent loop', trans_id2),
444
('non-directory parent', trans_id2)])
445
transform.adjust_path('name2', root, trans_id2)
446
oz_id = transform.new_directory('oz', root)
447
transform.set_executability(True, oz_id)
448
self.assertEqual(transform.find_conflicts(),
449
[('unversioned executability', oz_id)])
450
transform.version_file(b'oz-id', oz_id)
451
self.assertEqual(transform.find_conflicts(),
452
[('non-file executability', oz_id)])
453
transform.set_executability(None, oz_id)
454
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
456
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
457
with open(self.wt.abspath('name'), 'rb') as f:
458
self.assertEqual(b'contents', f.read())
459
transform2, root = self.get_transform()
460
oz_id = transform2.trans_id_tree_path('oz')
461
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
462
result = transform2.find_conflicts()
463
fp = FinalPaths(transform2)
464
self.assertTrue('oz/tip' in transform2._tree_path_ids)
465
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
466
self.assertEqual(len(result), 2)
467
self.assertEqual((result[0][0], result[0][1]),
468
('duplicate', newtip))
469
self.assertEqual((result[1][0], result[1][2]),
470
('duplicate id', newtip))
471
transform2.finalize()
472
transform3 = TreeTransform(self.wt)
473
self.addCleanup(transform3.finalize)
474
oz_id = transform3.trans_id_tree_path('oz')
475
transform3.delete_contents(oz_id)
476
self.assertEqual(transform3.find_conflicts(),
477
[('missing parent', oz_id)])
478
root_id = transform3.root
479
tip_id = transform3.trans_id_tree_path('oz/tip')
480
transform3.adjust_path('tip', root_id, tip_id)
483
def test_conflict_on_case_insensitive(self):
484
tree = self.make_branch_and_tree('tree')
485
# Don't try this at home, kids!
486
# Force the tree to report that it is case sensitive, for conflict
488
tree.case_sensitive = True
489
transform = TreeTransform(tree)
490
self.addCleanup(transform.finalize)
491
transform.new_file('file', transform.root, [b'content'])
492
transform.new_file('FiLe', transform.root, [b'content'])
493
result = transform.find_conflicts()
494
self.assertEqual([], result)
496
# Force the tree to report that it is case insensitive, for conflict
498
tree.case_sensitive = False
499
transform = TreeTransform(tree)
500
self.addCleanup(transform.finalize)
501
transform.new_file('file', transform.root, [b'content'])
502
transform.new_file('FiLe', transform.root, [b'content'])
503
result = transform.find_conflicts()
504
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
506
def test_conflict_on_case_insensitive_existing(self):
507
tree = self.make_branch_and_tree('tree')
508
self.build_tree(['tree/FiLe'])
509
# Don't try this at home, kids!
510
# Force the tree to report that it is case sensitive, for conflict
512
tree.case_sensitive = True
513
transform = TreeTransform(tree)
514
self.addCleanup(transform.finalize)
515
transform.new_file('file', transform.root, [b'content'])
516
result = transform.find_conflicts()
517
self.assertEqual([], result)
519
# Force the tree to report that it is case insensitive, for conflict
521
tree.case_sensitive = False
522
transform = TreeTransform(tree)
523
self.addCleanup(transform.finalize)
524
transform.new_file('file', transform.root, [b'content'])
525
result = transform.find_conflicts()
526
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
528
def test_resolve_case_insensitive_conflict(self):
529
tree = self.make_branch_and_tree('tree')
530
# Don't try this at home, kids!
531
# Force the tree to report that it is case insensitive, for conflict
533
tree.case_sensitive = False
534
transform = TreeTransform(tree)
535
self.addCleanup(transform.finalize)
536
transform.new_file('file', transform.root, [b'content'])
537
transform.new_file('FiLe', transform.root, [b'content'])
538
resolve_conflicts(transform)
540
self.assertPathExists('tree/file')
541
self.assertPathExists('tree/FiLe.moved')
543
def test_resolve_checkout_case_conflict(self):
544
tree = self.make_branch_and_tree('tree')
545
# Don't try this at home, kids!
546
# Force the tree to report that it is case insensitive, for conflict
548
tree.case_sensitive = False
549
transform = TreeTransform(tree)
550
self.addCleanup(transform.finalize)
551
transform.new_file('file', transform.root, [b'content'])
552
transform.new_file('FiLe', transform.root, [b'content'])
553
resolve_conflicts(transform,
554
pass_func=lambda t, c: resolve_checkout(t, c, []))
556
self.assertPathExists('tree/file')
557
self.assertPathExists('tree/FiLe.moved')
559
def test_apply_case_conflict(self):
560
"""Ensure that a transform with case conflicts can always be applied"""
561
tree = self.make_branch_and_tree('tree')
562
transform = TreeTransform(tree)
563
self.addCleanup(transform.finalize)
564
transform.new_file('file', transform.root, [b'content'])
565
transform.new_file('FiLe', transform.root, [b'content'])
566
dir = transform.new_directory('dir', transform.root)
567
transform.new_file('dirfile', dir, [b'content'])
568
transform.new_file('dirFiLe', dir, [b'content'])
569
resolve_conflicts(transform)
571
self.assertPathExists('tree/file')
572
if not os.path.exists('tree/FiLe.moved'):
573
self.assertPathExists('tree/FiLe')
574
self.assertPathExists('tree/dir/dirfile')
575
if not os.path.exists('tree/dir/dirFiLe.moved'):
576
self.assertPathExists('tree/dir/dirFiLe')
578
def test_case_insensitive_limbo(self):
579
tree = self.make_branch_and_tree('tree')
580
# Don't try this at home, kids!
581
# Force the tree to report that it is case insensitive
582
tree.case_sensitive = False
583
transform = TreeTransform(tree)
584
self.addCleanup(transform.finalize)
585
dir = transform.new_directory('dir', transform.root)
586
first = transform.new_file('file', dir, [b'content'])
587
second = transform.new_file('FiLe', dir, [b'content'])
588
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
589
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
591
def test_adjust_path_updates_child_limbo_names(self):
592
tree = self.make_branch_and_tree('tree')
593
transform = TreeTransform(tree)
594
self.addCleanup(transform.finalize)
595
foo_id = transform.new_directory('foo', transform.root)
596
bar_id = transform.new_directory('bar', foo_id)
597
baz_id = transform.new_directory('baz', bar_id)
598
qux_id = transform.new_directory('qux', baz_id)
599
transform.adjust_path('quxx', foo_id, bar_id)
600
self.assertStartsWith(transform._limbo_name(qux_id),
601
transform._limbo_name(bar_id))
603
def test_add_del(self):
604
start, root = self.get_transform()
605
start.new_directory('a', root, b'a')
607
transform, root = self.get_transform()
608
transform.delete_versioned(transform.trans_id_tree_path('a'))
609
transform.new_directory('a', root, b'a')
612
def test_unversioning(self):
613
create_tree, root = self.get_transform()
614
parent_id = create_tree.new_directory('parent', root, b'parent-id')
615
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
617
unversion = TreeTransform(self.wt)
618
self.addCleanup(unversion.finalize)
619
parent = unversion.trans_id_tree_path('parent')
620
unversion.unversion_file(parent)
621
self.assertEqual(unversion.find_conflicts(),
622
[('unversioned parent', parent_id)])
623
file_id = unversion.trans_id_tree_path('parent/child')
624
unversion.unversion_file(file_id)
627
def test_name_invariants(self):
628
create_tree, root = self.get_transform()
630
root = create_tree.root
631
create_tree.new_file('name1', root, [b'hello1'], b'name1')
632
create_tree.new_file('name2', root, [b'hello2'], b'name2')
633
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
634
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
635
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
636
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
639
mangle_tree, root = self.get_transform()
640
root = mangle_tree.root
642
name1 = mangle_tree.trans_id_tree_path('name1')
643
name2 = mangle_tree.trans_id_tree_path('name2')
644
mangle_tree.adjust_path('name2', root, name1)
645
mangle_tree.adjust_path('name1', root, name2)
647
#tests for deleting parent directories
648
ddir = mangle_tree.trans_id_tree_path('dying_directory')
649
mangle_tree.delete_contents(ddir)
650
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
651
mangle_tree.delete_versioned(dfile)
652
mangle_tree.unversion_file(dfile)
653
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
654
mangle_tree.adjust_path('mfile', root, mfile)
656
#tests for adding parent directories
657
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
658
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
659
mangle_tree.adjust_path('mfile2', newdir, mfile2)
660
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
661
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
662
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
663
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
665
with open(self.wt.abspath('name1'), 'r') as f:
666
self.assertEqual(f.read(), 'hello2')
667
with open(self.wt.abspath('name2'), 'r') as f:
668
self.assertEqual(f.read(), 'hello1')
669
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
670
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
671
with open(mfile2_path, 'r') as f:
672
self.assertEqual(f.read(), 'later2')
673
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
674
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
675
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
676
with open(newfile_path, 'r') as f:
677
self.assertEqual(f.read(), 'hello3')
678
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
679
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
680
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
682
def test_both_rename(self):
683
create_tree, root = self.get_transform()
684
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
685
create_tree.new_file('blackbox.py', newdir, [b'hello1'], b'blackbox-id')
687
mangle_tree, root = self.get_transform()
688
selftest = mangle_tree.trans_id_tree_path('selftest')
689
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
690
mangle_tree.adjust_path('test', root, selftest)
691
mangle_tree.adjust_path('test_too_much', root, selftest)
692
mangle_tree.set_executability(True, blackbox)
695
def test_both_rename2(self):
696
create_tree, root = self.get_transform()
697
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
698
tests = create_tree.new_directory('tests', breezy, b'tests-id')
699
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
700
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
703
mangle_tree, root = self.get_transform()
704
breezy = mangle_tree.trans_id_tree_path('breezy')
705
tests = mangle_tree.trans_id_tree_path('breezy/tests')
706
test_too_much = mangle_tree.trans_id_tree_path('breezy/tests/blackbox/test_too_much.py')
707
mangle_tree.adjust_path('selftest', breezy, tests)
708
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
709
mangle_tree.set_executability(True, test_too_much)
712
def test_both_rename3(self):
713
create_tree, root = self.get_transform()
714
tests = create_tree.new_directory('tests', root, b'tests-id')
715
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
718
mangle_tree, root = self.get_transform()
719
tests = mangle_tree.trans_id_tree_path('tests')
720
test_too_much = mangle_tree.trans_id_tree_path('tests/test_too_much.py')
721
mangle_tree.adjust_path('selftest', root, tests)
722
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
723
mangle_tree.set_executability(True, test_too_much)
726
def test_move_dangling_ie(self):
727
create_tree, root = self.get_transform()
729
root = create_tree.root
730
create_tree.new_file('name1', root, [b'hello1'], b'name1')
732
delete_contents, root = self.get_transform()
733
file = delete_contents.trans_id_tree_path('name1')
734
delete_contents.delete_contents(file)
735
delete_contents.apply()
736
move_id, root = self.get_transform()
737
name1 = move_id.trans_id_tree_path('name1')
738
newdir = move_id.new_directory('dir', root, b'newdir')
739
move_id.adjust_path('name2', newdir, name1)
742
def test_replace_dangling_ie(self):
743
create_tree, root = self.get_transform()
745
root = create_tree.root
746
create_tree.new_file('name1', root, [b'hello1'], b'name1')
748
delete_contents = TreeTransform(self.wt)
749
self.addCleanup(delete_contents.finalize)
750
file = delete_contents.trans_id_tree_path('name1')
751
delete_contents.delete_contents(file)
752
delete_contents.apply()
753
delete_contents.finalize()
754
replace = TreeTransform(self.wt)
755
self.addCleanup(replace.finalize)
756
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
757
conflicts = replace.find_conflicts()
758
name1 = replace.trans_id_tree_path('name1')
759
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
760
resolve_conflicts(replace)
763
def _test_symlinks(self, link_name1, link_target1,
764
link_name2, link_target2):
766
def ozpath(p): return 'oz/' + p
768
self.requireFeature(SymlinkFeature)
769
transform, root = self.get_transform()
770
oz_id = transform.new_directory('oz', root, b'oz-id')
771
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
773
wiz_id = transform.create_path(link_name2, oz_id)
774
transform.create_symlink(link_target2, wiz_id)
775
transform.version_file(b'wiz-id2', wiz_id)
776
transform.set_executability(True, wiz_id)
777
self.assertEqual(transform.find_conflicts(),
778
[('non-file executability', wiz_id)])
779
transform.set_executability(None, wiz_id)
781
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
782
self.assertEqual('symlink',
783
file_kind(self.wt.abspath(ozpath(link_name1))))
784
self.assertEqual(link_target2,
785
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
786
self.assertEqual(link_target1,
787
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
789
def test_symlinks(self):
790
self._test_symlinks('wizard', 'wizard-target',
791
'wizard2', 'behind_curtain')
793
def test_symlinks_unicode(self):
794
self.requireFeature(features.UnicodeFilenameFeature)
795
self._test_symlinks(u'\N{Euro Sign}wizard',
796
u'wizard-targ\N{Euro Sign}t',
797
u'\N{Euro Sign}wizard2',
798
u'b\N{Euro Sign}hind_curtain')
800
def test_unable_create_symlink(self):
802
wt = self.make_branch_and_tree('.')
803
tt = TreeTransform(wt) # TreeTransform obtains write lock
805
tt.new_symlink('foo', tt.root, 'bar')
809
os_symlink = getattr(os, 'symlink', None)
812
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
814
"Unable to create symlink 'foo' on this platform",
818
os.symlink = os_symlink
820
def get_conflicted(self):
821
create, root = self.get_transform()
822
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
823
oz = create.new_directory('oz', root, b'oz-id')
824
create.new_directory('emeraldcity', oz, b'emerald-id')
826
conflicts, root = self.get_transform()
827
# set up duplicate entry, duplicate id
828
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
830
old_dorothy = conflicts.trans_id_tree_path('dorothy')
831
oz = conflicts.trans_id_tree_path('oz')
832
# set up DeletedParent parent conflict
833
conflicts.delete_versioned(oz)
834
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
835
# set up MissingParent conflict
836
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
837
conflicts.adjust_path('munchkincity', root, munchkincity)
838
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
840
conflicts.adjust_path('emeraldcity', emerald, emerald)
841
return conflicts, emerald, oz, old_dorothy, new_dorothy
843
def test_conflict_resolution(self):
844
conflicts, emerald, oz, old_dorothy, new_dorothy =\
845
self.get_conflicted()
846
resolve_conflicts(conflicts)
847
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
848
self.assertIs(conflicts.final_file_id(old_dorothy), None)
849
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
850
self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
851
self.assertEqual(conflicts.final_parent(emerald), oz)
854
def test_cook_conflicts(self):
855
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
856
raw_conflicts = resolve_conflicts(tt)
857
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
858
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
859
'dorothy', None, b'dorothy-id')
860
self.assertEqual(cooked_conflicts[0], duplicate)
861
duplicate_id = DuplicateID('Unversioned existing file',
862
'dorothy.moved', 'dorothy', None,
864
self.assertEqual(cooked_conflicts[1], duplicate_id)
865
missing_parent = MissingParent('Created directory', 'munchkincity',
867
deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
868
self.assertEqual(cooked_conflicts[2], missing_parent)
869
unversioned_parent = UnversionedParent('Versioned directory',
872
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
874
self.assertEqual(cooked_conflicts[3], unversioned_parent)
875
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
876
'oz/emeraldcity', b'emerald-id', b'emerald-id')
877
self.assertEqual(cooked_conflicts[4], deleted_parent)
878
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
879
self.assertEqual(cooked_conflicts[6], parent_loop)
880
self.assertEqual(len(cooked_conflicts), 7)
883
def test_string_conflicts(self):
884
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
885
raw_conflicts = resolve_conflicts(tt)
886
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
888
conflicts_s = [text_type(c) for c in cooked_conflicts]
889
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
890
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
891
'Moved existing file to '
893
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
894
'Unversioned existing file '
896
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
897
' munchkincity. Created directory.')
898
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
899
' versioned, but has versioned'
900
' children. Versioned directory.')
901
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
902
" is not empty. Not deleting.")
903
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
904
' versioned, but has versioned'
905
' children. Versioned directory.')
906
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
907
' oz/emeraldcity. Cancelled move.')
909
def prepare_wrong_parent_kind(self):
910
tt, root = self.get_transform()
911
tt.new_file('parent', root, [b'contents'], b'parent-id')
913
tt, root = self.get_transform()
914
parent_id = tt.trans_id_file_id(b'parent-id')
915
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
918
def test_find_conflicts_wrong_parent_kind(self):
919
tt = self.prepare_wrong_parent_kind()
922
def test_resolve_conflicts_wrong_existing_parent_kind(self):
923
tt = self.prepare_wrong_parent_kind()
924
raw_conflicts = resolve_conflicts(tt)
925
self.assertEqual({('non-directory parent', 'Created directory',
926
'new-3')}, raw_conflicts)
927
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
928
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
929
b'parent-id')], cooked_conflicts)
931
self.assertFalse(self.wt.is_versioned('parent'))
932
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
934
def test_resolve_conflicts_wrong_new_parent_kind(self):
935
tt, root = self.get_transform()
936
parent_id = tt.new_directory('parent', root, b'parent-id')
937
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
939
tt, root = self.get_transform()
940
parent_id = tt.trans_id_file_id(b'parent-id')
941
tt.delete_contents(parent_id)
942
tt.create_file([b'contents'], parent_id)
943
raw_conflicts = resolve_conflicts(tt)
944
self.assertEqual({('non-directory parent', 'Created directory',
945
'new-3')}, raw_conflicts)
947
self.assertFalse(self.wt.is_versioned('parent'))
948
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
950
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
951
tt, root = self.get_transform()
952
parent_id = tt.new_directory('parent', root)
953
tt.new_file('child,', parent_id, [b'contents2'])
955
tt, root = self.get_transform()
956
parent_id = tt.trans_id_tree_path('parent')
957
tt.delete_contents(parent_id)
958
tt.create_file([b'contents'], parent_id)
959
resolve_conflicts(tt)
961
self.assertFalse(self.wt.is_versioned('parent'))
962
self.assertFalse(self.wt.is_versioned('parent.new'))
964
def test_resolve_conflicts_missing_parent(self):
965
wt = self.make_branch_and_tree('.')
966
tt = TreeTransform(wt)
967
self.addCleanup(tt.finalize)
968
parent = tt.trans_id_file_id(b'parent-id')
969
tt.new_file('file', parent, [b'Contents'])
970
raw_conflicts = resolve_conflicts(tt)
971
# Since the directory doesn't exist it's seen as 'missing'. So
972
# 'resolve_conflicts' create a conflict asking for it to be created.
973
self.assertLength(1, raw_conflicts)
974
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
976
# apply fail since the missing directory doesn't exist
977
self.assertRaises(errors.NoFinalPath, tt.apply)
979
def test_moving_versioned_directories(self):
980
create, root = self.get_transform()
981
kansas = create.new_directory('kansas', root, b'kansas-id')
982
create.new_directory('house', kansas, b'house-id')
983
create.new_directory('oz', root, b'oz-id')
985
cyclone, root = self.get_transform()
986
oz = cyclone.trans_id_tree_path('oz')
987
house = cyclone.trans_id_tree_path('house')
988
cyclone.adjust_path('house', oz, house)
991
def test_moving_root(self):
992
create, root = self.get_transform()
993
fun = create.new_directory('fun', root, b'fun-id')
994
create.new_directory('sun', root, b'sun-id')
995
create.new_directory('moon', root, b'moon')
997
transform, root = self.get_transform()
998
transform.adjust_root_path('oldroot', fun)
999
new_root = transform.trans_id_tree_path('')
1000
transform.version_file(b'new-root', new_root)
1003
def test_renames(self):
1004
create, root = self.get_transform()
1005
old = create.new_directory('old-parent', root, b'old-id')
1006
intermediate = create.new_directory('intermediate', old, b'im-id')
1007
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1010
rename, root = self.get_transform()
1011
old = rename.trans_id_file_id(b'old-id')
1012
rename.adjust_path('new', root, old)
1013
myfile = rename.trans_id_file_id(b'myfile-id')
1014
rename.set_executability(True, myfile)
1017
def test_rename_fails(self):
1018
self.requireFeature(features.not_running_as_root)
1019
# see https://bugs.launchpad.net/bzr/+bug/491763
1020
create, root_id = self.get_transform()
1021
first_dir = create.new_directory('first-dir', root_id, b'first-id')
1022
myfile = create.new_file('myfile', root_id, [b'myfile-text'],
1025
if os.name == "posix" and sys.platform != "cygwin":
1026
# posix filesystems fail on renaming if the readonly bit is set
1027
osutils.make_readonly(self.wt.abspath('first-dir'))
1028
elif os.name == "nt":
1029
# windows filesystems fail on renaming open files
1030
self.addCleanup(open(self.wt.abspath('myfile')).close)
1032
self.skipTest("Can't force a permissions error on rename")
1033
# now transform to rename
1034
rename_transform, root_id = self.get_transform()
1035
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1036
dir_id = rename_transform.trans_id_file_id(b'first-id')
1037
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1038
e = self.assertRaises(errors.TransformRenameFailed,
1039
rename_transform.apply)
1040
# On nix looks like:
1041
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1042
# to .../first-dir/newname: [Errno 13] Permission denied"
1043
# On windows looks like:
1044
# "Failed to rename .../work/myfile to
1045
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1046
# This test isn't concerned with exactly what the error looks like,
1047
# and the strerror will vary across OS and locales, but the assert
1048
# that the exeception attributes are what we expect
1049
self.assertEqual(e.errno, errno.EACCES)
1050
if os.name == "posix":
1051
self.assertEndsWith(e.to_path, "/first-dir/newname")
1053
self.assertEqual(os.path.basename(e.from_path), "myfile")
1055
def test_set_executability_order(self):
1056
"""Ensure that executability behaves the same, no matter what order.
1058
- create file and set executability simultaneously
1059
- create file and set executability afterward
1060
- unsetting the executability of a file whose executability has not been
1061
declared should throw an exception (this may happen when a
1062
merge attempts to create a file with a duplicate ID)
1064
transform, root = self.get_transform()
1065
wt = transform._tree
1067
self.addCleanup(wt.unlock)
1068
transform.new_file('set_on_creation', root, [b'Set on creation'], b'soc',
1070
sac = transform.new_file('set_after_creation', root,
1071
[b'Set after creation'], b'sac')
1072
transform.set_executability(True, sac)
1073
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1075
self.assertRaises(KeyError, transform.set_executability, None, uws)
1077
self.assertTrue(wt.is_executable('set_on_creation'))
1078
self.assertTrue(wt.is_executable('set_after_creation'))
1080
def test_preserve_mode(self):
1081
"""File mode is preserved when replacing content"""
1082
if sys.platform == 'win32':
1083
raise TestSkipped('chmod has no effect on win32')
1084
transform, root = self.get_transform()
1085
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1087
self.wt.lock_write()
1088
self.addCleanup(self.wt.unlock)
1089
self.assertTrue(self.wt.is_executable('file1'))
1090
transform, root = self.get_transform()
1091
file1_id = transform.trans_id_tree_path('file1')
1092
transform.delete_contents(file1_id)
1093
transform.create_file([b'contents2'], file1_id)
1095
self.assertTrue(self.wt.is_executable('file1'))
1097
def test__set_mode_stats_correctly(self):
1098
"""_set_mode stats to determine file mode."""
1099
if sys.platform == 'win32':
1100
raise TestSkipped('chmod has no effect on win32')
1104
def instrumented_stat(path):
1105
stat_paths.append(path)
1106
return real_stat(path)
1108
transform, root = self.get_transform()
1110
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1111
file_id=b'bar-id-1', executable=False)
1114
transform, root = self.get_transform()
1115
bar1_id = transform.trans_id_tree_path('bar')
1116
bar2_id = transform.trans_id_tree_path('bar2')
1118
os.stat = instrumented_stat
1119
transform.create_file([b'bar2 contents\n'], bar2_id, mode_id=bar1_id)
1122
transform.finalize()
1124
bar1_abspath = self.wt.abspath('bar')
1125
self.assertEqual([bar1_abspath], stat_paths)
1127
def test_iter_changes(self):
1128
self.wt.set_root_id(b'eert_toor')
1129
transform, root = self.get_transform()
1130
transform.new_file('old', root, [b'blah'], b'id-1', True)
1132
transform, root = self.get_transform()
1134
self.assertEqual([], list(transform.iter_changes()))
1135
old = transform.trans_id_tree_path('old')
1136
transform.unversion_file(old)
1137
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1138
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1139
(True, True))], list(transform.iter_changes()))
1140
transform.new_directory('new', root, b'id-1')
1141
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1142
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1143
('file', 'directory'),
1144
(True, False))], list(transform.iter_changes()))
1146
transform.finalize()
1148
def test_iter_changes_new(self):
1149
self.wt.set_root_id(b'eert_toor')
1150
transform, root = self.get_transform()
1151
transform.new_file('old', root, [b'blah'])
1153
transform, root = self.get_transform()
1155
old = transform.trans_id_tree_path('old')
1156
transform.version_file(b'id-1', old)
1157
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1158
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1159
(False, False))], list(transform.iter_changes()))
1161
transform.finalize()
1163
def test_iter_changes_modifications(self):
1164
self.wt.set_root_id(b'eert_toor')
1165
transform, root = self.get_transform()
1166
transform.new_file('old', root, [b'blah'], b'id-1')
1167
transform.new_file('new', root, [b'blah'])
1168
transform.new_directory('subdir', root, b'subdir-id')
1170
transform, root = self.get_transform()
1172
old = transform.trans_id_tree_path('old')
1173
subdir = transform.trans_id_tree_path('subdir')
1174
new = transform.trans_id_tree_path('new')
1175
self.assertEqual([], list(transform.iter_changes()))
1178
transform.delete_contents(old)
1179
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1180
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', None),
1181
(False, False))], list(transform.iter_changes()))
1184
transform.create_file([b'blah'], old)
1185
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1186
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1187
(False, False))], list(transform.iter_changes()))
1188
transform.cancel_deletion(old)
1189
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1190
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1191
(False, False))], list(transform.iter_changes()))
1192
transform.cancel_creation(old)
1194
# move file_id to a different file
1195
self.assertEqual([], list(transform.iter_changes()))
1196
transform.unversion_file(old)
1197
transform.version_file(b'id-1', new)
1198
transform.adjust_path('old', root, new)
1199
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1200
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1201
(False, False))], list(transform.iter_changes()))
1202
transform.cancel_versioning(new)
1203
transform._removed_id = set()
1206
self.assertEqual([], list(transform.iter_changes()))
1207
transform.set_executability(True, old)
1208
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1209
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1210
(False, True))], list(transform.iter_changes()))
1211
transform.set_executability(None, old)
1214
self.assertEqual([], list(transform.iter_changes()))
1215
transform.adjust_path('new', root, old)
1216
transform._new_parent = {}
1217
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1218
(b'eert_toor', b'eert_toor'), ('old', 'new'), ('file', 'file'),
1219
(False, False))], list(transform.iter_changes()))
1220
transform._new_name = {}
1223
self.assertEqual([], list(transform.iter_changes()))
1224
transform.adjust_path('new', subdir, old)
1225
transform._new_name = {}
1226
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1227
(True, True), (b'eert_toor', b'subdir-id'), ('old', 'old'),
1228
('file', 'file'), (False, False))],
1229
list(transform.iter_changes()))
1230
transform._new_path = {}
1233
transform.finalize()
1235
def test_iter_changes_modified_bleed(self):
1236
self.wt.set_root_id(b'eert_toor')
1237
"""Modified flag should not bleed from one change to another"""
1238
# unfortunately, we have no guarantee that file1 (which is modified)
1239
# will be applied before file2. And if it's applied after file2, it
1240
# obviously can't bleed into file2's change output. But for now, it
1242
transform, root = self.get_transform()
1243
transform.new_file('file1', root, [b'blah'], b'id-1')
1244
transform.new_file('file2', root, [b'blah'], b'id-2')
1246
transform, root = self.get_transform()
1248
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1249
transform.set_executability(True,
1250
transform.trans_id_file_id(b'id-2'))
1251
self.assertEqual([(b'id-1', (u'file1', u'file1'), True, (True, True),
1252
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1253
('file', None), (False, False)),
1254
(b'id-2', (u'file2', u'file2'), False, (True, True),
1255
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1256
('file', 'file'), (False, True))],
1257
list(transform.iter_changes()))
1259
transform.finalize()
1261
def test_iter_changes_move_missing(self):
1262
"""Test moving ids with no files around"""
1263
self.wt.set_root_id(b'toor_eert')
1264
# Need two steps because versioning a non-existant file is a conflict.
1265
transform, root = self.get_transform()
1266
transform.new_directory('floater', root, b'floater-id')
1268
transform, root = self.get_transform()
1269
transform.delete_contents(transform.trans_id_tree_path('floater'))
1271
transform, root = self.get_transform()
1272
floater = transform.trans_id_tree_path('floater')
1274
transform.adjust_path('flitter', root, floater)
1275
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1276
(True, True), (b'toor_eert', b'toor_eert'), ('floater', 'flitter'),
1277
(None, None), (False, False))], list(transform.iter_changes()))
1279
transform.finalize()
1281
def test_iter_changes_pointless(self):
1282
"""Ensure that no-ops are not treated as modifications"""
1283
self.wt.set_root_id(b'eert_toor')
1284
transform, root = self.get_transform()
1285
transform.new_file('old', root, [b'blah'], b'id-1')
1286
transform.new_directory('subdir', root, b'subdir-id')
1288
transform, root = self.get_transform()
1290
old = transform.trans_id_tree_path('old')
1291
subdir = transform.trans_id_tree_path('subdir')
1292
self.assertEqual([], list(transform.iter_changes()))
1293
transform.delete_contents(subdir)
1294
transform.create_directory(subdir)
1295
transform.set_executability(False, old)
1296
transform.unversion_file(old)
1297
transform.version_file(b'id-1', old)
1298
transform.adjust_path('old', root, old)
1299
self.assertEqual([], list(transform.iter_changes()))
1301
transform.finalize()
1303
def test_rename_count(self):
1304
transform, root = self.get_transform()
1305
transform.new_file('name1', root, [b'contents'])
1306
self.assertEqual(transform.rename_count, 0)
1308
self.assertEqual(transform.rename_count, 1)
1309
transform2, root = self.get_transform()
1310
transform2.adjust_path('name2', root,
1311
transform2.trans_id_tree_path('name1'))
1312
self.assertEqual(transform2.rename_count, 0)
1314
self.assertEqual(transform2.rename_count, 2)
1316
def test_change_parent(self):
1317
"""Ensure that after we change a parent, the results are still right.
1319
Renames and parent changes on pending transforms can happen as part
1320
of conflict resolution, and are explicitly permitted by the
1323
This test ensures they work correctly with the rename-avoidance
1326
transform, root = self.get_transform()
1327
parent1 = transform.new_directory('parent1', root)
1328
child1 = transform.new_file('child1', parent1, [b'contents'])
1329
parent2 = transform.new_directory('parent2', root)
1330
transform.adjust_path('child1', parent2, child1)
1332
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1333
self.assertPathExists(self.wt.abspath('parent2/child1'))
1334
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1335
# no rename for child1 (counting only renames during apply)
1336
self.assertEqual(2, transform.rename_count)
1338
def test_cancel_parent(self):
1339
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1341
This is like the test_change_parent, except that we cancel the parent
1342
before adjusting the path. The transform must detect that the
1343
directory is non-empty, and move children to safe locations.
1345
transform, root = self.get_transform()
1346
parent1 = transform.new_directory('parent1', root)
1347
child1 = transform.new_file('child1', parent1, [b'contents'])
1348
child2 = transform.new_file('child2', parent1, [b'contents'])
1350
transform.cancel_creation(parent1)
1352
self.fail('Failed to move child1 before deleting parent1')
1353
transform.cancel_creation(child2)
1354
transform.create_directory(parent1)
1356
transform.cancel_creation(parent1)
1357
# If the transform incorrectly believes that child2 is still in
1358
# parent1's limbo directory, it will try to rename it and fail
1359
# because was already moved by the first cancel_creation.
1361
self.fail('Transform still thinks child2 is a child of parent1')
1362
parent2 = transform.new_directory('parent2', root)
1363
transform.adjust_path('child1', parent2, child1)
1365
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1366
self.assertPathExists(self.wt.abspath('parent2/child1'))
1367
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1368
self.assertEqual(2, transform.rename_count)
1370
def test_adjust_and_cancel(self):
1371
"""Make sure adjust_path keeps track of limbo children properly"""
1372
transform, root = self.get_transform()
1373
parent1 = transform.new_directory('parent1', root)
1374
child1 = transform.new_file('child1', parent1, [b'contents'])
1375
parent2 = transform.new_directory('parent2', root)
1376
transform.adjust_path('child1', parent2, child1)
1377
transform.cancel_creation(child1)
1379
transform.cancel_creation(parent1)
1380
# if the transform thinks child1 is still in parent1's limbo
1381
# directory, it will attempt to move it and fail.
1383
self.fail('Transform still thinks child1 is a child of parent1')
1384
transform.finalize()
1386
def test_noname_contents(self):
1387
"""TreeTransform should permit deferring naming files."""
1388
transform, root = self.get_transform()
1389
parent = transform.trans_id_file_id(b'parent-id')
1391
transform.create_directory(parent)
1393
self.fail("Can't handle contents with no name")
1394
transform.finalize()
1396
def test_noname_contents_nested(self):
1397
"""TreeTransform should permit deferring naming files."""
1398
transform, root = self.get_transform()
1399
parent = transform.trans_id_file_id(b'parent-id')
1401
transform.create_directory(parent)
1403
self.fail("Can't handle contents with no name")
1404
child = transform.new_directory('child', parent)
1405
transform.adjust_path('parent', root, parent)
1407
self.assertPathExists(self.wt.abspath('parent/child'))
1408
self.assertEqual(1, transform.rename_count)
1410
def test_reuse_name(self):
1411
"""Avoid reusing the same limbo name for different files"""
1412
transform, root = self.get_transform()
1413
parent = transform.new_directory('parent', root)
1414
child1 = transform.new_directory('child', parent)
1416
child2 = transform.new_directory('child', parent)
1418
self.fail('Tranform tried to use the same limbo name twice')
1419
transform.adjust_path('child2', parent, child2)
1421
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1422
# child2 is put into top-level limbo because child1 has already
1423
# claimed the direct limbo path when child2 is created. There is no
1424
# advantage in renaming files once they're in top-level limbo, except
1426
self.assertEqual(2, transform.rename_count)
1428
def test_reuse_when_first_moved(self):
1429
"""Don't avoid direct paths when it is safe to use them"""
1430
transform, root = self.get_transform()
1431
parent = transform.new_directory('parent', root)
1432
child1 = transform.new_directory('child', parent)
1433
transform.adjust_path('child1', parent, child1)
1434
child2 = transform.new_directory('child', parent)
1436
# limbo/new-1 => parent
1437
self.assertEqual(1, transform.rename_count)
1439
def test_reuse_after_cancel(self):
1440
"""Don't avoid direct paths when it is safe to use them"""
1441
transform, root = self.get_transform()
1442
parent2 = transform.new_directory('parent2', root)
1443
child1 = transform.new_directory('child1', parent2)
1444
transform.cancel_creation(parent2)
1445
transform.create_directory(parent2)
1446
child2 = transform.new_directory('child1', parent2)
1447
transform.adjust_path('child2', parent2, child1)
1449
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1450
self.assertEqual(2, transform.rename_count)
1452
def test_finalize_order(self):
1453
"""Finalize must be done in child-to-parent order"""
1454
transform, root = self.get_transform()
1455
parent = transform.new_directory('parent', root)
1456
child = transform.new_directory('child', parent)
1458
transform.finalize()
1460
self.fail('Tried to remove parent before child1')
1462
def test_cancel_with_cancelled_child_should_succeed(self):
1463
transform, root = self.get_transform()
1464
parent = transform.new_directory('parent', root)
1465
child = transform.new_directory('child', parent)
1466
transform.cancel_creation(child)
1467
transform.cancel_creation(parent)
1468
transform.finalize()
1470
def test_rollback_on_directory_clash(self):
1472
wt = self.make_branch_and_tree('.')
1473
tt = TreeTransform(wt) # TreeTransform obtains write lock
1475
foo = tt.new_directory('foo', tt.root)
1476
tt.new_file('bar', foo, [b'foobar'])
1477
baz = tt.new_directory('baz', tt.root)
1478
tt.new_file('qux', baz, [b'quux'])
1479
# Ask for a rename 'foo' -> 'baz'
1480
tt.adjust_path('baz', tt.root, foo)
1481
# Lie to tt that we've already resolved all conflicts.
1482
tt.apply(no_conflicts=True)
1486
# The rename will fail because the target directory is not empty (but
1487
# raises FileExists anyway).
1488
err = self.assertRaises(errors.FileExists, tt_helper)
1489
self.assertEndsWith(err.path, "/baz")
1491
def test_two_directories_clash(self):
1493
wt = self.make_branch_and_tree('.')
1494
tt = TreeTransform(wt) # TreeTransform obtains write lock
1496
foo_1 = tt.new_directory('foo', tt.root)
1497
tt.new_directory('bar', foo_1)
1498
# Adding the same directory with a different content
1499
foo_2 = tt.new_directory('foo', tt.root)
1500
tt.new_directory('baz', foo_2)
1501
# Lie to tt that we've already resolved all conflicts.
1502
tt.apply(no_conflicts=True)
1506
err = self.assertRaises(errors.FileExists, tt_helper)
1507
self.assertEndsWith(err.path, "/foo")
1509
def test_two_directories_clash_finalize(self):
1511
wt = self.make_branch_and_tree('.')
1512
tt = TreeTransform(wt) # TreeTransform obtains write lock
1514
foo_1 = tt.new_directory('foo', tt.root)
1515
tt.new_directory('bar', foo_1)
1516
# Adding the same directory with a different content
1517
foo_2 = tt.new_directory('foo', tt.root)
1518
tt.new_directory('baz', foo_2)
1519
# Lie to tt that we've already resolved all conflicts.
1520
tt.apply(no_conflicts=True)
1524
err = self.assertRaises(errors.FileExists, tt_helper)
1525
self.assertEndsWith(err.path, "/foo")
1527
def test_file_to_directory(self):
1528
wt = self.make_branch_and_tree('.')
1529
self.build_tree(['foo'])
1532
tt = TreeTransform(wt)
1533
self.addCleanup(tt.finalize)
1534
foo_trans_id = tt.trans_id_tree_path("foo")
1535
tt.delete_contents(foo_trans_id)
1536
tt.create_directory(foo_trans_id)
1537
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1538
tt.create_file([b"aa\n"], bar_trans_id)
1539
tt.version_file(b"bar-1", bar_trans_id)
1541
self.assertPathExists("foo/bar")
1544
self.assertEqual(wt.kind("foo"), "directory")
1548
changes = wt.changes_from(wt.basis_tree())
1549
self.assertFalse(changes.has_changed(), changes)
1551
def test_file_to_symlink(self):
1552
self.requireFeature(SymlinkFeature)
1553
wt = self.make_branch_and_tree('.')
1554
self.build_tree(['foo'])
1557
tt = TreeTransform(wt)
1558
self.addCleanup(tt.finalize)
1559
foo_trans_id = tt.trans_id_tree_path("foo")
1560
tt.delete_contents(foo_trans_id)
1561
tt.create_symlink("bar", foo_trans_id)
1563
self.assertPathExists("foo")
1565
self.addCleanup(wt.unlock)
1566
self.assertEqual(wt.kind("foo"), "symlink")
1568
def test_dir_to_file(self):
1569
wt = self.make_branch_and_tree('.')
1570
self.build_tree(['foo/', 'foo/bar'])
1571
wt.add(['foo', 'foo/bar'])
1573
tt = TreeTransform(wt)
1574
self.addCleanup(tt.finalize)
1575
foo_trans_id = tt.trans_id_tree_path("foo")
1576
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1577
tt.delete_contents(foo_trans_id)
1578
tt.delete_versioned(bar_trans_id)
1579
tt.create_file([b"aa\n"], foo_trans_id)
1581
self.assertPathExists("foo")
1583
self.addCleanup(wt.unlock)
1584
self.assertEqual(wt.kind("foo"), "file")
1586
def test_dir_to_hardlink(self):
1587
self.requireFeature(HardlinkFeature)
1588
wt = self.make_branch_and_tree('.')
1589
self.build_tree(['foo/', 'foo/bar'])
1590
wt.add(['foo', 'foo/bar'])
1592
tt = TreeTransform(wt)
1593
self.addCleanup(tt.finalize)
1594
foo_trans_id = tt.trans_id_tree_path("foo")
1595
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1596
tt.delete_contents(foo_trans_id)
1597
tt.delete_versioned(bar_trans_id)
1598
self.build_tree(['baz'])
1599
tt.create_hardlink("baz", foo_trans_id)
1601
self.assertPathExists("foo")
1602
self.assertPathExists("baz")
1604
self.addCleanup(wt.unlock)
1605
self.assertEqual(wt.kind("foo"), "file")
1607
def test_no_final_path(self):
1608
transform, root = self.get_transform()
1609
trans_id = transform.trans_id_file_id(b'foo')
1610
transform.create_file([b'bar'], trans_id)
1611
transform.cancel_creation(trans_id)
1614
def test_create_from_tree(self):
1615
tree1 = self.make_branch_and_tree('tree1')
1616
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1617
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1618
tree2 = self.make_branch_and_tree('tree2')
1619
tt = TreeTransform(tree2)
1620
foo_trans_id = tt.create_path('foo', tt.root)
1621
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1622
bar_trans_id = tt.create_path('bar', tt.root)
1623
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id=b'bar-id')
1625
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1626
self.assertFileEqual(b'baz', 'tree2/bar')
1628
def test_create_from_tree_bytes(self):
1629
"""Provided lines are used instead of tree content."""
1630
tree1 = self.make_branch_and_tree('tree1')
1631
self.build_tree_contents([('tree1/foo', b'bar'),])
1632
tree1.add('foo', b'foo-id')
1633
tree2 = self.make_branch_and_tree('tree2')
1634
tt = TreeTransform(tree2)
1635
foo_trans_id = tt.create_path('foo', tt.root)
1636
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id',
1639
self.assertFileEqual(b'qux', 'tree2/foo')
1641
def test_create_from_tree_symlink(self):
1642
self.requireFeature(SymlinkFeature)
1643
tree1 = self.make_branch_and_tree('tree1')
1644
os.symlink('bar', 'tree1/foo')
1645
tree1.add('foo', b'foo-id')
1646
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1647
foo_trans_id = tt.create_path('foo', tt.root)
1648
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1650
self.assertEqual('bar', os.readlink('tree2/foo'))
1653
class TransformGroup(object):
1655
def __init__(self, dirname, root_id):
1658
self.wt = ControlDir.create_standalone_workingtree(dirname)
1659
self.wt.set_root_id(root_id)
1660
self.b = self.wt.branch
1661
self.tt = TreeTransform(self.wt)
1662
self.root = self.tt.trans_id_tree_path('')
1665
def conflict_text(tree, merge):
1666
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1667
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1670
class TestInventoryAltered(tests.TestCaseWithTransport):
1672
def test_inventory_altered_unchanged(self):
1673
tree = self.make_branch_and_tree('tree')
1674
self.build_tree(['tree/foo'])
1675
tree.add('foo', b'foo-id')
1676
with TransformPreview(tree) as tt:
1677
self.assertEqual([], tt._inventory_altered())
1679
def test_inventory_altered_changed_parent_id(self):
1680
tree = self.make_branch_and_tree('tree')
1681
self.build_tree(['tree/foo'])
1682
tree.add('foo', b'foo-id')
1683
with TransformPreview(tree) as tt:
1684
tt.unversion_file(tt.root)
1685
tt.version_file(b'new-id', tt.root)
1686
foo_trans_id = tt.trans_id_tree_path('foo')
1687
foo_tuple = ('foo', foo_trans_id)
1688
root_tuple = ('', tt.root)
1689
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1691
def test_inventory_altered_noop_changed_parent_id(self):
1692
tree = self.make_branch_and_tree('tree')
1693
self.build_tree(['tree/foo'])
1694
tree.add('foo', b'foo-id')
1695
with TransformPreview(tree) as tt:
1696
tt.unversion_file(tt.root)
1697
tt.version_file(tree.get_root_id(), tt.root)
1698
foo_trans_id = tt.trans_id_tree_path('foo')
1699
self.assertEqual([], tt._inventory_altered())
1702
class TestTransformMerge(TestCaseInTempDir):
1704
def test_text_merge(self):
1705
root_id = generate_ids.gen_root_id()
1706
base = TransformGroup("base", root_id)
1707
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1708
base.tt.new_file('b', base.root, [b'b1'], b'b')
1709
base.tt.new_file('c', base.root, [b'c'], b'c')
1710
base.tt.new_file('d', base.root, [b'd'], b'd')
1711
base.tt.new_file('e', base.root, [b'e'], b'e')
1712
base.tt.new_file('f', base.root, [b'f'], b'f')
1713
base.tt.new_directory('g', base.root, b'g')
1714
base.tt.new_directory('h', base.root, b'h')
1716
other = TransformGroup("other", root_id)
1717
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1718
other.tt.new_file('b', other.root, [b'b2'], b'b')
1719
other.tt.new_file('c', other.root, [b'c2'], b'c')
1720
other.tt.new_file('d', other.root, [b'd'], b'd')
1721
other.tt.new_file('e', other.root, [b'e2'], b'e')
1722
other.tt.new_file('f', other.root, [b'f'], b'f')
1723
other.tt.new_file('g', other.root, [b'g'], b'g')
1724
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1725
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
1727
this = TransformGroup("this", root_id)
1728
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1729
this.tt.new_file('b', this.root, [b'b'], b'b')
1730
this.tt.new_file('c', this.root, [b'c'], b'c')
1731
this.tt.new_file('d', this.root, [b'd2'], b'd')
1732
this.tt.new_file('e', this.root, [b'e2'], b'e')
1733
this.tt.new_file('f', this.root, [b'f'], b'f')
1734
this.tt.new_file('g', this.root, [b'g'], b'g')
1735
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1736
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
1738
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1741
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1742
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
1743
# three-way text conflict
1744
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1745
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
1747
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
1749
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
1750
# Ambigious clean merge
1751
self.assertEqual(this.wt.get_file(this.wt.id2path(b'e')).read(), b'e2')
1753
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1754
# Correct correct results when THIS == OTHER
1755
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
1756
# Text conflict when THIS & OTHER are text and BASE is dir
1757
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1758
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1759
self.assertEqual(this.wt.get_file('h.THIS').read(),
1761
self.assertEqual(this.wt.get_file('h.OTHER').read(),
1763
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1764
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1765
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1766
self.assertEqual(this.wt.get_file('i.THIS').read(),
1768
self.assertEqual(this.wt.get_file('i.OTHER').read(),
1770
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1771
modified = ['a', 'b', 'c', 'h', 'i']
1772
merge_modified = this.wt.merge_modified()
1773
self.assertSubset(merge_modified, modified)
1774
self.assertEqual(len(merge_modified), len(modified))
1775
with open(this.wt.abspath(this.wt.id2path('a')), 'wb') as f: f.write(b'booga')
1777
merge_modified = this.wt.merge_modified()
1778
self.assertSubset(merge_modified, modified)
1779
self.assertEqual(len(merge_modified), len(modified))
1783
def test_file_merge(self):
1784
self.requireFeature(SymlinkFeature)
1785
root_id = generate_ids.gen_root_id()
1786
base = TransformGroup("BASE", root_id)
1787
this = TransformGroup("THIS", root_id)
1788
other = TransformGroup("OTHER", root_id)
1789
for tg in this, base, other:
1790
tg.tt.new_directory('a', tg.root, b'a')
1791
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1792
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1793
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1794
targets = ((base, 'base-e', 'base-f', None, None),
1795
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1796
(other, 'other-e', None, 'other-g', 'other-h'))
1797
for tg, e_target, f_target, g_target, h_target in targets:
1798
for link, target in (('e', e_target), ('f', f_target),
1799
('g', g_target), ('h', h_target)):
1800
if target is not None:
1801
tg.tt.new_symlink(link, tg.root, target, link.encode('ascii'))
1803
for tg in this, base, other:
1805
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1806
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1807
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1808
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1809
for suffix in ('THIS', 'BASE', 'OTHER'):
1810
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1811
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1812
self.assertEqual(this.wt.id2path(b'd'), 'd.OTHER')
1813
self.assertEqual(this.wt.id2path(b'f'), 'f.THIS')
1814
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1815
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1816
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1817
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1818
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1819
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1820
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1821
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1822
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1823
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1825
def test_filename_merge(self):
1826
root_id = generate_ids.gen_root_id()
1827
base = TransformGroup("BASE", root_id)
1828
this = TransformGroup("THIS", root_id)
1829
other = TransformGroup("OTHER", root_id)
1830
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1831
for t in [base, this, other]]
1832
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1833
for t in [base, this, other]]
1834
base.tt.new_directory('c', base_a, b'c')
1835
this.tt.new_directory('c1', this_a, b'c')
1836
other.tt.new_directory('c', other_b, b'c')
1838
base.tt.new_directory('d', base_a, b'd')
1839
this.tt.new_directory('d1', this_b, b'd')
1840
other.tt.new_directory('d', other_a, b'd')
1842
base.tt.new_directory('e', base_a, b'e')
1843
this.tt.new_directory('e', this_a, b'e')
1844
other.tt.new_directory('e1', other_b, b'e')
1846
base.tt.new_directory('f', base_a, b'f')
1847
this.tt.new_directory('f1', this_b, b'f')
1848
other.tt.new_directory('f1', other_b, b'f')
1850
for tg in [this, base, other]:
1852
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1853
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1854
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1855
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1856
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
1858
def test_filename_merge_conflicts(self):
1859
root_id = generate_ids.gen_root_id()
1860
base = TransformGroup("BASE", root_id)
1861
this = TransformGroup("THIS", root_id)
1862
other = TransformGroup("OTHER", root_id)
1863
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1864
for t in [base, this, other]]
1865
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1866
for t in [base, this, other]]
1868
base.tt.new_file('g', base_a, [b'g'], b'g')
1869
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1871
base.tt.new_file('h', base_a, [b'h'], b'h')
1872
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1874
base.tt.new_file('i', base.root, [b'i'], b'i')
1875
other.tt.new_directory('i1', this_b, b'i')
1877
for tg in [this, base, other]:
1879
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1881
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
1882
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1883
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1884
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
1885
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1886
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1887
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1890
class TestBuildTree(tests.TestCaseWithTransport):
1892
def test_build_tree_with_symlinks(self):
1893
self.requireFeature(SymlinkFeature)
1895
a = ControlDir.create_standalone_workingtree('a')
1897
with open('a/foo/bar', 'wb') as f: f.write(b'contents')
1898
os.symlink('a/foo/bar', 'a/foo/baz')
1899
a.add(['foo', 'foo/bar', 'foo/baz'])
1900
a.commit('initial commit')
1901
b = ControlDir.create_standalone_workingtree('b')
1902
basis = a.basis_tree()
1904
self.addCleanup(basis.unlock)
1905
build_tree(basis, b)
1906
self.assertIs(os.path.isdir('b/foo'), True)
1907
with open('b/foo/bar', 'rb') as f:
1908
self.assertEqual(f.read(), b"contents")
1909
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1911
def test_build_with_references(self):
1912
tree = self.make_branch_and_tree('source',
1913
format='development-subtree')
1914
subtree = self.make_branch_and_tree('source/subtree',
1915
format='development-subtree')
1916
tree.add_reference(subtree)
1917
tree.commit('a revision')
1918
tree.branch.create_checkout('target')
1919
self.assertPathExists('target')
1920
self.assertPathExists('target/subtree')
1922
def test_file_conflict_handling(self):
1923
"""Ensure that when building trees, conflict handling is done"""
1924
source = self.make_branch_and_tree('source')
1925
target = self.make_branch_and_tree('target')
1926
self.build_tree(['source/file', 'target/file'])
1927
source.add('file', b'new-file')
1928
source.commit('added file')
1929
build_tree(source.basis_tree(), target)
1930
self.assertEqual([DuplicateEntry('Moved existing file to',
1931
'file.moved', 'file', None, 'new-file')],
1933
target2 = self.make_branch_and_tree('target2')
1934
with open('target2/file', 'wb') as target_file, \
1935
open('source/file', 'rb') as source_file:
1936
target_file.write(source_file.read())
1937
build_tree(source.basis_tree(), target2)
1938
self.assertEqual([], target2.conflicts())
1940
def test_symlink_conflict_handling(self):
1941
"""Ensure that when building trees, conflict handling is done"""
1942
self.requireFeature(SymlinkFeature)
1943
source = self.make_branch_and_tree('source')
1944
os.symlink('foo', 'source/symlink')
1945
source.add('symlink', b'new-symlink')
1946
source.commit('added file')
1947
target = self.make_branch_and_tree('target')
1948
os.symlink('bar', 'target/symlink')
1949
build_tree(source.basis_tree(), target)
1950
self.assertEqual([DuplicateEntry('Moved existing file to',
1951
'symlink.moved', 'symlink', None, 'new-symlink')],
1953
target = self.make_branch_and_tree('target2')
1954
os.symlink('foo', 'target2/symlink')
1955
build_tree(source.basis_tree(), target)
1956
self.assertEqual([], target.conflicts())
1958
def test_directory_conflict_handling(self):
1959
"""Ensure that when building trees, conflict handling is done"""
1960
source = self.make_branch_and_tree('source')
1961
target = self.make_branch_and_tree('target')
1962
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1963
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
1964
source.commit('added file')
1965
build_tree(source.basis_tree(), target)
1966
self.assertEqual([], target.conflicts())
1967
self.assertPathExists('target/dir1/file')
1969
# Ensure contents are merged
1970
target = self.make_branch_and_tree('target2')
1971
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1972
build_tree(source.basis_tree(), target)
1973
self.assertEqual([], target.conflicts())
1974
self.assertPathExists('target2/dir1/file2')
1975
self.assertPathExists('target2/dir1/file')
1977
# Ensure new contents are suppressed for existing branches
1978
target = self.make_branch_and_tree('target3')
1979
self.make_branch('target3/dir1')
1980
self.build_tree(['target3/dir1/file2'])
1981
build_tree(source.basis_tree(), target)
1982
self.assertPathDoesNotExist('target3/dir1/file')
1983
self.assertPathExists('target3/dir1/file2')
1984
self.assertPathExists('target3/dir1.diverted/file')
1985
self.assertEqual([DuplicateEntry('Diverted to',
1986
'dir1.diverted', 'dir1', 'new-dir1', None)],
1989
target = self.make_branch_and_tree('target4')
1990
self.build_tree(['target4/dir1/'])
1991
self.make_branch('target4/dir1/file')
1992
build_tree(source.basis_tree(), target)
1993
self.assertPathExists('target4/dir1/file')
1994
self.assertEqual('directory', file_kind('target4/dir1/file'))
1995
self.assertPathExists('target4/dir1/file.diverted')
1996
self.assertEqual([DuplicateEntry('Diverted to',
1997
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
2000
def test_mixed_conflict_handling(self):
2001
"""Ensure that when building trees, conflict handling is done"""
2002
source = self.make_branch_and_tree('source')
2003
target = self.make_branch_and_tree('target')
2004
self.build_tree(['source/name', 'target/name/'])
2005
source.add('name', b'new-name')
2006
source.commit('added file')
2007
build_tree(source.basis_tree(), target)
2008
self.assertEqual([DuplicateEntry('Moved existing file to',
2009
'name.moved', 'name', None, 'new-name')], target.conflicts())
2011
def test_raises_in_populated(self):
2012
source = self.make_branch_and_tree('source')
2013
self.build_tree(['source/name'])
2015
source.commit('added name')
2016
target = self.make_branch_and_tree('target')
2017
self.build_tree(['target/name'])
2019
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2020
build_tree, source.basis_tree(), target)
2022
def test_build_tree_rename_count(self):
2023
source = self.make_branch_and_tree('source')
2024
self.build_tree(['source/file1', 'source/dir1/'])
2025
source.add(['file1', 'dir1'])
2026
source.commit('add1')
2027
target1 = self.make_branch_and_tree('target1')
2028
transform_result = build_tree(source.basis_tree(), target1)
2029
self.assertEqual(2, transform_result.rename_count)
2031
self.build_tree(['source/dir1/file2'])
2032
source.add(['dir1/file2'])
2033
source.commit('add3')
2034
target2 = self.make_branch_and_tree('target2')
2035
transform_result = build_tree(source.basis_tree(), target2)
2036
# children of non-root directories should not be renamed
2037
self.assertEqual(2, transform_result.rename_count)
2039
def create_ab_tree(self):
2040
"""Create a committed test tree with two files"""
2041
source = self.make_branch_and_tree('source')
2042
self.build_tree_contents([('source/file1', b'A')])
2043
self.build_tree_contents([('source/file2', b'B')])
2044
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2045
source.commit('commit files')
2047
self.addCleanup(source.unlock)
2050
def test_build_tree_accelerator_tree(self):
2051
source = self.create_ab_tree()
2052
self.build_tree_contents([('source/file2', b'C')])
2054
real_source_get_file = source.get_file
2055
def get_file(path, file_id=None):
2056
calls.append(file_id)
2057
return real_source_get_file(path, file_id)
2058
source.get_file = get_file
2059
target = self.make_branch_and_tree('target')
2060
revision_tree = source.basis_tree()
2061
revision_tree.lock_read()
2062
self.addCleanup(revision_tree.unlock)
2063
build_tree(revision_tree, target, source)
2064
self.assertEqual([b'file1-id'], calls)
2066
self.addCleanup(target.unlock)
2067
self.assertEqual([], list(target.iter_changes(revision_tree)))
2069
def test_build_tree_accelerator_tree_observes_sha1(self):
2070
source = self.create_ab_tree()
2071
sha1 = osutils.sha_string(b'A')
2072
target = self.make_branch_and_tree('target')
2074
self.addCleanup(target.unlock)
2075
state = target.current_dirstate()
2076
state._cutoff_time = time.time() + 60
2077
build_tree(source.basis_tree(), target, source)
2078
entry = state._get_entry(0, path_utf8=b'file1')
2079
self.assertEqual(sha1, entry[1][0][1])
2081
def test_build_tree_accelerator_tree_missing_file(self):
2082
source = self.create_ab_tree()
2083
os.unlink('source/file1')
2084
source.remove(['file2'])
2085
target = self.make_branch_and_tree('target')
2086
revision_tree = source.basis_tree()
2087
revision_tree.lock_read()
2088
self.addCleanup(revision_tree.unlock)
2089
build_tree(revision_tree, target, source)
2091
self.addCleanup(target.unlock)
2092
self.assertEqual([], list(target.iter_changes(revision_tree)))
2094
def test_build_tree_accelerator_wrong_kind(self):
2095
self.requireFeature(SymlinkFeature)
2096
source = self.make_branch_and_tree('source')
2097
self.build_tree_contents([('source/file1', b'')])
2098
self.build_tree_contents([('source/file2', b'')])
2099
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2100
source.commit('commit files')
2101
os.unlink('source/file2')
2102
self.build_tree_contents([('source/file2/', b'C')])
2103
os.unlink('source/file1')
2104
os.symlink('file2', 'source/file1')
2106
real_source_get_file = source.get_file
2107
def get_file(path, file_id=None):
2108
calls.append(file_id)
2109
return real_source_get_file(path, file_id)
2110
source.get_file = get_file
2111
target = self.make_branch_and_tree('target')
2112
revision_tree = source.basis_tree()
2113
revision_tree.lock_read()
2114
self.addCleanup(revision_tree.unlock)
2115
build_tree(revision_tree, target, source)
2116
self.assertEqual([], calls)
2118
self.addCleanup(target.unlock)
2119
self.assertEqual([], list(target.iter_changes(revision_tree)))
2121
def test_build_tree_hardlink(self):
2122
self.requireFeature(HardlinkFeature)
2123
source = self.create_ab_tree()
2124
target = self.make_branch_and_tree('target')
2125
revision_tree = source.basis_tree()
2126
revision_tree.lock_read()
2127
self.addCleanup(revision_tree.unlock)
2128
build_tree(revision_tree, target, source, hardlink=True)
2130
self.addCleanup(target.unlock)
2131
self.assertEqual([], list(target.iter_changes(revision_tree)))
2132
source_stat = os.stat('source/file1')
2133
target_stat = os.stat('target/file1')
2134
self.assertEqual(source_stat, target_stat)
2136
# Explicitly disallowing hardlinks should prevent them.
2137
target2 = self.make_branch_and_tree('target2')
2138
build_tree(revision_tree, target2, source, hardlink=False)
2140
self.addCleanup(target2.unlock)
2141
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2142
source_stat = os.stat('source/file1')
2143
target2_stat = os.stat('target2/file1')
2144
self.assertNotEqual(source_stat, target2_stat)
2146
def test_build_tree_accelerator_tree_moved(self):
2147
source = self.make_branch_and_tree('source')
2148
self.build_tree_contents([('source/file1', b'A')])
2149
source.add(['file1'], [b'file1-id'])
2150
source.commit('commit files')
2151
source.rename_one('file1', 'file2')
2153
self.addCleanup(source.unlock)
2154
target = self.make_branch_and_tree('target')
2155
revision_tree = source.basis_tree()
2156
revision_tree.lock_read()
2157
self.addCleanup(revision_tree.unlock)
2158
build_tree(revision_tree, target, source)
2160
self.addCleanup(target.unlock)
2161
self.assertEqual([], list(target.iter_changes(revision_tree)))
2163
def test_build_tree_hardlinks_preserve_execute(self):
2164
self.requireFeature(HardlinkFeature)
2165
source = self.create_ab_tree()
2166
tt = TreeTransform(source)
2167
trans_id = tt.trans_id_tree_path('file1')
2168
tt.set_executability(True, trans_id)
2170
self.assertTrue(source.is_executable('file1'))
2171
target = self.make_branch_and_tree('target')
2172
revision_tree = source.basis_tree()
2173
revision_tree.lock_read()
2174
self.addCleanup(revision_tree.unlock)
2175
build_tree(revision_tree, target, source, hardlink=True)
2177
self.addCleanup(target.unlock)
2178
self.assertEqual([], list(target.iter_changes(revision_tree)))
2179
self.assertTrue(source.is_executable('file1'))
2181
def install_rot13_content_filter(self, pattern):
2183
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2184
# below, but that looks a bit... hard to read even if it's exactly
2186
original_registry = filters._reset_registry()
2187
def restore_registry():
2188
filters._reset_registry(original_registry)
2189
self.addCleanup(restore_registry)
2190
def rot13(chunks, context=None):
2191
return [b''.join(chunks).encode('rot13')]
2192
rot13filter = filters.ContentFilter(rot13, rot13)
2193
filters.filter_stacks_registry.register(
2194
'rot13', {'yes': [rot13filter]}.get)
2195
os.mkdir(self.test_home_dir + '/.bazaar')
2196
rules_filename = self.test_home_dir + '/.bazaar/rules'
2197
with open(rules_filename, 'wb') as f:
2198
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2199
def uninstall_rules():
2200
os.remove(rules_filename)
2202
self.addCleanup(uninstall_rules)
2205
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2206
"""build_tree will not hardlink files that have content filtering rules
2207
applied to them (but will still hardlink other files from the same tree
2210
self.requireFeature(HardlinkFeature)
2211
self.install_rot13_content_filter(b'file1')
2212
source = self.create_ab_tree()
2213
target = self.make_branch_and_tree('target')
2214
revision_tree = source.basis_tree()
2215
revision_tree.lock_read()
2216
self.addCleanup(revision_tree.unlock)
2217
build_tree(revision_tree, target, source, hardlink=True)
2219
self.addCleanup(target.unlock)
2220
self.assertEqual([], list(target.iter_changes(revision_tree)))
2221
source_stat = os.stat('source/file1')
2222
target_stat = os.stat('target/file1')
2223
self.assertNotEqual(source_stat, target_stat)
2224
source_stat = os.stat('source/file2')
2225
target_stat = os.stat('target/file2')
2226
self.assertEqualStat(source_stat, target_stat)
2228
def test_case_insensitive_build_tree_inventory(self):
2229
if (features.CaseInsensitiveFilesystemFeature.available()
2230
or features.CaseInsCasePresFilenameFeature.available()):
2231
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2232
source = self.make_branch_and_tree('source')
2233
self.build_tree(['source/file', 'source/FILE'])
2234
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2235
source.commit('added files')
2236
# Don't try this at home, kids!
2237
# Force the tree to report that it is case insensitive
2238
target = self.make_branch_and_tree('target')
2239
target.case_sensitive = False
2240
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2241
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2242
self.assertEqual('FILE', target.id2path(b'upper-id'))
2244
def test_build_tree_observes_sha(self):
2245
source = self.make_branch_and_tree('source')
2246
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2247
source.add(['file1', 'dir', 'dir/file2'],
2248
[b'file1-id', b'dir-id', b'file2-id'])
2249
source.commit('new files')
2250
target = self.make_branch_and_tree('target')
2252
self.addCleanup(target.unlock)
2253
# We make use of the fact that DirState caches its cutoff time. So we
2254
# set the 'safe' time to one minute in the future.
2255
state = target.current_dirstate()
2256
state._cutoff_time = time.time() + 60
2257
build_tree(source.basis_tree(), target)
2258
entry1_sha = osutils.sha_file_by_name('source/file1')
2259
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2260
# entry[1] is the state information, entry[1][0] is the state of the
2261
# working tree, entry[1][0][1] is the sha value for the current working
2263
entry1 = state._get_entry(0, path_utf8=b'file1')
2264
self.assertEqual(entry1_sha, entry1[1][0][1])
2265
# The 'size' field must also be set.
2266
self.assertEqual(25, entry1[1][0][2])
2267
entry1_state = entry1[1][0]
2268
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2269
self.assertEqual(entry2_sha, entry2[1][0][1])
2270
self.assertEqual(29, entry2[1][0][2])
2271
entry2_state = entry2[1][0]
2272
# Now, make sure that we don't have to re-read the content. The
2273
# packed_stat should match exactly.
2274
self.assertEqual(entry1_sha, target.get_file_sha1('file1', b'file1-id'))
2275
self.assertEqual(entry2_sha,
2276
target.get_file_sha1('dir/file2', b'file2-id'))
2277
self.assertEqual(entry1_state, entry1[1][0])
2278
self.assertEqual(entry2_state, entry2[1][0])
2281
class TestCommitTransform(tests.TestCaseWithTransport):
2283
def get_branch(self):
2284
tree = self.make_branch_and_tree('tree')
2286
self.addCleanup(tree.unlock)
2287
tree.commit('empty commit')
2290
def get_branch_and_transform(self):
2291
branch = self.get_branch()
2292
tt = TransformPreview(branch.basis_tree())
2293
self.addCleanup(tt.finalize)
2296
def test_commit_wrong_basis(self):
2297
branch = self.get_branch()
2298
basis = branch.repository.revision_tree(
2299
_mod_revision.NULL_REVISION)
2300
tt = TransformPreview(basis)
2301
self.addCleanup(tt.finalize)
2302
e = self.assertRaises(ValueError, tt.commit, branch, '')
2303
self.assertEqual('TreeTransform not based on branch basis: null:',
2306
def test_empy_commit(self):
2307
branch, tt = self.get_branch_and_transform()
2308
rev = tt.commit(branch, 'my message')
2309
self.assertEqual(2, branch.revno())
2310
repo = branch.repository
2311
self.assertEqual('my message', repo.get_revision(rev).message)
2313
def test_merge_parents(self):
2314
branch, tt = self.get_branch_and_transform()
2315
rev = tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2316
self.assertEqual([b'rev1b', b'rev1c'],
2317
branch.basis_tree().get_parent_ids()[1:])
2319
def test_first_commit(self):
2320
branch = self.make_branch('branch')
2322
self.addCleanup(branch.unlock)
2323
tt = TransformPreview(branch.basis_tree())
2324
self.addCleanup(tt.finalize)
2325
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2326
rev = tt.commit(branch, 'my message')
2327
self.assertEqual([], branch.basis_tree().get_parent_ids())
2328
self.assertNotEqual(_mod_revision.NULL_REVISION,
2329
branch.last_revision())
2331
def test_first_commit_with_merge_parents(self):
2332
branch = self.make_branch('branch')
2334
self.addCleanup(branch.unlock)
2335
tt = TransformPreview(branch.basis_tree())
2336
self.addCleanup(tt.finalize)
2337
e = self.assertRaises(ValueError, tt.commit, branch,
2338
'my message', [b'rev1b-id'])
2339
self.assertEqual('Cannot supply merge parents for first commit.',
2341
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2343
def test_add_files(self):
2344
branch, tt = self.get_branch_and_transform()
2345
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2346
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2347
if SymlinkFeature.available():
2348
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2349
rev = tt.commit(branch, 'message')
2350
tree = branch.basis_tree()
2351
self.assertEqual('file', tree.id2path(b'file-id'))
2352
self.assertEqual(b'contents', tree.get_file_text('file', b'file-id'))
2353
self.assertEqual('dir', tree.id2path(b'dir-id'))
2354
if SymlinkFeature.available():
2355
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2356
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2358
def test_add_unversioned(self):
2359
branch, tt = self.get_branch_and_transform()
2360
tt.new_file('file', tt.root, [b'contents'])
2361
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2362
'message', strict=True)
2364
def test_modify_strict(self):
2365
branch, tt = self.get_branch_and_transform()
2366
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2367
tt.commit(branch, 'message', strict=True)
2368
tt = TransformPreview(branch.basis_tree())
2369
self.addCleanup(tt.finalize)
2370
trans_id = tt.trans_id_file_id(b'file-id')
2371
tt.delete_contents(trans_id)
2372
tt.create_file([b'contents'], trans_id)
2373
tt.commit(branch, 'message', strict=True)
2375
def test_commit_malformed(self):
2376
"""Committing a malformed transform should raise an exception.
2378
In this case, we are adding a file without adding its parent.
2380
branch, tt = self.get_branch_and_transform()
2381
parent_id = tt.trans_id_file_id(b'parent-id')
2382
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2383
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2386
def test_commit_rich_revision_data(self):
2387
branch, tt = self.get_branch_and_transform()
2388
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2389
committer='me <me@example.com>',
2390
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2391
authors=['Author1 <author1@example.com>',
2392
'Author2 <author2@example.com>',
2394
self.assertEqual(b'revid-1', rev_id)
2395
revision = branch.repository.get_revision(rev_id)
2396
self.assertEqual(1, revision.timestamp)
2397
self.assertEqual(43201, revision.timezone)
2398
self.assertEqual('me <me@example.com>', revision.committer)
2399
self.assertEqual(['Author1 <author1@example.com>',
2400
'Author2 <author2@example.com>'],
2401
revision.get_apparent_authors())
2402
del revision.properties['authors']
2403
self.assertEqual({'foo': 'bar',
2404
'branch-nick': 'tree'},
2405
revision.properties)
2407
def test_no_explicit_revprops(self):
2408
branch, tt = self.get_branch_and_transform()
2409
rev_id = tt.commit(branch, 'message', authors=[
2410
'Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>', ])
2412
revision = branch.repository.get_revision(rev_id)
2413
self.assertEqual(['Author1 <author1@example.com>',
2414
'Author2 <author2@example.com>'],
2415
revision.get_apparent_authors())
2416
self.assertEqual('tree', revision.properties['branch-nick'])
2419
class TestFileMover(tests.TestCaseWithTransport):
2421
def test_file_mover(self):
2422
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2423
mover = _FileMover()
2424
mover.rename('a', 'q')
2425
self.assertPathExists('q')
2426
self.assertPathDoesNotExist('a')
2427
self.assertPathExists('q/b')
2428
self.assertPathExists('c')
2429
self.assertPathExists('c/d')
2431
def test_pre_delete_rollback(self):
2432
self.build_tree(['a/'])
2433
mover = _FileMover()
2434
mover.pre_delete('a', 'q')
2435
self.assertPathExists('q')
2436
self.assertPathDoesNotExist('a')
2438
self.assertPathDoesNotExist('q')
2439
self.assertPathExists('a')
2441
def test_apply_deletions(self):
2442
self.build_tree(['a/', 'b/'])
2443
mover = _FileMover()
2444
mover.pre_delete('a', 'q')
2445
mover.pre_delete('b', 'r')
2446
self.assertPathExists('q')
2447
self.assertPathExists('r')
2448
self.assertPathDoesNotExist('a')
2449
self.assertPathDoesNotExist('b')
2450
mover.apply_deletions()
2451
self.assertPathDoesNotExist('q')
2452
self.assertPathDoesNotExist('r')
2453
self.assertPathDoesNotExist('a')
2454
self.assertPathDoesNotExist('b')
2456
def test_file_mover_rollback(self):
2457
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2458
mover = _FileMover()
2459
mover.rename('c/d', 'c/f')
2460
mover.rename('c/e', 'c/d')
2462
mover.rename('a', 'c')
2463
except errors.FileExists as e:
2465
self.assertPathExists('a')
2466
self.assertPathExists('c/d')
2469
class Bogus(Exception):
2473
class TestTransformRollback(tests.TestCaseWithTransport):
2475
class ExceptionFileMover(_FileMover):
2477
def __init__(self, bad_source=None, bad_target=None):
2478
_FileMover.__init__(self)
2479
self.bad_source = bad_source
2480
self.bad_target = bad_target
2482
def rename(self, source, target):
2483
if (self.bad_source is not None and
2484
source.endswith(self.bad_source)):
2486
elif (self.bad_target is not None and
2487
target.endswith(self.bad_target)):
2490
_FileMover.rename(self, source, target)
2492
def test_rollback_rename(self):
2493
tree = self.make_branch_and_tree('.')
2494
self.build_tree(['a/', 'a/b'])
2495
tt = TreeTransform(tree)
2496
self.addCleanup(tt.finalize)
2497
a_id = tt.trans_id_tree_path('a')
2498
tt.adjust_path('c', tt.root, a_id)
2499
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2500
self.assertRaises(Bogus, tt.apply,
2501
_mover=self.ExceptionFileMover(bad_source='a'))
2502
self.assertPathExists('a')
2503
self.assertPathExists('a/b')
2505
self.assertPathExists('c')
2506
self.assertPathExists('c/d')
2508
def test_rollback_rename_into_place(self):
2509
tree = self.make_branch_and_tree('.')
2510
self.build_tree(['a/', 'a/b'])
2511
tt = TreeTransform(tree)
2512
self.addCleanup(tt.finalize)
2513
a_id = tt.trans_id_tree_path('a')
2514
tt.adjust_path('c', tt.root, a_id)
2515
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2516
self.assertRaises(Bogus, tt.apply,
2517
_mover=self.ExceptionFileMover(bad_target='c/d'))
2518
self.assertPathExists('a')
2519
self.assertPathExists('a/b')
2521
self.assertPathExists('c')
2522
self.assertPathExists('c/d')
2524
def test_rollback_deletion(self):
2525
tree = self.make_branch_and_tree('.')
2526
self.build_tree(['a/', 'a/b'])
2527
tt = TreeTransform(tree)
2528
self.addCleanup(tt.finalize)
2529
a_id = tt.trans_id_tree_path('a')
2530
tt.delete_contents(a_id)
2531
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2532
self.assertRaises(Bogus, tt.apply,
2533
_mover=self.ExceptionFileMover(bad_target='d'))
2534
self.assertPathExists('a')
2535
self.assertPathExists('a/b')
2538
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2539
"""Ensure treetransform creation errors can be safely cleaned up after"""
2541
def _override_globals_in_method(self, instance, method_name, globals):
2542
"""Replace method on instance with one with updated globals"""
2544
func = getattr(instance, method_name).__func__
2545
new_globals = dict(func.__globals__)
2546
new_globals.update(globals)
2547
new_func = types.FunctionType(func.__code__, new_globals,
2548
func.__name__, func.__defaults__)
2549
setattr(instance, method_name,
2550
types.MethodType(new_func, instance, instance.__class__))
2551
self.addCleanup(delattr, instance, method_name)
2554
def _fake_open_raises_before(name, mode):
2555
"""Like open() but raises before doing anything"""
2559
def _fake_open_raises_after(name, mode):
2560
"""Like open() but raises after creating file without returning"""
2561
open(name, mode).close()
2564
def create_transform_and_root_trans_id(self):
2565
"""Setup a transform creating a file in limbo"""
2566
tree = self.make_branch_and_tree('.')
2567
tt = TreeTransform(tree)
2568
return tt, tt.create_path("a", tt.root)
2570
def create_transform_and_subdir_trans_id(self):
2571
"""Setup a transform creating a directory containing a file in limbo"""
2572
tree = self.make_branch_and_tree('.')
2573
tt = TreeTransform(tree)
2574
d_trans_id = tt.create_path("d", tt.root)
2575
tt.create_directory(d_trans_id)
2576
f_trans_id = tt.create_path("a", d_trans_id)
2577
tt.adjust_path("a", d_trans_id, f_trans_id)
2578
return tt, f_trans_id
2580
def test_root_create_file_open_raises_before_creation(self):
2581
tt, trans_id = self.create_transform_and_root_trans_id()
2582
self._override_globals_in_method(tt, "create_file",
2583
{"open": self._fake_open_raises_before})
2584
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2585
path = tt._limbo_name(trans_id)
2586
self.assertPathDoesNotExist(path)
2588
self.assertPathDoesNotExist(tt._limbodir)
2590
def test_root_create_file_open_raises_after_creation(self):
2591
tt, trans_id = self.create_transform_and_root_trans_id()
2592
self._override_globals_in_method(tt, "create_file",
2593
{"open": self._fake_open_raises_after})
2594
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2595
path = tt._limbo_name(trans_id)
2596
self.assertPathExists(path)
2598
self.assertPathDoesNotExist(path)
2599
self.assertPathDoesNotExist(tt._limbodir)
2601
def test_subdir_create_file_open_raises_before_creation(self):
2602
tt, trans_id = self.create_transform_and_subdir_trans_id()
2603
self._override_globals_in_method(tt, "create_file",
2604
{"open": self._fake_open_raises_before})
2605
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2606
path = tt._limbo_name(trans_id)
2607
self.assertPathDoesNotExist(path)
2609
self.assertPathDoesNotExist(tt._limbodir)
2611
def test_subdir_create_file_open_raises_after_creation(self):
2612
tt, trans_id = self.create_transform_and_subdir_trans_id()
2613
self._override_globals_in_method(tt, "create_file",
2614
{"open": self._fake_open_raises_after})
2615
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2616
path = tt._limbo_name(trans_id)
2617
self.assertPathExists(path)
2619
self.assertPathDoesNotExist(path)
2620
self.assertPathDoesNotExist(tt._limbodir)
2622
def test_rename_in_limbo_rename_raises_after_rename(self):
2623
tt, trans_id = self.create_transform_and_root_trans_id()
2624
parent1 = tt.new_directory('parent1', tt.root)
2625
child1 = tt.new_file('child1', parent1, [b'contents'])
2626
parent2 = tt.new_directory('parent2', tt.root)
2628
class FakeOSModule(object):
2629
def rename(self, old, new):
2632
self._override_globals_in_method(tt, "_rename_in_limbo",
2633
{"os": FakeOSModule()})
2635
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2636
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2637
self.assertPathExists(path)
2639
self.assertPathDoesNotExist(path)
2640
self.assertPathDoesNotExist(tt._limbodir)
2642
def test_rename_in_limbo_rename_raises_before_rename(self):
2643
tt, trans_id = self.create_transform_and_root_trans_id()
2644
parent1 = tt.new_directory('parent1', tt.root)
2645
child1 = tt.new_file('child1', parent1, [b'contents'])
2646
parent2 = tt.new_directory('parent2', tt.root)
2648
class FakeOSModule(object):
2649
def rename(self, old, new):
2651
self._override_globals_in_method(tt, "_rename_in_limbo",
2652
{"os": FakeOSModule()})
2654
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2655
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2656
self.assertPathExists(path)
2658
self.assertPathDoesNotExist(path)
2659
self.assertPathDoesNotExist(tt._limbodir)
2662
class TestTransformMissingParent(tests.TestCaseWithTransport):
2664
def make_tt_with_versioned_dir(self):
2665
wt = self.make_branch_and_tree('.')
2666
self.build_tree(['dir/',])
2667
wt.add(['dir'], [b'dir-id'])
2668
wt.commit('Create dir')
2669
tt = TreeTransform(wt)
2670
self.addCleanup(tt.finalize)
2673
def test_resolve_create_parent_for_versioned_file(self):
2674
wt, tt = self.make_tt_with_versioned_dir()
2675
dir_tid = tt.trans_id_tree_path('dir')
2676
file_tid = tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2677
tt.delete_contents(dir_tid)
2678
tt.unversion_file(dir_tid)
2679
conflicts = resolve_conflicts(tt)
2680
# one conflict for the missing directory, one for the unversioned
2682
self.assertLength(2, conflicts)
2684
def test_non_versioned_file_create_conflict(self):
2685
wt, tt = self.make_tt_with_versioned_dir()
2686
dir_tid = tt.trans_id_tree_path('dir')
2687
tt.new_file('file', dir_tid, [b'Contents'])
2688
tt.delete_contents(dir_tid)
2689
tt.unversion_file(dir_tid)
2690
conflicts = resolve_conflicts(tt)
2691
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2692
self.assertLength(1, conflicts)
2693
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2697
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2698
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2700
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2701
('', ''), ('directory', 'directory'), (False, False))
2704
class TestTransformPreview(tests.TestCaseWithTransport):
2706
def create_tree(self):
2707
tree = self.make_branch_and_tree('.')
2708
self.build_tree_contents([('a', b'content 1')])
2709
tree.set_root_id(b'TREE_ROOT')
2710
tree.add('a', b'a-id')
2711
tree.commit('rev1', rev_id=b'rev1')
2712
return tree.branch.repository.revision_tree(b'rev1')
2714
def get_empty_preview(self):
2715
repository = self.make_repository('repo')
2716
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2717
preview = TransformPreview(tree)
2718
self.addCleanup(preview.finalize)
2721
def test_transform_preview(self):
2722
revision_tree = self.create_tree()
2723
preview = TransformPreview(revision_tree)
2724
self.addCleanup(preview.finalize)
2726
def test_transform_preview_tree(self):
2727
revision_tree = self.create_tree()
2728
preview = TransformPreview(revision_tree)
2729
self.addCleanup(preview.finalize)
2730
preview.get_preview_tree()
2732
def test_transform_new_file(self):
2733
revision_tree = self.create_tree()
2734
preview = TransformPreview(revision_tree)
2735
self.addCleanup(preview.finalize)
2736
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2737
preview_tree = preview.get_preview_tree()
2738
self.assertEqual(preview_tree.kind('file2'), 'file')
2739
with preview_tree.get_file('file2', b'file2-id') as f:
2740
self.assertEqual(f.read(), b'content B\n')
2742
def test_diff_preview_tree(self):
2743
revision_tree = self.create_tree()
2744
preview = TransformPreview(revision_tree)
2745
self.addCleanup(preview.finalize)
2746
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2747
preview_tree = preview.get_preview_tree()
2749
show_diff_trees(revision_tree, preview_tree, out)
2750
lines = out.getvalue().splitlines()
2751
self.assertEqual(lines[0], b"=== added file 'file2'")
2752
# 3 lines of diff administrivia
2753
self.assertEqual(lines[4], b"+content B")
2755
def test_transform_conflicts(self):
2756
revision_tree = self.create_tree()
2757
preview = TransformPreview(revision_tree)
2758
self.addCleanup(preview.finalize)
2759
preview.new_file('a', preview.root, [b'content 2'])
2760
resolve_conflicts(preview)
2761
trans_id = preview.trans_id_file_id(b'a-id')
2762
self.assertEqual('a.moved', preview.final_name(trans_id))
2764
def get_tree_and_preview_tree(self):
2765
revision_tree = self.create_tree()
2766
preview = TransformPreview(revision_tree)
2767
self.addCleanup(preview.finalize)
2768
a_trans_id = preview.trans_id_file_id(b'a-id')
2769
preview.delete_contents(a_trans_id)
2770
preview.create_file([b'b content'], a_trans_id)
2771
preview_tree = preview.get_preview_tree()
2772
return revision_tree, preview_tree
2774
def test_iter_changes(self):
2775
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2776
root = revision_tree.get_root_id()
2777
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2778
(root, root), ('a', 'a'), ('file', 'file'),
2780
list(preview_tree.iter_changes(revision_tree)))
2782
def test_include_unchanged_succeeds(self):
2783
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2784
changes = preview_tree.iter_changes(revision_tree,
2785
include_unchanged=True)
2786
root = revision_tree.get_root_id()
2788
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2790
def test_specific_files(self):
2791
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2792
changes = preview_tree.iter_changes(revision_tree,
2793
specific_files=[''])
2794
self.assertEqual([A_ENTRY], list(changes))
2796
def test_want_unversioned(self):
2797
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2798
changes = preview_tree.iter_changes(revision_tree,
2799
want_unversioned=True)
2800
self.assertEqual([A_ENTRY], list(changes))
2802
def test_ignore_extra_trees_no_specific_files(self):
2803
# extra_trees is harmless without specific_files, so we'll silently
2804
# accept it, even though we won't use it.
2805
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2806
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2808
def test_ignore_require_versioned_no_specific_files(self):
2809
# require_versioned is meaningless without specific_files.
2810
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2811
preview_tree.iter_changes(revision_tree, require_versioned=False)
2813
def test_ignore_pb(self):
2814
# pb could be supported, but TT.iter_changes doesn't support it.
2815
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2816
preview_tree.iter_changes(revision_tree)
2818
def test_kind(self):
2819
revision_tree = self.create_tree()
2820
preview = TransformPreview(revision_tree)
2821
self.addCleanup(preview.finalize)
2822
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2823
preview.new_directory('directory', preview.root, b'dir-id')
2824
preview_tree = preview.get_preview_tree()
2825
self.assertEqual('file', preview_tree.kind('file'))
2826
self.assertEqual('directory', preview_tree.kind('directory'))
2828
def test_get_file_mtime(self):
2829
preview = self.get_empty_preview()
2830
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2832
limbo_path = preview._limbo_name(file_trans_id)
2833
preview_tree = preview.get_preview_tree()
2834
self.assertEqual(os.stat(limbo_path).st_mtime,
2835
preview_tree.get_file_mtime('file', b'file-id'))
2837
def test_get_file_mtime_renamed(self):
2838
work_tree = self.make_branch_and_tree('tree')
2839
self.build_tree(['tree/file'])
2840
work_tree.add('file', b'file-id')
2841
preview = TransformPreview(work_tree)
2842
self.addCleanup(preview.finalize)
2843
file_trans_id = preview.trans_id_tree_path('file')
2844
preview.adjust_path('renamed', preview.root, file_trans_id)
2845
preview_tree = preview.get_preview_tree()
2846
preview_mtime = preview_tree.get_file_mtime('renamed', b'file-id')
2847
work_mtime = work_tree.get_file_mtime('file', b'file-id')
2849
def test_get_file_size(self):
2850
work_tree = self.make_branch_and_tree('tree')
2851
self.build_tree_contents([('tree/old', b'old')])
2852
work_tree.add('old', b'old-id')
2853
preview = TransformPreview(work_tree)
2854
self.addCleanup(preview.finalize)
2855
new_id = preview.new_file('name', preview.root, [b'contents'], b'new-id',
2857
tree = preview.get_preview_tree()
2858
self.assertEqual(len('old'), tree.get_file_size('old'))
2859
self.assertEqual(len('contents'), tree.get_file_size('name'))
2861
def test_get_file(self):
2862
preview = self.get_empty_preview()
2863
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2864
preview_tree = preview.get_preview_tree()
2865
with preview_tree.get_file('file') as tree_file:
2866
self.assertEqual(b'contents', tree_file.read())
2868
def test_get_symlink_target(self):
2869
self.requireFeature(SymlinkFeature)
2870
preview = self.get_empty_preview()
2871
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2872
preview_tree = preview.get_preview_tree()
2873
self.assertEqual('target',
2874
preview_tree.get_symlink_target('symlink'))
2876
def test_all_file_ids(self):
2877
tree = self.make_branch_and_tree('tree')
2878
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2879
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2880
preview = TransformPreview(tree)
2881
self.addCleanup(preview.finalize)
2882
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2883
c_trans_id = preview.trans_id_file_id(b'c-id')
2884
preview.unversion_file(c_trans_id)
2885
preview.version_file(b'c-id', c_trans_id)
2886
preview_tree = preview.get_preview_tree()
2887
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2888
preview_tree.all_file_ids())
2890
def test_path2id_deleted_unchanged(self):
2891
tree = self.make_branch_and_tree('tree')
2892
self.build_tree(['tree/unchanged', 'tree/deleted'])
2893
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2894
preview = TransformPreview(tree)
2895
self.addCleanup(preview.finalize)
2896
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2897
preview_tree = preview.get_preview_tree()
2898
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2899
self.assertFalse(preview_tree.is_versioned('deleted'))
2901
def test_path2id_created(self):
2902
tree = self.make_branch_and_tree('tree')
2903
self.build_tree(['tree/unchanged'])
2904
tree.add(['unchanged'], [b'unchanged-id'])
2905
preview = TransformPreview(tree)
2906
self.addCleanup(preview.finalize)
2907
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2908
[b'contents'], b'new-id')
2909
preview_tree = preview.get_preview_tree()
2910
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
2912
def test_path2id_moved(self):
2913
tree = self.make_branch_and_tree('tree')
2914
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2915
tree.add(['old_parent', 'old_parent/child'],
2916
[b'old_parent-id', b'child-id'])
2917
preview = TransformPreview(tree)
2918
self.addCleanup(preview.finalize)
2919
new_parent = preview.new_directory('new_parent', preview.root,
2921
preview.adjust_path('child', new_parent,
2922
preview.trans_id_file_id(b'child-id'))
2923
preview_tree = preview.get_preview_tree()
2924
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
2925
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
2927
def test_path2id_renamed_parent(self):
2928
tree = self.make_branch_and_tree('tree')
2929
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2930
tree.add(['old_name', 'old_name/child'],
2931
[b'parent-id', b'child-id'])
2932
preview = TransformPreview(tree)
2933
self.addCleanup(preview.finalize)
2934
preview.adjust_path('new_name', preview.root,
2935
preview.trans_id_file_id(b'parent-id'))
2936
preview_tree = preview.get_preview_tree()
2937
self.assertFalse(preview_tree.is_versioned('old_name/child'))
2938
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
2940
def assertMatchingIterEntries(self, tt, specific_files=None):
2941
preview_tree = tt.get_preview_tree()
2942
preview_result = list(preview_tree.iter_entries_by_dir(
2943
specific_files=specific_files))
2946
actual_result = list(tree.iter_entries_by_dir(
2947
specific_files=specific_files))
2948
self.assertEqual(actual_result, preview_result)
2950
def test_iter_entries_by_dir_new(self):
2951
tree = self.make_branch_and_tree('tree')
2952
tt = TreeTransform(tree)
2953
tt.new_file('new', tt.root, [b'contents'], b'new-id')
2954
self.assertMatchingIterEntries(tt)
2956
def test_iter_entries_by_dir_deleted(self):
2957
tree = self.make_branch_and_tree('tree')
2958
self.build_tree(['tree/deleted'])
2959
tree.add('deleted', b'deleted-id')
2960
tt = TreeTransform(tree)
2961
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
2962
self.assertMatchingIterEntries(tt)
2964
def test_iter_entries_by_dir_unversioned(self):
2965
tree = self.make_branch_and_tree('tree')
2966
self.build_tree(['tree/removed'])
2967
tree.add('removed', b'removed-id')
2968
tt = TreeTransform(tree)
2969
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
2970
self.assertMatchingIterEntries(tt)
2972
def test_iter_entries_by_dir_moved(self):
2973
tree = self.make_branch_and_tree('tree')
2974
self.build_tree(['tree/moved', 'tree/new_parent/'])
2975
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
2976
tt = TreeTransform(tree)
2977
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
2978
tt.trans_id_file_id(b'moved-id'))
2979
self.assertMatchingIterEntries(tt)
2981
def test_iter_entries_by_dir_specific_files(self):
2982
tree = self.make_branch_and_tree('tree')
2983
tree.set_root_id(b'tree-root-id')
2984
self.build_tree(['tree/parent/', 'tree/parent/child'])
2985
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
2986
tt = TreeTransform(tree)
2987
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
2989
def test_symlink_content_summary(self):
2990
self.requireFeature(SymlinkFeature)
2991
preview = self.get_empty_preview()
2992
preview.new_symlink('path', preview.root, 'target', b'path-id')
2993
summary = preview.get_preview_tree().path_content_summary('path')
2994
self.assertEqual(('symlink', None, None, 'target'), summary)
2996
def test_missing_content_summary(self):
2997
preview = self.get_empty_preview()
2998
summary = preview.get_preview_tree().path_content_summary('path')
2999
self.assertEqual(('missing', None, None, None), summary)
3001
def test_deleted_content_summary(self):
3002
tree = self.make_branch_and_tree('tree')
3003
self.build_tree(['tree/path/'])
3005
preview = TransformPreview(tree)
3006
self.addCleanup(preview.finalize)
3007
preview.delete_contents(preview.trans_id_tree_path('path'))
3008
summary = preview.get_preview_tree().path_content_summary('path')
3009
self.assertEqual(('missing', None, None, None), summary)
3011
def test_file_content_summary_executable(self):
3012
preview = self.get_empty_preview()
3013
path_id = preview.new_file('path', preview.root, [b'contents'], b'path-id')
3014
preview.set_executability(True, path_id)
3015
summary = preview.get_preview_tree().path_content_summary('path')
3016
self.assertEqual(4, len(summary))
3017
self.assertEqual('file', summary[0])
3018
# size must be known
3019
self.assertEqual(len('contents'), summary[1])
3021
self.assertEqual(True, summary[2])
3022
# will not have hash (not cheap to determine)
3023
self.assertIs(None, summary[3])
3025
def test_change_executability(self):
3026
tree = self.make_branch_and_tree('tree')
3027
self.build_tree(['tree/path'])
3029
preview = TransformPreview(tree)
3030
self.addCleanup(preview.finalize)
3031
path_id = preview.trans_id_tree_path('path')
3032
preview.set_executability(True, path_id)
3033
summary = preview.get_preview_tree().path_content_summary('path')
3034
self.assertEqual(True, summary[2])
3036
def test_file_content_summary_non_exec(self):
3037
preview = self.get_empty_preview()
3038
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3039
summary = preview.get_preview_tree().path_content_summary('path')
3040
self.assertEqual(4, len(summary))
3041
self.assertEqual('file', summary[0])
3042
# size must be known
3043
self.assertEqual(len('contents'), summary[1])
3045
self.assertEqual(False, summary[2])
3046
# will not have hash (not cheap to determine)
3047
self.assertIs(None, summary[3])
3049
def test_dir_content_summary(self):
3050
preview = self.get_empty_preview()
3051
preview.new_directory('path', preview.root, b'path-id')
3052
summary = preview.get_preview_tree().path_content_summary('path')
3053
self.assertEqual(('directory', None, None, None), summary)
3055
def test_tree_content_summary(self):
3056
preview = self.get_empty_preview()
3057
path = preview.new_directory('path', preview.root, b'path-id')
3058
preview.set_tree_reference(b'rev-1', path)
3059
summary = preview.get_preview_tree().path_content_summary('path')
3060
self.assertEqual(4, len(summary))
3061
self.assertEqual('tree-reference', summary[0])
3063
def test_annotate(self):
3064
tree = self.make_branch_and_tree('tree')
3065
self.build_tree_contents([('tree/file', b'a\n')])
3066
tree.add('file', b'file-id')
3067
tree.commit('a', rev_id=b'one')
3068
self.build_tree_contents([('tree/file', b'a\nb\n')])
3069
preview = TransformPreview(tree)
3070
self.addCleanup(preview.finalize)
3071
file_trans_id = preview.trans_id_file_id(b'file-id')
3072
preview.delete_contents(file_trans_id)
3073
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3074
preview_tree = preview.get_preview_tree()
3080
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3081
self.assertEqual(expected, annotation)
3083
def test_annotate_missing(self):
3084
preview = self.get_empty_preview()
3085
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3086
preview_tree = preview.get_preview_tree()
3092
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3093
self.assertEqual(expected, annotation)
3095
def test_annotate_rename(self):
3096
tree = self.make_branch_and_tree('tree')
3097
self.build_tree_contents([('tree/file', b'a\n')])
3098
tree.add('file', b'file-id')
3099
tree.commit('a', rev_id=b'one')
3100
preview = TransformPreview(tree)
3101
self.addCleanup(preview.finalize)
3102
file_trans_id = preview.trans_id_file_id(b'file-id')
3103
preview.adjust_path('newname', preview.root, file_trans_id)
3104
preview_tree = preview.get_preview_tree()
3108
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3109
self.assertEqual(expected, annotation)
3111
def test_annotate_deleted(self):
3112
tree = self.make_branch_and_tree('tree')
3113
self.build_tree_contents([('tree/file', b'a\n')])
3114
tree.add('file', b'file-id')
3115
tree.commit('a', rev_id=b'one')
3116
self.build_tree_contents([('tree/file', b'a\nb\n')])
3117
preview = TransformPreview(tree)
3118
self.addCleanup(preview.finalize)
3119
file_trans_id = preview.trans_id_file_id(b'file-id')
3120
preview.delete_contents(file_trans_id)
3121
preview_tree = preview.get_preview_tree()
3122
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3123
self.assertIs(None, annotation)
3125
def test_stored_kind(self):
3126
preview = self.get_empty_preview()
3127
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3128
preview_tree = preview.get_preview_tree()
3129
self.assertEqual('file', preview_tree.stored_kind('file'))
3131
def test_is_executable(self):
3132
preview = self.get_empty_preview()
3133
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3134
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3135
preview_tree = preview.get_preview_tree()
3136
self.assertEqual(True, preview_tree.is_executable('file'))
3138
def test_get_set_parent_ids(self):
3139
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3140
self.assertEqual([], preview_tree.get_parent_ids())
3141
preview_tree.set_parent_ids([b'rev-1'])
3142
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3144
def test_plan_file_merge(self):
3145
work_a = self.make_branch_and_tree('wta')
3146
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3147
work_a.add('file', b'file-id')
3148
base_id = work_a.commit('base version')
3149
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3150
preview = TransformPreview(work_a)
3151
self.addCleanup(preview.finalize)
3152
trans_id = preview.trans_id_file_id(b'file-id')
3153
preview.delete_contents(trans_id)
3154
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3155
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3156
tree_a = preview.get_preview_tree()
3157
tree_a.set_parent_ids([base_id])
3159
('killed-a', b'a\n'),
3160
('killed-b', b'b\n'),
3161
('unchanged', b'c\n'),
3162
('unchanged', b'd\n'),
3165
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3167
def test_plan_file_merge_revision_tree(self):
3168
work_a = self.make_branch_and_tree('wta')
3169
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3170
work_a.add('file', b'file-id')
3171
base_id = work_a.commit('base version')
3172
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3173
preview = TransformPreview(work_a.basis_tree())
3174
self.addCleanup(preview.finalize)
3175
trans_id = preview.trans_id_file_id(b'file-id')
3176
preview.delete_contents(trans_id)
3177
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3178
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3179
tree_a = preview.get_preview_tree()
3180
tree_a.set_parent_ids([base_id])
3182
('killed-a', b'a\n'),
3183
('killed-b', b'b\n'),
3184
('unchanged', b'c\n'),
3185
('unchanged', b'd\n'),
3188
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3190
def test_walkdirs(self):
3191
preview = self.get_empty_preview()
3192
root = preview.new_directory('', ROOT_PARENT, b'tree-root')
3193
# FIXME: new_directory should mark root.
3194
preview.fixup_new_roots()
3195
preview_tree = preview.get_preview_tree()
3196
file_trans_id = preview.new_file('a', preview.root, [b'contents'],
3198
expected = [(('', b'tree-root'),
3199
[('a', 'a', 'file', None, b'a-id', 'file')])]
3200
self.assertEqual(expected, list(preview_tree.walkdirs()))
3202
def test_extras(self):
3203
work_tree = self.make_branch_and_tree('tree')
3204
self.build_tree(['tree/removed-file', 'tree/existing-file',
3205
'tree/not-removed-file'])
3206
work_tree.add(['removed-file', 'not-removed-file'])
3207
preview = TransformPreview(work_tree)
3208
self.addCleanup(preview.finalize)
3209
preview.new_file('new-file', preview.root, [b'contents'])
3210
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3211
b'new-versioned-id')
3212
tree = preview.get_preview_tree()
3213
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3214
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3217
def test_merge_into_preview(self):
3218
work_tree = self.make_branch_and_tree('tree')
3219
self.build_tree_contents([('tree/file', b'b\n')])
3220
work_tree.add('file', b'file-id')
3221
work_tree.commit('first commit')
3222
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3223
self.build_tree_contents([('child/file', b'b\nc\n')])
3224
child_tree.commit('child commit')
3225
child_tree.lock_write()
3226
self.addCleanup(child_tree.unlock)
3227
work_tree.lock_write()
3228
self.addCleanup(work_tree.unlock)
3229
preview = TransformPreview(work_tree)
3230
self.addCleanup(preview.finalize)
3231
file_trans_id = preview.trans_id_file_id(b'file-id')
3232
preview.delete_contents(file_trans_id)
3233
preview.create_file([b'a\nb\n'], file_trans_id)
3234
preview_tree = preview.get_preview_tree()
3235
merger = Merger.from_revision_ids(preview_tree,
3236
child_tree.branch.last_revision(),
3237
other_branch=child_tree.branch,
3238
tree_branch=work_tree.branch)
3239
merger.merge_type = Merge3Merger
3240
tt = merger.make_merger().make_preview_transform()
3241
self.addCleanup(tt.finalize)
3242
final_tree = tt.get_preview_tree()
3245
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3247
def test_merge_preview_into_workingtree(self):
3248
tree = self.make_branch_and_tree('tree')
3249
tree.set_root_id(b'TREE_ROOT')
3250
tt = TransformPreview(tree)
3251
self.addCleanup(tt.finalize)
3252
tt.new_file('name', tt.root, [b'content'], b'file-id')
3253
tree2 = self.make_branch_and_tree('tree2')
3254
tree2.set_root_id(b'TREE_ROOT')
3255
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3257
merger.merge_type = Merge3Merger
3260
def test_merge_preview_into_workingtree_handles_conflicts(self):
3261
tree = self.make_branch_and_tree('tree')
3262
self.build_tree_contents([('tree/foo', b'bar')])
3263
tree.add('foo', b'foo-id')
3265
tt = TransformPreview(tree)
3266
self.addCleanup(tt.finalize)
3267
trans_id = tt.trans_id_file_id(b'foo-id')
3268
tt.delete_contents(trans_id)
3269
tt.create_file([b'baz'], trans_id)
3270
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3271
self.build_tree_contents([('tree2/foo', b'qux')])
3272
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3274
merger.merge_type = Merge3Merger
3277
def test_has_filename(self):
3278
wt = self.make_branch_and_tree('tree')
3279
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3280
tt = TransformPreview(wt)
3281
removed_id = tt.trans_id_tree_path('removed')
3282
tt.delete_contents(removed_id)
3283
tt.new_file('new', tt.root, [b'contents'])
3284
modified_id = tt.trans_id_tree_path('modified')
3285
tt.delete_contents(modified_id)
3286
tt.create_file([b'modified-contents'], modified_id)
3287
self.addCleanup(tt.finalize)
3288
tree = tt.get_preview_tree()
3289
self.assertTrue(tree.has_filename('unmodified'))
3290
self.assertFalse(tree.has_filename('not-present'))
3291
self.assertFalse(tree.has_filename('removed'))
3292
self.assertTrue(tree.has_filename('new'))
3293
self.assertTrue(tree.has_filename('modified'))
3295
def test_is_executable(self):
3296
tree = self.make_branch_and_tree('tree')
3297
preview = TransformPreview(tree)
3298
self.addCleanup(preview.finalize)
3299
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3300
preview_tree = preview.get_preview_tree()
3301
self.assertEqual(False, preview_tree.is_executable('tree/foo', b'baz-id'))
3302
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3304
def test_commit_preview_tree(self):
3305
tree = self.make_branch_and_tree('tree')
3306
rev_id = tree.commit('rev1')
3307
tree.branch.lock_write()
3308
self.addCleanup(tree.branch.unlock)
3309
tt = TransformPreview(tree)
3310
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3311
self.addCleanup(tt.finalize)
3312
preview = tt.get_preview_tree()
3313
preview.set_parent_ids([rev_id])
3314
builder = tree.branch.get_commit_builder([rev_id])
3315
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3316
builder.finish_inventory()
3317
rev2_id = builder.commit('rev2')
3318
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3319
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3321
def test_ascii_limbo_paths(self):
3322
self.requireFeature(features.UnicodeFilenameFeature)
3323
branch = self.make_branch('any')
3324
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3325
tt = TransformPreview(tree)
3326
self.addCleanup(tt.finalize)
3327
foo_id = tt.new_directory('', ROOT_PARENT)
3328
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3329
limbo_path = tt._limbo_name(bar_id)
3330
self.assertEqual(limbo_path, limbo_path)
3333
class FakeSerializer(object):
3334
"""Serializer implementation that simply returns the input.
3336
The input is returned in the order used by pack.ContainerPushParser.
3339
def bytes_record(bytes, names):
3343
class TestSerializeTransform(tests.TestCaseWithTransport):
3345
_test_needs_features = [features.UnicodeFilenameFeature]
3347
def get_preview(self, tree=None):
3349
tree = self.make_branch_and_tree('tree')
3350
tt = TransformPreview(tree)
3351
self.addCleanup(tt.finalize)
3354
def assertSerializesTo(self, expected, tt):
3355
records = list(tt.serialize(FakeSerializer()))
3356
self.assertEqual(expected, records)
3359
def default_attribs():
3364
b'_new_executability': {},
3366
b'_tree_path_ids': {b'': b'new-0'},
3368
b'_removed_contents': [],
3369
b'_non_present_ids': {},
3372
def make_records(self, attribs, contents):
3374
((((b'attribs'),),), bencode.bencode(attribs))]
3375
records.extend([(((n, k),), c) for n, k, c in contents])
3378
def creation_records(self):
3379
attribs = self.default_attribs()
3380
attribs[b'_id_number'] = 3
3381
attribs[b'_new_name'] = {
3382
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3383
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3384
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3385
attribs[b'_new_executability'] = {b'new-1': 1}
3387
(b'new-1', b'file', b'i 1\nbar\n'),
3388
(b'new-2', b'directory', b''),
3390
return self.make_records(attribs, contents)
3392
def test_serialize_creation(self):
3393
tt = self.get_preview()
3394
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3395
tt.new_directory('qux', tt.root, b'quxx')
3396
self.assertSerializesTo(self.creation_records(), tt)
3398
def test_deserialize_creation(self):
3399
tt = self.get_preview()
3400
tt.deserialize(iter(self.creation_records()))
3401
self.assertEqual(3, tt._id_number)
3402
self.assertEqual({'new-1': u'foo\u1234',
3403
'new-2': 'qux'}, tt._new_name)
3404
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3405
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3406
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3407
self.assertEqual({'new-1': True}, tt._new_executability)
3408
self.assertEqual({'new-1': 'file',
3409
'new-2': 'directory'}, tt._new_contents)
3410
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3412
foo_content = foo_limbo.read()
3415
self.assertEqual(b'bar', foo_content)
3417
def symlink_creation_records(self):
3418
attribs = self.default_attribs()
3419
attribs[b'_id_number'] = 2
3420
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3421
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3422
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3423
return self.make_records(attribs, contents)
3425
def test_serialize_symlink_creation(self):
3426
self.requireFeature(features.SymlinkFeature)
3427
tt = self.get_preview()
3428
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3429
self.assertSerializesTo(self.symlink_creation_records(), tt)
3431
def test_deserialize_symlink_creation(self):
3432
self.requireFeature(features.SymlinkFeature)
3433
tt = self.get_preview()
3434
tt.deserialize(iter(self.symlink_creation_records()))
3435
abspath = tt._limbo_name('new-1')
3436
foo_content = osutils.readlink(abspath)
3437
self.assertEqual(u'bar\u1234', foo_content)
3439
def make_destruction_preview(self):
3440
tree = self.make_branch_and_tree('.')
3441
self.build_tree([u'foo\u1234', 'bar'])
3442
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3443
return self.get_preview(tree)
3445
def destruction_records(self):
3446
attribs = self.default_attribs()
3447
attribs[b'_id_number'] = 3
3448
attribs[b'_removed_id'] = [b'new-1']
3449
attribs[b'_removed_contents'] = [b'new-2']
3450
attribs[b'_tree_path_ids'] = {
3452
u'foo\u1234'.encode('utf-8'): b'new-1',
3455
return self.make_records(attribs, [])
3457
def test_serialize_destruction(self):
3458
tt = self.make_destruction_preview()
3459
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3460
tt.unversion_file(foo_trans_id)
3461
bar_trans_id = tt.trans_id_tree_path('bar')
3462
tt.delete_contents(bar_trans_id)
3463
self.assertSerializesTo(self.destruction_records(), tt)
3465
def test_deserialize_destruction(self):
3466
tt = self.make_destruction_preview()
3467
tt.deserialize(iter(self.destruction_records()))
3468
self.assertEqual({u'foo\u1234': 'new-1',
3470
'': tt.root}, tt._tree_path_ids)
3471
self.assertEqual({'new-1': u'foo\u1234',
3473
tt.root: ''}, tt._tree_id_paths)
3474
self.assertEqual({'new-1'}, tt._removed_id)
3475
self.assertEqual({'new-2'}, tt._removed_contents)
3477
def missing_records(self):
3478
attribs = self.default_attribs()
3479
attribs[b'_id_number'] = 2
3480
attribs[b'_non_present_ids'] = {
3482
return self.make_records(attribs, [])
3484
def test_serialize_missing(self):
3485
tt = self.get_preview()
3486
boo_trans_id = tt.trans_id_file_id(b'boo')
3487
self.assertSerializesTo(self.missing_records(), tt)
3489
def test_deserialize_missing(self):
3490
tt = self.get_preview()
3491
tt.deserialize(iter(self.missing_records()))
3492
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3494
def make_modification_preview(self):
3495
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3496
LINES_TWO = b'z\nbb\nx\ndd\n'
3497
tree = self.make_branch_and_tree('tree')
3498
self.build_tree_contents([('tree/file', LINES_ONE)])
3499
tree.add('file', b'file-id')
3500
return self.get_preview(tree), [LINES_TWO]
3502
def modification_records(self):
3503
attribs = self.default_attribs()
3504
attribs[b'_id_number'] = 2
3505
attribs[b'_tree_path_ids'] = {
3508
attribs[b'_removed_contents'] = [b'new-1']
3509
contents = [(b'new-1', b'file',
3510
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3511
return self.make_records(attribs, contents)
3513
def test_serialize_modification(self):
3514
tt, LINES = self.make_modification_preview()
3515
trans_id = tt.trans_id_file_id(b'file-id')
3516
tt.delete_contents(trans_id)
3517
tt.create_file(LINES, trans_id)
3518
self.assertSerializesTo(self.modification_records(), tt)
3520
def test_deserialize_modification(self):
3521
tt, LINES = self.make_modification_preview()
3522
tt.deserialize(iter(self.modification_records()))
3523
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3525
def make_kind_change_preview(self):
3526
LINES = b'a\nb\nc\nd\n'
3527
tree = self.make_branch_and_tree('tree')
3528
self.build_tree(['tree/foo/'])
3529
tree.add('foo', b'foo-id')
3530
return self.get_preview(tree), [LINES]
3532
def kind_change_records(self):
3533
attribs = self.default_attribs()
3534
attribs[b'_id_number'] = 2
3535
attribs[b'_tree_path_ids'] = {
3538
attribs[b'_removed_contents'] = [b'new-1']
3539
contents = [(b'new-1', b'file',
3540
b'i 4\na\nb\nc\nd\n\n')]
3541
return self.make_records(attribs, contents)
3543
def test_serialize_kind_change(self):
3544
tt, LINES = self.make_kind_change_preview()
3545
trans_id = tt.trans_id_file_id(b'foo-id')
3546
tt.delete_contents(trans_id)
3547
tt.create_file(LINES, trans_id)
3548
self.assertSerializesTo(self.kind_change_records(), tt)
3550
def test_deserialize_kind_change(self):
3551
tt, LINES = self.make_kind_change_preview()
3552
tt.deserialize(iter(self.kind_change_records()))
3553
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3555
def make_add_contents_preview(self):
3556
LINES = b'a\nb\nc\nd\n'
3557
tree = self.make_branch_and_tree('tree')
3558
self.build_tree(['tree/foo'])
3560
os.unlink('tree/foo')
3561
return self.get_preview(tree), LINES
3563
def add_contents_records(self):
3564
attribs = self.default_attribs()
3565
attribs[b'_id_number'] = 2
3566
attribs[b'_tree_path_ids'] = {
3569
contents = [(b'new-1', b'file',
3570
b'i 4\na\nb\nc\nd\n\n')]
3571
return self.make_records(attribs, contents)
3573
def test_serialize_add_contents(self):
3574
tt, LINES = self.make_add_contents_preview()
3575
trans_id = tt.trans_id_tree_path('foo')
3576
tt.create_file([LINES], trans_id)
3577
self.assertSerializesTo(self.add_contents_records(), tt)
3579
def test_deserialize_add_contents(self):
3580
tt, LINES = self.make_add_contents_preview()
3581
tt.deserialize(iter(self.add_contents_records()))
3582
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3584
def test_get_parents_lines(self):
3585
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3586
LINES_TWO = b'z\nbb\nx\ndd\n'
3587
tree = self.make_branch_and_tree('tree')
3588
self.build_tree_contents([('tree/file', LINES_ONE)])
3589
tree.add('file', b'file-id')
3590
tt = self.get_preview(tree)
3591
trans_id = tt.trans_id_tree_path('file')
3592
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3593
tt._get_parents_lines(trans_id))
3595
def test_get_parents_texts(self):
3596
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3597
LINES_TWO = b'z\nbb\nx\ndd\n'
3598
tree = self.make_branch_and_tree('tree')
3599
self.build_tree_contents([('tree/file', LINES_ONE)])
3600
tree.add('file', b'file-id')
3601
tt = self.get_preview(tree)
3602
trans_id = tt.trans_id_tree_path('file')
3603
self.assertEqual((LINES_ONE,),
3604
tt._get_parents_texts(trans_id))
3607
class TestOrphan(tests.TestCaseWithTransport):
3609
def test_no_orphan_for_transform_preview(self):
3610
tree = self.make_branch_and_tree('tree')
3611
tt = transform.TransformPreview(tree)
3612
self.addCleanup(tt.finalize)
3613
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3615
def _set_orphan_policy(self, wt, policy):
3616
wt.branch.get_config_stack().set('transform.orphan_policy',
3619
def _prepare_orphan(self, wt):
3620
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3621
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3622
wt.commit('add dir and file ignoring foo')
3623
tt = transform.TreeTransform(wt)
3624
self.addCleanup(tt.finalize)
3625
# dir and bar are deleted
3626
dir_tid = tt.trans_id_tree_path('dir')
3627
file_tid = tt.trans_id_tree_path('dir/file')
3628
orphan_tid = tt.trans_id_tree_path('dir/foo')
3629
tt.delete_contents(file_tid)
3630
tt.unversion_file(file_tid)
3631
tt.delete_contents(dir_tid)
3632
tt.unversion_file(dir_tid)
3633
# There should be a conflict because dir still contain foo
3634
raw_conflicts = tt.find_conflicts()
3635
self.assertLength(1, raw_conflicts)
3636
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3637
return tt, orphan_tid
3639
def test_new_orphan_created(self):
3640
wt = self.make_branch_and_tree('.')
3641
self._set_orphan_policy(wt, 'move')
3642
tt, orphan_tid = self._prepare_orphan(wt)
3645
warnings.append(args[0] % args[1:])
3646
self.overrideAttr(trace, 'warning', warning)
3647
remaining_conflicts = resolve_conflicts(tt)
3648
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3650
# Yeah for resolved conflicts !
3651
self.assertLength(0, remaining_conflicts)
3652
# We have a new orphan
3653
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3654
self.assertEqual('brz-orphans',
3655
tt.final_name(tt.final_parent(orphan_tid)))
3657
def test_never_orphan(self):
3658
wt = self.make_branch_and_tree('.')
3659
self._set_orphan_policy(wt, 'conflict')
3660
tt, orphan_tid = self._prepare_orphan(wt)
3661
remaining_conflicts = resolve_conflicts(tt)
3662
self.assertLength(1, remaining_conflicts)
3663
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3664
remaining_conflicts.pop())
3666
def test_orphan_error(self):
3667
def bogus_orphan(tt, orphan_id, parent_id):
3668
raise transform.OrphaningError(tt.final_name(orphan_id),
3669
tt.final_name(parent_id))
3670
transform.orphaning_registry.register('bogus', bogus_orphan,
3671
'Raise an error when orphaning')
3672
wt = self.make_branch_and_tree('.')
3673
self._set_orphan_policy(wt, 'bogus')
3674
tt, orphan_tid = self._prepare_orphan(wt)
3675
remaining_conflicts = resolve_conflicts(tt)
3676
self.assertLength(1, remaining_conflicts)
3677
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3678
remaining_conflicts.pop())
3680
def test_unknown_orphan_policy(self):
3681
wt = self.make_branch_and_tree('.')
3682
# Set a fictional policy nobody ever implemented
3683
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3684
tt, orphan_tid = self._prepare_orphan(wt)
3687
warnings.append(args[0] % args[1:])
3688
self.overrideAttr(trace, 'warning', warning)
3689
remaining_conflicts = resolve_conflicts(tt)
3690
# We fallback to the default policy which create a conflict
3691
self.assertLength(1, remaining_conflicts)
3692
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3693
remaining_conflicts.pop())
3694
self.assertLength(1, warnings)
3695
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3698
class TestTransformHooks(tests.TestCaseWithTransport):
3701
super(TestTransformHooks, self).setUp()
3702
self.wt = self.make_branch_and_tree('.')
3705
def get_transform(self):
3706
transform = TreeTransform(self.wt)
3707
self.addCleanup(transform.finalize)
3708
return transform, transform.root
3710
def test_pre_commit_hooks(self):
3712
def record_pre_transform(tree, tt):
3713
calls.append((tree, tt))
3714
MutableTree.hooks.install_named_hook('pre_transform',
3715
record_pre_transform, "Pre transform")
3716
transform, root = self.get_transform()
3717
old_root_id = transform.tree_file_id(root)
3719
self.assertEqual(old_root_id, self.wt.get_root_id())
3720
self.assertEqual([(self.wt, transform)], calls)
3722
def test_post_commit_hooks(self):
3724
def record_post_transform(tree, tt):
3725
calls.append((tree, tt))
3726
MutableTree.hooks.install_named_hook('post_transform',
3727
record_post_transform, "Post transform")
3728
transform, root = self.get_transform()
3729
old_root_id = transform.tree_file_id(root)
3731
self.assertEqual(old_root_id, self.wt.get_root_id())
3732
self.assertEqual([(self.wt, transform)], calls)
3735
class TestLinkTree(tests.TestCaseWithTransport):
3737
_test_needs_features = [HardlinkFeature]
3740
tests.TestCaseWithTransport.setUp(self)
3741
self.parent_tree = self.make_branch_and_tree('parent')
3742
self.parent_tree.lock_write()
3743
self.addCleanup(self.parent_tree.unlock)
3744
self.build_tree_contents([('parent/foo', b'bar')])
3745
self.parent_tree.add('foo')
3746
self.parent_tree.commit('added foo')
3747
child_controldir = self.parent_tree.controldir.sprout('child')
3748
self.child_tree = child_controldir.open_workingtree()
3750
def hardlinked(self):
3751
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3752
child_stat = os.lstat(self.child_tree.abspath('foo'))
3753
return parent_stat.st_ino == child_stat.st_ino
3755
def test_link_fails_if_modified(self):
3756
"""If the file to be linked has modified text, don't link."""
3757
self.build_tree_contents([('child/foo', b'baz')])
3758
transform.link_tree(self.child_tree, self.parent_tree)
3759
self.assertFalse(self.hardlinked())
3761
def test_link_fails_if_execute_bit_changed(self):
3762
"""If the file to be linked has modified execute bit, don't link."""
3763
tt = TreeTransform(self.child_tree)
3765
trans_id = tt.trans_id_tree_path('foo')
3766
tt.set_executability(True, trans_id)
3770
transform.link_tree(self.child_tree, self.parent_tree)
3771
self.assertFalse(self.hardlinked())
3773
def test_link_succeeds_if_unmodified(self):
3774
"""If the file to be linked is unmodified, link"""
3775
transform.link_tree(self.child_tree, self.parent_tree)
3776
self.assertTrue(self.hardlinked())