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 (
70
from .features import (
74
from ..transform import (
88
class TestTreeTransform(tests.TestCaseWithTransport):
91
super(TestTreeTransform, self).setUp()
92
self.wt = self.make_branch_and_tree('.', format='development-subtree')
95
def get_transform(self):
96
transform = TreeTransform(self.wt)
97
self.addCleanup(transform.finalize)
98
return transform, transform.root
100
def get_transform_for_sha1_test(self):
101
trans, root = self.get_transform()
102
self.wt.lock_tree_write()
103
self.addCleanup(self.wt.unlock)
104
contents = ['just some content\n']
105
sha1 = osutils.sha_strings(contents)
106
# Roll back the clock
107
trans._creation_mtime = time.time() - 20.0
108
return trans, root, contents, sha1
110
def test_existing_limbo(self):
111
transform, root = self.get_transform()
112
limbo_name = transform._limbodir
113
deletion_path = transform._deletiondir
114
os.mkdir(pathjoin(limbo_name, 'hehe'))
115
self.assertRaises(ImmortalLimbo, transform.apply)
116
self.assertRaises(LockError, self.wt.unlock)
117
self.assertRaises(ExistingLimbo, self.get_transform)
118
self.assertRaises(LockError, self.wt.unlock)
119
os.rmdir(pathjoin(limbo_name, 'hehe'))
121
os.rmdir(deletion_path)
122
transform, root = self.get_transform()
125
def test_existing_pending_deletion(self):
126
transform, root = self.get_transform()
127
deletion_path = self._limbodir = urlutils.local_path_from_url(
128
transform._tree._transport.abspath('pending-deletion'))
129
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
130
self.assertRaises(ImmortalPendingDeletion, transform.apply)
131
self.assertRaises(LockError, self.wt.unlock)
132
self.assertRaises(ExistingPendingDeletion, self.get_transform)
134
def test_build(self):
135
transform, root = self.get_transform()
136
self.wt.lock_tree_write()
137
self.addCleanup(self.wt.unlock)
138
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
139
imaginary_id = transform.trans_id_tree_path('imaginary')
140
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
141
self.assertEqual(imaginary_id, imaginary_id2)
142
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
143
self.assertEqual('directory', transform.final_kind(root))
144
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
145
trans_id = transform.create_path('name', root)
146
self.assertIs(transform.final_file_id(trans_id), None)
147
self.assertIs(None, transform.final_kind(trans_id))
148
transform.create_file('contents', trans_id)
149
transform.set_executability(True, trans_id)
150
transform.version_file('my_pretties', trans_id)
151
self.assertRaises(DuplicateKey, transform.version_file,
152
'my_pretties', trans_id)
153
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
154
self.assertEqual(transform.final_parent(trans_id), root)
155
self.assertIs(transform.final_parent(root), ROOT_PARENT)
156
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
157
oz_id = transform.create_path('oz', root)
158
transform.create_directory(oz_id)
159
transform.version_file('ozzie', oz_id)
160
trans_id2 = transform.create_path('name2', root)
161
transform.create_file('contents', trans_id2)
162
transform.set_executability(False, trans_id2)
163
transform.version_file('my_pretties2', trans_id2)
164
modified_paths = transform.apply().modified_paths
165
self.assertEqual('contents', self.wt.get_file('name').read())
166
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
167
self.assertIs(self.wt.is_executable('name'), True)
168
self.assertIs(self.wt.is_executable('name2'), False)
169
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
170
self.assertEqual(len(modified_paths), 3)
171
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
172
('ozzie', 'my_pretties', 'my_pretties2')]
173
self.assertSubset(tree_mod_paths, modified_paths)
174
# is it safe to finalize repeatedly?
178
def test_apply_informs_tree_of_observed_sha1(self):
179
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
180
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
183
orig = self.wt._observed_sha1
184
def _observed_sha1(*args):
187
self.wt._observed_sha1 = _observed_sha1
189
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
192
def test_create_file_caches_sha1(self):
193
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
194
trans_id = trans.create_path('file1', root)
195
trans.create_file(contents, trans_id, sha1=sha1)
196
st_val = osutils.lstat(trans._limbo_name(trans_id))
197
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
198
self.assertEqual(o_sha1, sha1)
199
self.assertEqualStat(o_st_val, st_val)
201
def test__apply_insertions_updates_sha1(self):
202
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
203
trans_id = trans.create_path('file1', root)
204
trans.create_file(contents, trans_id, sha1=sha1)
205
st_val = osutils.lstat(trans._limbo_name(trans_id))
206
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
207
self.assertEqual(o_sha1, sha1)
208
self.assertEqualStat(o_st_val, st_val)
209
creation_mtime = trans._creation_mtime + 10.0
210
# We fake a time difference from when the file was created until now it
211
# is being renamed by using os.utime. Note that the change we actually
212
# want to see is the real ctime change from 'os.rename()', but as long
213
# as we observe a new stat value, we should be fine.
214
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
216
new_st_val = osutils.lstat(self.wt.abspath('file1'))
217
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
218
self.assertEqual(o_sha1, sha1)
219
self.assertEqualStat(o_st_val, new_st_val)
220
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
222
def test_new_file_caches_sha1(self):
223
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
224
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
226
st_val = osutils.lstat(trans._limbo_name(trans_id))
227
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
228
self.assertEqual(o_sha1, sha1)
229
self.assertEqualStat(o_st_val, st_val)
231
def test_cancel_creation_removes_observed_sha1(self):
232
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
233
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
235
self.assertTrue(trans_id in trans._observed_sha1s)
236
trans.cancel_creation(trans_id)
237
self.assertFalse(trans_id in trans._observed_sha1s)
239
def test_create_files_same_timestamp(self):
240
transform, root = self.get_transform()
241
self.wt.lock_tree_write()
242
self.addCleanup(self.wt.unlock)
243
# Roll back the clock, so that we know everything is being set to the
245
transform._creation_mtime = creation_mtime = time.time() - 20.0
246
transform.create_file('content-one',
247
transform.create_path('one', root))
248
time.sleep(1) # *ugly*
249
transform.create_file('content-two',
250
transform.create_path('two', root))
252
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
254
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
256
# We only guarantee 2s resolution
257
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
258
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
259
# But if we have more than that, all files should get the same result
260
self.assertEqual(st1.st_mtime, st2.st_mtime)
262
def test_change_root_id(self):
263
transform, root = self.get_transform()
264
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
265
transform.new_directory('', ROOT_PARENT, b'new-root-id')
266
transform.delete_contents(root)
267
transform.unversion_file(root)
268
transform.fixup_new_roots()
270
self.assertEqual(b'new-root-id', self.wt.get_root_id())
272
def test_change_root_id_add_files(self):
273
transform, root = self.get_transform()
274
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
275
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
276
transform.new_file('file', new_trans_id, ['new-contents\n'],
278
transform.delete_contents(root)
279
transform.unversion_file(root)
280
transform.fixup_new_roots()
282
self.assertEqual(b'new-root-id', self.wt.get_root_id())
283
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
284
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
286
def test_add_two_roots(self):
287
transform, root = self.get_transform()
288
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
289
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
290
self.assertRaises(ValueError, transform.fixup_new_roots)
292
def test_retain_existing_root(self):
293
tt, root = self.get_transform()
295
tt.new_directory('', ROOT_PARENT, b'new-root-id')
297
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
299
def test_retain_existing_root_added_file(self):
300
tt, root = self.get_transform()
301
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
302
child = tt.new_directory('child', new_trans_id, b'child-id')
304
self.assertEqual(tt.root, tt.final_parent(child))
306
def test_add_unversioned_root(self):
307
transform, root = self.get_transform()
308
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
309
transform.delete_contents(transform.root)
310
transform.fixup_new_roots()
311
self.assertNotIn(transform.root, transform._new_id)
313
def test_remove_root_fixup(self):
314
transform, root = self.get_transform()
315
old_root_id = self.wt.get_root_id()
316
self.assertNotEqual(b'new-root-id', old_root_id)
317
transform.delete_contents(root)
318
transform.unversion_file(root)
319
transform.fixup_new_roots()
321
self.assertEqual(old_root_id, self.wt.get_root_id())
323
transform, root = self.get_transform()
324
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
325
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
326
self.assertRaises(ValueError, transform.fixup_new_roots)
328
def test_fixup_new_roots_permits_empty_tree(self):
329
transform, root = self.get_transform()
330
transform.delete_contents(root)
331
transform.unversion_file(root)
332
transform.fixup_new_roots()
333
self.assertIs(None, transform.final_kind(root))
334
self.assertIs(None, transform.final_file_id(root))
336
def test_apply_retains_root_directory(self):
337
# Do not attempt to delete the physical root directory, because that
339
transform, root = self.get_transform()
341
transform.delete_contents(root)
342
e = self.assertRaises(AssertionError, self.assertRaises,
343
errors.TransformRenameFailed,
345
self.assertContainsRe('TransformRenameFailed not raised', str(e))
347
def test_apply_retains_file_id(self):
348
transform, root = self.get_transform()
349
old_root_id = transform.tree_file_id(root)
350
transform.unversion_file(root)
352
self.assertEqual(old_root_id, self.wt.get_root_id())
354
def test_hardlink(self):
355
self.requireFeature(HardlinkFeature)
356
transform, root = self.get_transform()
357
transform.new_file('file1', root, 'contents')
359
target = self.make_branch_and_tree('target')
360
target_transform = TreeTransform(target)
361
trans_id = target_transform.create_path('file1', target_transform.root)
362
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
363
target_transform.apply()
364
self.assertPathExists('target/file1')
365
source_stat = os.stat(self.wt.abspath('file1'))
366
target_stat = os.stat('target/file1')
367
self.assertEqual(source_stat, target_stat)
369
def test_convenience(self):
370
transform, root = self.get_transform()
371
self.wt.lock_tree_write()
372
self.addCleanup(self.wt.unlock)
373
trans_id = transform.new_file('name', root, 'contents',
375
oz = transform.new_directory('oz', root, 'oz-id')
376
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
377
toto = transform.new_file('toto', dorothy, 'toto-contents',
380
self.assertEqual(len(transform.find_conflicts()), 0)
382
self.assertRaises(ReusingTransform, transform.find_conflicts)
383
self.assertEqual('contents', file(self.wt.abspath('name')).read())
384
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
385
self.assertIs(self.wt.is_executable('name'), True)
386
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
387
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
388
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
390
self.assertEqual('toto-contents',
391
self.wt.get_file('oz/dorothy/toto').read())
392
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
394
def test_tree_reference(self):
395
transform, root = self.get_transform()
396
tree = transform._tree
397
trans_id = transform.new_directory('reference', root, 'subtree-id')
398
transform.set_tree_reference('subtree-revision', trans_id)
401
self.addCleanup(tree.unlock)
402
self.assertEqual('subtree-revision',
403
tree.root_inventory.get_entry('subtree-id').reference_revision)
405
def test_conflicts(self):
406
transform, root = self.get_transform()
407
trans_id = transform.new_file('name', root, 'contents',
409
self.assertEqual(len(transform.find_conflicts()), 0)
410
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
411
self.assertEqual(transform.find_conflicts(),
412
[('duplicate', trans_id, trans_id2, 'name')])
413
self.assertRaises(MalformedTransform, transform.apply)
414
transform.adjust_path('name', trans_id, trans_id2)
415
self.assertEqual(transform.find_conflicts(),
416
[('non-directory parent', trans_id)])
417
tinman_id = transform.trans_id_tree_path('tinman')
418
transform.adjust_path('name', tinman_id, trans_id2)
419
self.assertEqual(transform.find_conflicts(),
420
[('unversioned parent', tinman_id),
421
('missing parent', tinman_id)])
422
lion_id = transform.create_path('lion', root)
423
self.assertEqual(transform.find_conflicts(),
424
[('unversioned parent', tinman_id),
425
('missing parent', tinman_id)])
426
transform.adjust_path('name', lion_id, trans_id2)
427
self.assertEqual(transform.find_conflicts(),
428
[('unversioned parent', lion_id),
429
('missing parent', lion_id)])
430
transform.version_file("Courage", lion_id)
431
self.assertEqual(transform.find_conflicts(),
432
[('missing parent', lion_id),
433
('versioning no contents', lion_id)])
434
transform.adjust_path('name2', root, trans_id2)
435
self.assertEqual(transform.find_conflicts(),
436
[('versioning no contents', lion_id)])
437
transform.create_file('Contents, okay?', lion_id)
438
transform.adjust_path('name2', trans_id2, trans_id2)
439
self.assertEqual(transform.find_conflicts(),
440
[('parent loop', trans_id2),
441
('non-directory parent', trans_id2)])
442
transform.adjust_path('name2', root, trans_id2)
443
oz_id = transform.new_directory('oz', root)
444
transform.set_executability(True, oz_id)
445
self.assertEqual(transform.find_conflicts(),
446
[('unversioned executability', oz_id)])
447
transform.version_file('oz-id', oz_id)
448
self.assertEqual(transform.find_conflicts(),
449
[('non-file executability', oz_id)])
450
transform.set_executability(None, oz_id)
451
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
453
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
454
self.assertEqual('contents', file(self.wt.abspath('name')).read())
455
transform2, root = self.get_transform()
456
oz_id = transform2.trans_id_tree_path('oz')
457
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
458
result = transform2.find_conflicts()
459
fp = FinalPaths(transform2)
460
self.assertTrue('oz/tip' in transform2._tree_path_ids)
461
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
462
self.assertEqual(len(result), 2)
463
self.assertEqual((result[0][0], result[0][1]),
464
('duplicate', newtip))
465
self.assertEqual((result[1][0], result[1][2]),
466
('duplicate id', newtip))
467
transform2.finalize()
468
transform3 = TreeTransform(self.wt)
469
self.addCleanup(transform3.finalize)
470
oz_id = transform3.trans_id_tree_path('oz')
471
transform3.delete_contents(oz_id)
472
self.assertEqual(transform3.find_conflicts(),
473
[('missing parent', oz_id)])
474
root_id = transform3.root
475
tip_id = transform3.trans_id_tree_path('oz/tip')
476
transform3.adjust_path('tip', root_id, tip_id)
479
def test_conflict_on_case_insensitive(self):
480
tree = self.make_branch_and_tree('tree')
481
# Don't try this at home, kids!
482
# Force the tree to report that it is case sensitive, for conflict
484
tree.case_sensitive = True
485
transform = TreeTransform(tree)
486
self.addCleanup(transform.finalize)
487
transform.new_file('file', transform.root, 'content')
488
transform.new_file('FiLe', transform.root, 'content')
489
result = transform.find_conflicts()
490
self.assertEqual([], result)
492
# Force the tree to report that it is case insensitive, for conflict
494
tree.case_sensitive = False
495
transform = TreeTransform(tree)
496
self.addCleanup(transform.finalize)
497
transform.new_file('file', transform.root, 'content')
498
transform.new_file('FiLe', transform.root, 'content')
499
result = transform.find_conflicts()
500
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
502
def test_conflict_on_case_insensitive_existing(self):
503
tree = self.make_branch_and_tree('tree')
504
self.build_tree(['tree/FiLe'])
505
# Don't try this at home, kids!
506
# Force the tree to report that it is case sensitive, for conflict
508
tree.case_sensitive = True
509
transform = TreeTransform(tree)
510
self.addCleanup(transform.finalize)
511
transform.new_file('file', transform.root, 'content')
512
result = transform.find_conflicts()
513
self.assertEqual([], result)
515
# Force the tree to report that it is case insensitive, for conflict
517
tree.case_sensitive = False
518
transform = TreeTransform(tree)
519
self.addCleanup(transform.finalize)
520
transform.new_file('file', transform.root, 'content')
521
result = transform.find_conflicts()
522
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
524
def test_resolve_case_insensitive_conflict(self):
525
tree = self.make_branch_and_tree('tree')
526
# Don't try this at home, kids!
527
# Force the tree to report that it is case insensitive, for conflict
529
tree.case_sensitive = False
530
transform = TreeTransform(tree)
531
self.addCleanup(transform.finalize)
532
transform.new_file('file', transform.root, 'content')
533
transform.new_file('FiLe', transform.root, 'content')
534
resolve_conflicts(transform)
536
self.assertPathExists('tree/file')
537
self.assertPathExists('tree/FiLe.moved')
539
def test_resolve_checkout_case_conflict(self):
540
tree = self.make_branch_and_tree('tree')
541
# Don't try this at home, kids!
542
# Force the tree to report that it is case insensitive, for conflict
544
tree.case_sensitive = False
545
transform = TreeTransform(tree)
546
self.addCleanup(transform.finalize)
547
transform.new_file('file', transform.root, 'content')
548
transform.new_file('FiLe', transform.root, 'content')
549
resolve_conflicts(transform,
550
pass_func=lambda t, c: resolve_checkout(t, c, []))
552
self.assertPathExists('tree/file')
553
self.assertPathExists('tree/FiLe.moved')
555
def test_apply_case_conflict(self):
556
"""Ensure that a transform with case conflicts can always be applied"""
557
tree = self.make_branch_and_tree('tree')
558
transform = TreeTransform(tree)
559
self.addCleanup(transform.finalize)
560
transform.new_file('file', transform.root, 'content')
561
transform.new_file('FiLe', transform.root, 'content')
562
dir = transform.new_directory('dir', transform.root)
563
transform.new_file('dirfile', dir, 'content')
564
transform.new_file('dirFiLe', dir, 'content')
565
resolve_conflicts(transform)
567
self.assertPathExists('tree/file')
568
if not os.path.exists('tree/FiLe.moved'):
569
self.assertPathExists('tree/FiLe')
570
self.assertPathExists('tree/dir/dirfile')
571
if not os.path.exists('tree/dir/dirFiLe.moved'):
572
self.assertPathExists('tree/dir/dirFiLe')
574
def test_case_insensitive_limbo(self):
575
tree = self.make_branch_and_tree('tree')
576
# Don't try this at home, kids!
577
# Force the tree to report that it is case insensitive
578
tree.case_sensitive = False
579
transform = TreeTransform(tree)
580
self.addCleanup(transform.finalize)
581
dir = transform.new_directory('dir', transform.root)
582
first = transform.new_file('file', dir, 'content')
583
second = transform.new_file('FiLe', dir, 'content')
584
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
585
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
587
def test_adjust_path_updates_child_limbo_names(self):
588
tree = self.make_branch_and_tree('tree')
589
transform = TreeTransform(tree)
590
self.addCleanup(transform.finalize)
591
foo_id = transform.new_directory('foo', transform.root)
592
bar_id = transform.new_directory('bar', foo_id)
593
baz_id = transform.new_directory('baz', bar_id)
594
qux_id = transform.new_directory('qux', baz_id)
595
transform.adjust_path('quxx', foo_id, bar_id)
596
self.assertStartsWith(transform._limbo_name(qux_id),
597
transform._limbo_name(bar_id))
599
def test_add_del(self):
600
start, root = self.get_transform()
601
start.new_directory('a', root, 'a')
603
transform, root = self.get_transform()
604
transform.delete_versioned(transform.trans_id_tree_path('a'))
605
transform.new_directory('a', root, 'a')
608
def test_unversioning(self):
609
create_tree, root = self.get_transform()
610
parent_id = create_tree.new_directory('parent', root, b'parent-id')
611
create_tree.new_file('child', parent_id, 'child', b'child-id')
613
unversion = TreeTransform(self.wt)
614
self.addCleanup(unversion.finalize)
615
parent = unversion.trans_id_tree_path('parent')
616
unversion.unversion_file(parent)
617
self.assertEqual(unversion.find_conflicts(),
618
[('unversioned parent', parent_id)])
619
file_id = unversion.trans_id_tree_path('parent/child')
620
unversion.unversion_file(file_id)
623
def test_name_invariants(self):
624
create_tree, root = self.get_transform()
626
root = create_tree.root
627
create_tree.new_file('name1', root, 'hello1', 'name1')
628
create_tree.new_file('name2', root, 'hello2', 'name2')
629
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
630
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
631
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
632
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
635
mangle_tree, root = self.get_transform()
636
root = mangle_tree.root
638
name1 = mangle_tree.trans_id_tree_path('name1')
639
name2 = mangle_tree.trans_id_tree_path('name2')
640
mangle_tree.adjust_path('name2', root, name1)
641
mangle_tree.adjust_path('name1', root, name2)
643
#tests for deleting parent directories
644
ddir = mangle_tree.trans_id_tree_path('dying_directory')
645
mangle_tree.delete_contents(ddir)
646
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
647
mangle_tree.delete_versioned(dfile)
648
mangle_tree.unversion_file(dfile)
649
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
650
mangle_tree.adjust_path('mfile', root, mfile)
652
#tests for adding parent directories
653
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
654
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
655
mangle_tree.adjust_path('mfile2', newdir, mfile2)
656
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
657
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
658
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
659
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
661
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
662
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
663
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
664
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
665
self.assertEqual(file(mfile2_path).read(), 'later2')
666
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
667
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
668
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
669
self.assertEqual(file(newfile_path).read(), 'hello3')
670
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
671
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
672
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
674
def test_both_rename(self):
675
create_tree, root = self.get_transform()
676
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
677
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
679
mangle_tree, root = self.get_transform()
680
selftest = mangle_tree.trans_id_tree_path('selftest')
681
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
682
mangle_tree.adjust_path('test', root, selftest)
683
mangle_tree.adjust_path('test_too_much', root, selftest)
684
mangle_tree.set_executability(True, blackbox)
687
def test_both_rename2(self):
688
create_tree, root = self.get_transform()
689
breezy = create_tree.new_directory('breezy', root, 'breezy-id')
690
tests = create_tree.new_directory('tests', breezy, 'tests-id')
691
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
692
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
695
mangle_tree, root = self.get_transform()
696
breezy = mangle_tree.trans_id_tree_path('breezy')
697
tests = mangle_tree.trans_id_tree_path('breezy/tests')
698
test_too_much = mangle_tree.trans_id_tree_path('breezy/tests/blackbox/test_too_much.py')
699
mangle_tree.adjust_path('selftest', breezy, tests)
700
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
701
mangle_tree.set_executability(True, test_too_much)
704
def test_both_rename3(self):
705
create_tree, root = self.get_transform()
706
tests = create_tree.new_directory('tests', root, 'tests-id')
707
create_tree.new_file('test_too_much.py', tests, 'hello1',
710
mangle_tree, root = self.get_transform()
711
tests = mangle_tree.trans_id_tree_path('tests')
712
test_too_much = mangle_tree.trans_id_tree_path('tests/test_too_much.py')
713
mangle_tree.adjust_path('selftest', root, tests)
714
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
715
mangle_tree.set_executability(True, test_too_much)
718
def test_move_dangling_ie(self):
719
create_tree, root = self.get_transform()
721
root = create_tree.root
722
create_tree.new_file('name1', root, 'hello1', 'name1')
724
delete_contents, root = self.get_transform()
725
file = delete_contents.trans_id_tree_path('name1')
726
delete_contents.delete_contents(file)
727
delete_contents.apply()
728
move_id, root = self.get_transform()
729
name1 = move_id.trans_id_tree_path('name1')
730
newdir = move_id.new_directory('dir', root, 'newdir')
731
move_id.adjust_path('name2', newdir, name1)
734
def test_replace_dangling_ie(self):
735
create_tree, root = self.get_transform()
737
root = create_tree.root
738
create_tree.new_file('name1', root, 'hello1', 'name1')
740
delete_contents = TreeTransform(self.wt)
741
self.addCleanup(delete_contents.finalize)
742
file = delete_contents.trans_id_tree_path('name1')
743
delete_contents.delete_contents(file)
744
delete_contents.apply()
745
delete_contents.finalize()
746
replace = TreeTransform(self.wt)
747
self.addCleanup(replace.finalize)
748
name2 = replace.new_file('name2', root, 'hello2', 'name1')
749
conflicts = replace.find_conflicts()
750
name1 = replace.trans_id_tree_path('name1')
751
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
752
resolve_conflicts(replace)
755
def _test_symlinks(self, link_name1, link_target1,
756
link_name2, link_target2):
758
def ozpath(p): return 'oz/' + p
760
self.requireFeature(SymlinkFeature)
761
transform, root = self.get_transform()
762
oz_id = transform.new_directory('oz', root, 'oz-id')
763
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
765
wiz_id = transform.create_path(link_name2, oz_id)
766
transform.create_symlink(link_target2, wiz_id)
767
transform.version_file('wiz-id2', wiz_id)
768
transform.set_executability(True, wiz_id)
769
self.assertEqual(transform.find_conflicts(),
770
[('non-file executability', wiz_id)])
771
transform.set_executability(None, wiz_id)
773
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
774
self.assertEqual('symlink',
775
file_kind(self.wt.abspath(ozpath(link_name1))))
776
self.assertEqual(link_target2,
777
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
778
self.assertEqual(link_target1,
779
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
781
def test_symlinks(self):
782
self._test_symlinks('wizard', 'wizard-target',
783
'wizard2', 'behind_curtain')
785
def test_symlinks_unicode(self):
786
self.requireFeature(features.UnicodeFilenameFeature)
787
self._test_symlinks(u'\N{Euro Sign}wizard',
788
u'wizard-targ\N{Euro Sign}t',
789
u'\N{Euro Sign}wizard2',
790
u'b\N{Euro Sign}hind_curtain')
792
def test_unable_create_symlink(self):
794
wt = self.make_branch_and_tree('.')
795
tt = TreeTransform(wt) # TreeTransform obtains write lock
797
tt.new_symlink('foo', tt.root, 'bar')
801
os_symlink = getattr(os, 'symlink', None)
804
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
806
"Unable to create symlink 'foo' on this platform",
810
os.symlink = os_symlink
812
def get_conflicted(self):
813
create, root = self.get_transform()
814
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
815
oz = create.new_directory('oz', root, 'oz-id')
816
create.new_directory('emeraldcity', oz, 'emerald-id')
818
conflicts, root = self.get_transform()
819
# set up duplicate entry, duplicate id
820
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
822
old_dorothy = conflicts.trans_id_tree_path('dorothy')
823
oz = conflicts.trans_id_tree_path('oz')
824
# set up DeletedParent parent conflict
825
conflicts.delete_versioned(oz)
826
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
827
# set up MissingParent conflict
828
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
829
conflicts.adjust_path('munchkincity', root, munchkincity)
830
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
832
conflicts.adjust_path('emeraldcity', emerald, emerald)
833
return conflicts, emerald, oz, old_dorothy, new_dorothy
835
def test_conflict_resolution(self):
836
conflicts, emerald, oz, old_dorothy, new_dorothy =\
837
self.get_conflicted()
838
resolve_conflicts(conflicts)
839
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
840
self.assertIs(conflicts.final_file_id(old_dorothy), None)
841
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
842
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
843
self.assertEqual(conflicts.final_parent(emerald), oz)
846
def test_cook_conflicts(self):
847
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
848
raw_conflicts = resolve_conflicts(tt)
849
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
850
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
851
'dorothy', None, 'dorothy-id')
852
self.assertEqual(cooked_conflicts[0], duplicate)
853
duplicate_id = DuplicateID('Unversioned existing file',
854
'dorothy.moved', 'dorothy', None,
856
self.assertEqual(cooked_conflicts[1], duplicate_id)
857
missing_parent = MissingParent('Created directory', 'munchkincity',
859
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
860
self.assertEqual(cooked_conflicts[2], missing_parent)
861
unversioned_parent = UnversionedParent('Versioned directory',
864
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
866
self.assertEqual(cooked_conflicts[3], unversioned_parent)
867
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
868
'oz/emeraldcity', 'emerald-id', 'emerald-id')
869
self.assertEqual(cooked_conflicts[4], deleted_parent)
870
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
871
self.assertEqual(cooked_conflicts[6], parent_loop)
872
self.assertEqual(len(cooked_conflicts), 7)
875
def test_string_conflicts(self):
876
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
877
raw_conflicts = resolve_conflicts(tt)
878
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
880
conflicts_s = [unicode(c) for c in cooked_conflicts]
881
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
882
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
883
'Moved existing file to '
885
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
886
'Unversioned existing file '
888
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
889
' munchkincity. Created directory.')
890
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
891
' versioned, but has versioned'
892
' children. Versioned directory.')
893
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
894
" is not empty. Not deleting.")
895
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
896
' versioned, but has versioned'
897
' children. Versioned directory.')
898
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
899
' oz/emeraldcity. Cancelled move.')
901
def prepare_wrong_parent_kind(self):
902
tt, root = self.get_transform()
903
tt.new_file('parent', root, 'contents', b'parent-id')
905
tt, root = self.get_transform()
906
parent_id = tt.trans_id_file_id('parent-id')
907
tt.new_file('child,', parent_id, 'contents2', b'file-id')
910
def test_find_conflicts_wrong_parent_kind(self):
911
tt = self.prepare_wrong_parent_kind()
914
def test_resolve_conflicts_wrong_existing_parent_kind(self):
915
tt = self.prepare_wrong_parent_kind()
916
raw_conflicts = resolve_conflicts(tt)
917
self.assertEqual({('non-directory parent', 'Created directory',
918
'new-3')}, raw_conflicts)
919
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
920
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
921
b'parent-id')], cooked_conflicts)
923
self.assertFalse(self.wt.is_versioned('parent'))
924
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
926
def test_resolve_conflicts_wrong_new_parent_kind(self):
927
tt, root = self.get_transform()
928
parent_id = tt.new_directory('parent', root, b'parent-id')
929
tt.new_file('child,', parent_id, 'contents2', b'file-id')
931
tt, root = self.get_transform()
932
parent_id = tt.trans_id_file_id('parent-id')
933
tt.delete_contents(parent_id)
934
tt.create_file('contents', parent_id)
935
raw_conflicts = resolve_conflicts(tt)
936
self.assertEqual({('non-directory parent', 'Created directory',
937
'new-3')}, raw_conflicts)
939
self.assertFalse(self.wt.is_versioned('parent'))
940
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
942
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
943
tt, root = self.get_transform()
944
parent_id = tt.new_directory('parent', root)
945
tt.new_file('child,', parent_id, 'contents2')
947
tt, root = self.get_transform()
948
parent_id = tt.trans_id_tree_path('parent')
949
tt.delete_contents(parent_id)
950
tt.create_file('contents', parent_id)
951
resolve_conflicts(tt)
953
self.assertFalse(self.wt.is_versioned('parent'))
954
self.assertFalse(self.wt.is_versioned('parent.new'))
956
def test_resolve_conflicts_missing_parent(self):
957
wt = self.make_branch_and_tree('.')
958
tt = TreeTransform(wt)
959
self.addCleanup(tt.finalize)
960
parent = tt.trans_id_file_id('parent-id')
961
tt.new_file('file', parent, 'Contents')
962
raw_conflicts = resolve_conflicts(tt)
963
# Since the directory doesn't exist it's seen as 'missing'. So
964
# 'resolve_conflicts' create a conflict asking for it to be created.
965
self.assertLength(1, raw_conflicts)
966
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
968
# apply fail since the missing directory doesn't exist
969
self.assertRaises(errors.NoFinalPath, tt.apply)
971
def test_moving_versioned_directories(self):
972
create, root = self.get_transform()
973
kansas = create.new_directory('kansas', root, 'kansas-id')
974
create.new_directory('house', kansas, 'house-id')
975
create.new_directory('oz', root, 'oz-id')
977
cyclone, root = self.get_transform()
978
oz = cyclone.trans_id_tree_path('oz')
979
house = cyclone.trans_id_tree_path('house')
980
cyclone.adjust_path('house', oz, house)
983
def test_moving_root(self):
984
create, root = self.get_transform()
985
fun = create.new_directory('fun', root, 'fun-id')
986
create.new_directory('sun', root, 'sun-id')
987
create.new_directory('moon', root, 'moon')
989
transform, root = self.get_transform()
990
transform.adjust_root_path('oldroot', fun)
991
new_root = transform.trans_id_tree_path('')
992
transform.version_file('new-root', new_root)
995
def test_renames(self):
996
create, root = self.get_transform()
997
old = create.new_directory('old-parent', root, 'old-id')
998
intermediate = create.new_directory('intermediate', old, 'im-id')
999
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1002
rename, root = self.get_transform()
1003
old = rename.trans_id_file_id('old-id')
1004
rename.adjust_path('new', root, old)
1005
myfile = rename.trans_id_file_id('myfile-id')
1006
rename.set_executability(True, myfile)
1009
def test_rename_fails(self):
1010
self.requireFeature(features.not_running_as_root)
1011
# see https://bugs.launchpad.net/bzr/+bug/491763
1012
create, root_id = self.get_transform()
1013
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1014
myfile = create.new_file('myfile', root_id, 'myfile-text',
1017
if os.name == "posix" and sys.platform != "cygwin":
1018
# posix filesystems fail on renaming if the readonly bit is set
1019
osutils.make_readonly(self.wt.abspath('first-dir'))
1020
elif os.name == "nt":
1021
# windows filesystems fail on renaming open files
1022
self.addCleanup(file(self.wt.abspath('myfile')).close)
1024
self.skipTest("Can't force a permissions error on rename")
1025
# now transform to rename
1026
rename_transform, root_id = self.get_transform()
1027
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1028
dir_id = rename_transform.trans_id_file_id('first-id')
1029
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1030
e = self.assertRaises(errors.TransformRenameFailed,
1031
rename_transform.apply)
1032
# On nix looks like:
1033
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1034
# to .../first-dir/newname: [Errno 13] Permission denied"
1035
# On windows looks like:
1036
# "Failed to rename .../work/myfile to
1037
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1038
# This test isn't concerned with exactly what the error looks like,
1039
# and the strerror will vary across OS and locales, but the assert
1040
# that the exeception attributes are what we expect
1041
self.assertEqual(e.errno, errno.EACCES)
1042
if os.name == "posix":
1043
self.assertEndsWith(e.to_path, "/first-dir/newname")
1045
self.assertEqual(os.path.basename(e.from_path), "myfile")
1047
def test_set_executability_order(self):
1048
"""Ensure that executability behaves the same, no matter what order.
1050
- create file and set executability simultaneously
1051
- create file and set executability afterward
1052
- unsetting the executability of a file whose executability has not been
1053
declared should throw an exception (this may happen when a
1054
merge attempts to create a file with a duplicate ID)
1056
transform, root = self.get_transform()
1057
wt = transform._tree
1059
self.addCleanup(wt.unlock)
1060
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1062
sac = transform.new_file('set_after_creation', root,
1063
'Set after creation', 'sac')
1064
transform.set_executability(True, sac)
1065
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1067
self.assertRaises(KeyError, transform.set_executability, None, uws)
1069
self.assertTrue(wt.is_executable('set_on_creation'))
1070
self.assertTrue(wt.is_executable('set_after_creation'))
1072
def test_preserve_mode(self):
1073
"""File mode is preserved when replacing content"""
1074
if sys.platform == 'win32':
1075
raise TestSkipped('chmod has no effect on win32')
1076
transform, root = self.get_transform()
1077
transform.new_file('file1', root, 'contents', 'file1-id', True)
1079
self.wt.lock_write()
1080
self.addCleanup(self.wt.unlock)
1081
self.assertTrue(self.wt.is_executable('file1'))
1082
transform, root = self.get_transform()
1083
file1_id = transform.trans_id_tree_path('file1')
1084
transform.delete_contents(file1_id)
1085
transform.create_file('contents2', file1_id)
1087
self.assertTrue(self.wt.is_executable('file1'))
1089
def test__set_mode_stats_correctly(self):
1090
"""_set_mode stats to determine file mode."""
1091
if sys.platform == 'win32':
1092
raise TestSkipped('chmod has no effect on win32')
1096
def instrumented_stat(path):
1097
stat_paths.append(path)
1098
return real_stat(path)
1100
transform, root = self.get_transform()
1102
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1103
file_id='bar-id-1', executable=False)
1106
transform, root = self.get_transform()
1107
bar1_id = transform.trans_id_tree_path('bar')
1108
bar2_id = transform.trans_id_tree_path('bar2')
1110
os.stat = instrumented_stat
1111
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1114
transform.finalize()
1116
bar1_abspath = self.wt.abspath('bar')
1117
self.assertEqual([bar1_abspath], stat_paths)
1119
def test_iter_changes(self):
1120
self.wt.set_root_id(b'eert_toor')
1121
transform, root = self.get_transform()
1122
transform.new_file('old', root, 'blah', 'id-1', True)
1124
transform, root = self.get_transform()
1126
self.assertEqual([], list(transform.iter_changes()))
1127
old = transform.trans_id_tree_path('old')
1128
transform.unversion_file(old)
1129
self.assertEqual([('id-1', ('old', None), False, (True, False),
1130
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1131
(True, True))], list(transform.iter_changes()))
1132
transform.new_directory('new', root, 'id-1')
1133
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1134
('eert_toor', 'eert_toor'), ('old', 'new'),
1135
('file', 'directory'),
1136
(True, False))], list(transform.iter_changes()))
1138
transform.finalize()
1140
def test_iter_changes_new(self):
1141
self.wt.set_root_id(b'eert_toor')
1142
transform, root = self.get_transform()
1143
transform.new_file('old', root, 'blah')
1145
transform, root = self.get_transform()
1147
old = transform.trans_id_tree_path('old')
1148
transform.version_file('id-1', old)
1149
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1150
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1151
(False, False))], list(transform.iter_changes()))
1153
transform.finalize()
1155
def test_iter_changes_modifications(self):
1156
self.wt.set_root_id(b'eert_toor')
1157
transform, root = self.get_transform()
1158
transform.new_file('old', root, 'blah', 'id-1')
1159
transform.new_file('new', root, 'blah')
1160
transform.new_directory('subdir', root, 'subdir-id')
1162
transform, root = self.get_transform()
1164
old = transform.trans_id_tree_path('old')
1165
subdir = transform.trans_id_tree_path('subdir')
1166
new = transform.trans_id_tree_path('new')
1167
self.assertEqual([], list(transform.iter_changes()))
1170
transform.delete_contents(old)
1171
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1172
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1173
(False, False))], list(transform.iter_changes()))
1176
transform.create_file('blah', old)
1177
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1178
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1179
(False, False))], list(transform.iter_changes()))
1180
transform.cancel_deletion(old)
1181
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1182
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1183
(False, False))], list(transform.iter_changes()))
1184
transform.cancel_creation(old)
1186
# move file_id to a different file
1187
self.assertEqual([], list(transform.iter_changes()))
1188
transform.unversion_file(old)
1189
transform.version_file('id-1', new)
1190
transform.adjust_path('old', root, new)
1191
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1192
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1193
(False, False))], list(transform.iter_changes()))
1194
transform.cancel_versioning(new)
1195
transform._removed_id = set()
1198
self.assertEqual([], list(transform.iter_changes()))
1199
transform.set_executability(True, old)
1200
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1201
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1202
(False, True))], list(transform.iter_changes()))
1203
transform.set_executability(None, old)
1206
self.assertEqual([], list(transform.iter_changes()))
1207
transform.adjust_path('new', root, old)
1208
transform._new_parent = {}
1209
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1210
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1211
(False, False))], list(transform.iter_changes()))
1212
transform._new_name = {}
1215
self.assertEqual([], list(transform.iter_changes()))
1216
transform.adjust_path('new', subdir, old)
1217
transform._new_name = {}
1218
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1219
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1220
('file', 'file'), (False, False))],
1221
list(transform.iter_changes()))
1222
transform._new_path = {}
1225
transform.finalize()
1227
def test_iter_changes_modified_bleed(self):
1228
self.wt.set_root_id(b'eert_toor')
1229
"""Modified flag should not bleed from one change to another"""
1230
# unfortunately, we have no guarantee that file1 (which is modified)
1231
# will be applied before file2. And if it's applied after file2, it
1232
# obviously can't bleed into file2's change output. But for now, it
1234
transform, root = self.get_transform()
1235
transform.new_file('file1', root, 'blah', 'id-1')
1236
transform.new_file('file2', root, 'blah', 'id-2')
1238
transform, root = self.get_transform()
1240
transform.delete_contents(transform.trans_id_file_id('id-1'))
1241
transform.set_executability(True,
1242
transform.trans_id_file_id('id-2'))
1243
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1244
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1245
('file', None), (False, False)),
1246
('id-2', (u'file2', u'file2'), False, (True, True),
1247
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1248
('file', 'file'), (False, True))],
1249
list(transform.iter_changes()))
1251
transform.finalize()
1253
def test_iter_changes_move_missing(self):
1254
"""Test moving ids with no files around"""
1255
self.wt.set_root_id(b'toor_eert')
1256
# Need two steps because versioning a non-existant file is a conflict.
1257
transform, root = self.get_transform()
1258
transform.new_directory('floater', root, 'floater-id')
1260
transform, root = self.get_transform()
1261
transform.delete_contents(transform.trans_id_tree_path('floater'))
1263
transform, root = self.get_transform()
1264
floater = transform.trans_id_tree_path('floater')
1266
transform.adjust_path('flitter', root, floater)
1267
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1268
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1269
(None, None), (False, False))], list(transform.iter_changes()))
1271
transform.finalize()
1273
def test_iter_changes_pointless(self):
1274
"""Ensure that no-ops are not treated as modifications"""
1275
self.wt.set_root_id(b'eert_toor')
1276
transform, root = self.get_transform()
1277
transform.new_file('old', root, 'blah', 'id-1')
1278
transform.new_directory('subdir', root, 'subdir-id')
1280
transform, root = self.get_transform()
1282
old = transform.trans_id_tree_path('old')
1283
subdir = transform.trans_id_tree_path('subdir')
1284
self.assertEqual([], list(transform.iter_changes()))
1285
transform.delete_contents(subdir)
1286
transform.create_directory(subdir)
1287
transform.set_executability(False, old)
1288
transform.unversion_file(old)
1289
transform.version_file('id-1', old)
1290
transform.adjust_path('old', root, old)
1291
self.assertEqual([], list(transform.iter_changes()))
1293
transform.finalize()
1295
def test_rename_count(self):
1296
transform, root = self.get_transform()
1297
transform.new_file('name1', root, 'contents')
1298
self.assertEqual(transform.rename_count, 0)
1300
self.assertEqual(transform.rename_count, 1)
1301
transform2, root = self.get_transform()
1302
transform2.adjust_path('name2', root,
1303
transform2.trans_id_tree_path('name1'))
1304
self.assertEqual(transform2.rename_count, 0)
1306
self.assertEqual(transform2.rename_count, 2)
1308
def test_change_parent(self):
1309
"""Ensure that after we change a parent, the results are still right.
1311
Renames and parent changes on pending transforms can happen as part
1312
of conflict resolution, and are explicitly permitted by the
1315
This test ensures they work correctly with the rename-avoidance
1318
transform, root = self.get_transform()
1319
parent1 = transform.new_directory('parent1', root)
1320
child1 = transform.new_file('child1', parent1, 'contents')
1321
parent2 = transform.new_directory('parent2', root)
1322
transform.adjust_path('child1', parent2, child1)
1324
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1325
self.assertPathExists(self.wt.abspath('parent2/child1'))
1326
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1327
# no rename for child1 (counting only renames during apply)
1328
self.assertEqual(2, transform.rename_count)
1330
def test_cancel_parent(self):
1331
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1333
This is like the test_change_parent, except that we cancel the parent
1334
before adjusting the path. The transform must detect that the
1335
directory is non-empty, and move children to safe locations.
1337
transform, root = self.get_transform()
1338
parent1 = transform.new_directory('parent1', root)
1339
child1 = transform.new_file('child1', parent1, 'contents')
1340
child2 = transform.new_file('child2', parent1, 'contents')
1342
transform.cancel_creation(parent1)
1344
self.fail('Failed to move child1 before deleting parent1')
1345
transform.cancel_creation(child2)
1346
transform.create_directory(parent1)
1348
transform.cancel_creation(parent1)
1349
# If the transform incorrectly believes that child2 is still in
1350
# parent1's limbo directory, it will try to rename it and fail
1351
# because was already moved by the first cancel_creation.
1353
self.fail('Transform still thinks child2 is a child of parent1')
1354
parent2 = transform.new_directory('parent2', root)
1355
transform.adjust_path('child1', parent2, child1)
1357
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1358
self.assertPathExists(self.wt.abspath('parent2/child1'))
1359
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1360
self.assertEqual(2, transform.rename_count)
1362
def test_adjust_and_cancel(self):
1363
"""Make sure adjust_path keeps track of limbo children properly"""
1364
transform, root = self.get_transform()
1365
parent1 = transform.new_directory('parent1', root)
1366
child1 = transform.new_file('child1', parent1, 'contents')
1367
parent2 = transform.new_directory('parent2', root)
1368
transform.adjust_path('child1', parent2, child1)
1369
transform.cancel_creation(child1)
1371
transform.cancel_creation(parent1)
1372
# if the transform thinks child1 is still in parent1's limbo
1373
# directory, it will attempt to move it and fail.
1375
self.fail('Transform still thinks child1 is a child of parent1')
1376
transform.finalize()
1378
def test_noname_contents(self):
1379
"""TreeTransform should permit deferring naming files."""
1380
transform, root = self.get_transform()
1381
parent = transform.trans_id_file_id('parent-id')
1383
transform.create_directory(parent)
1385
self.fail("Can't handle contents with no name")
1386
transform.finalize()
1388
def test_noname_contents_nested(self):
1389
"""TreeTransform should permit deferring naming files."""
1390
transform, root = self.get_transform()
1391
parent = transform.trans_id_file_id('parent-id')
1393
transform.create_directory(parent)
1395
self.fail("Can't handle contents with no name")
1396
child = transform.new_directory('child', parent)
1397
transform.adjust_path('parent', root, parent)
1399
self.assertPathExists(self.wt.abspath('parent/child'))
1400
self.assertEqual(1, transform.rename_count)
1402
def test_reuse_name(self):
1403
"""Avoid reusing the same limbo name for different files"""
1404
transform, root = self.get_transform()
1405
parent = transform.new_directory('parent', root)
1406
child1 = transform.new_directory('child', parent)
1408
child2 = transform.new_directory('child', parent)
1410
self.fail('Tranform tried to use the same limbo name twice')
1411
transform.adjust_path('child2', parent, child2)
1413
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1414
# child2 is put into top-level limbo because child1 has already
1415
# claimed the direct limbo path when child2 is created. There is no
1416
# advantage in renaming files once they're in top-level limbo, except
1418
self.assertEqual(2, transform.rename_count)
1420
def test_reuse_when_first_moved(self):
1421
"""Don't avoid direct paths when it is safe to use them"""
1422
transform, root = self.get_transform()
1423
parent = transform.new_directory('parent', root)
1424
child1 = transform.new_directory('child', parent)
1425
transform.adjust_path('child1', parent, child1)
1426
child2 = transform.new_directory('child', parent)
1428
# limbo/new-1 => parent
1429
self.assertEqual(1, transform.rename_count)
1431
def test_reuse_after_cancel(self):
1432
"""Don't avoid direct paths when it is safe to use them"""
1433
transform, root = self.get_transform()
1434
parent2 = transform.new_directory('parent2', root)
1435
child1 = transform.new_directory('child1', parent2)
1436
transform.cancel_creation(parent2)
1437
transform.create_directory(parent2)
1438
child2 = transform.new_directory('child1', parent2)
1439
transform.adjust_path('child2', parent2, child1)
1441
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1442
self.assertEqual(2, transform.rename_count)
1444
def test_finalize_order(self):
1445
"""Finalize must be done in child-to-parent order"""
1446
transform, root = self.get_transform()
1447
parent = transform.new_directory('parent', root)
1448
child = transform.new_directory('child', parent)
1450
transform.finalize()
1452
self.fail('Tried to remove parent before child1')
1454
def test_cancel_with_cancelled_child_should_succeed(self):
1455
transform, root = self.get_transform()
1456
parent = transform.new_directory('parent', root)
1457
child = transform.new_directory('child', parent)
1458
transform.cancel_creation(child)
1459
transform.cancel_creation(parent)
1460
transform.finalize()
1462
def test_rollback_on_directory_clash(self):
1464
wt = self.make_branch_and_tree('.')
1465
tt = TreeTransform(wt) # TreeTransform obtains write lock
1467
foo = tt.new_directory('foo', tt.root)
1468
tt.new_file('bar', foo, 'foobar')
1469
baz = tt.new_directory('baz', tt.root)
1470
tt.new_file('qux', baz, 'quux')
1471
# Ask for a rename 'foo' -> 'baz'
1472
tt.adjust_path('baz', tt.root, foo)
1473
# Lie to tt that we've already resolved all conflicts.
1474
tt.apply(no_conflicts=True)
1478
# The rename will fail because the target directory is not empty (but
1479
# raises FileExists anyway).
1480
err = self.assertRaises(errors.FileExists, tt_helper)
1481
self.assertEndsWith(err.path, "/baz")
1483
def test_two_directories_clash(self):
1485
wt = self.make_branch_and_tree('.')
1486
tt = TreeTransform(wt) # TreeTransform obtains write lock
1488
foo_1 = tt.new_directory('foo', tt.root)
1489
tt.new_directory('bar', foo_1)
1490
# Adding the same directory with a different content
1491
foo_2 = tt.new_directory('foo', tt.root)
1492
tt.new_directory('baz', foo_2)
1493
# Lie to tt that we've already resolved all conflicts.
1494
tt.apply(no_conflicts=True)
1498
err = self.assertRaises(errors.FileExists, tt_helper)
1499
self.assertEndsWith(err.path, "/foo")
1501
def test_two_directories_clash_finalize(self):
1503
wt = self.make_branch_and_tree('.')
1504
tt = TreeTransform(wt) # TreeTransform obtains write lock
1506
foo_1 = tt.new_directory('foo', tt.root)
1507
tt.new_directory('bar', foo_1)
1508
# Adding the same directory with a different content
1509
foo_2 = tt.new_directory('foo', tt.root)
1510
tt.new_directory('baz', foo_2)
1511
# Lie to tt that we've already resolved all conflicts.
1512
tt.apply(no_conflicts=True)
1516
err = self.assertRaises(errors.FileExists, tt_helper)
1517
self.assertEndsWith(err.path, "/foo")
1519
def test_file_to_directory(self):
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
tt.delete_contents(foo_trans_id)
1528
tt.create_directory(foo_trans_id)
1529
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1530
tt.create_file(["aa\n"], bar_trans_id)
1531
tt.version_file("bar-1", bar_trans_id)
1533
self.assertPathExists("foo/bar")
1536
self.assertEqual(wt.kind("foo"), "directory")
1540
changes = wt.changes_from(wt.basis_tree())
1541
self.assertFalse(changes.has_changed(), changes)
1543
def test_file_to_symlink(self):
1544
self.requireFeature(SymlinkFeature)
1545
wt = self.make_branch_and_tree('.')
1546
self.build_tree(['foo'])
1549
tt = TreeTransform(wt)
1550
self.addCleanup(tt.finalize)
1551
foo_trans_id = tt.trans_id_tree_path("foo")
1552
tt.delete_contents(foo_trans_id)
1553
tt.create_symlink("bar", foo_trans_id)
1555
self.assertPathExists("foo")
1557
self.addCleanup(wt.unlock)
1558
self.assertEqual(wt.kind("foo"), "symlink")
1560
def test_dir_to_file(self):
1561
wt = self.make_branch_and_tree('.')
1562
self.build_tree(['foo/', 'foo/bar'])
1563
wt.add(['foo', 'foo/bar'])
1565
tt = TreeTransform(wt)
1566
self.addCleanup(tt.finalize)
1567
foo_trans_id = tt.trans_id_tree_path("foo")
1568
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1569
tt.delete_contents(foo_trans_id)
1570
tt.delete_versioned(bar_trans_id)
1571
tt.create_file(["aa\n"], foo_trans_id)
1573
self.assertPathExists("foo")
1575
self.addCleanup(wt.unlock)
1576
self.assertEqual(wt.kind("foo"), "file")
1578
def test_dir_to_hardlink(self):
1579
self.requireFeature(HardlinkFeature)
1580
wt = self.make_branch_and_tree('.')
1581
self.build_tree(['foo/', 'foo/bar'])
1582
wt.add(['foo', 'foo/bar'])
1584
tt = TreeTransform(wt)
1585
self.addCleanup(tt.finalize)
1586
foo_trans_id = tt.trans_id_tree_path("foo")
1587
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1588
tt.delete_contents(foo_trans_id)
1589
tt.delete_versioned(bar_trans_id)
1590
self.build_tree(['baz'])
1591
tt.create_hardlink("baz", foo_trans_id)
1593
self.assertPathExists("foo")
1594
self.assertPathExists("baz")
1596
self.addCleanup(wt.unlock)
1597
self.assertEqual(wt.kind("foo"), "file")
1599
def test_no_final_path(self):
1600
transform, root = self.get_transform()
1601
trans_id = transform.trans_id_file_id('foo')
1602
transform.create_file('bar', trans_id)
1603
transform.cancel_creation(trans_id)
1606
def test_create_from_tree(self):
1607
tree1 = self.make_branch_and_tree('tree1')
1608
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1609
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1610
tree2 = self.make_branch_and_tree('tree2')
1611
tt = TreeTransform(tree2)
1612
foo_trans_id = tt.create_path('foo', tt.root)
1613
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1614
bar_trans_id = tt.create_path('bar', tt.root)
1615
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id='bbar-id')
1617
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1618
self.assertFileEqual('baz', 'tree2/bar')
1620
def test_create_from_tree_bytes(self):
1621
"""Provided lines are used instead of tree content."""
1622
tree1 = self.make_branch_and_tree('tree1')
1623
self.build_tree_contents([('tree1/foo', 'bar'),])
1624
tree1.add('foo', b'foo-id')
1625
tree2 = self.make_branch_and_tree('tree2')
1626
tt = TreeTransform(tree2)
1627
foo_trans_id = tt.create_path('foo', tt.root)
1628
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id',
1631
self.assertFileEqual('qux', 'tree2/foo')
1633
def test_create_from_tree_symlink(self):
1634
self.requireFeature(SymlinkFeature)
1635
tree1 = self.make_branch_and_tree('tree1')
1636
os.symlink('bar', 'tree1/foo')
1637
tree1.add('foo', b'foo-id')
1638
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1639
foo_trans_id = tt.create_path('foo', tt.root)
1640
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1642
self.assertEqual('bar', os.readlink('tree2/foo'))
1645
class TransformGroup(object):
1647
def __init__(self, dirname, root_id):
1650
self.wt = ControlDir.create_standalone_workingtree(dirname)
1651
self.wt.set_root_id(root_id)
1652
self.b = self.wt.branch
1653
self.tt = TreeTransform(self.wt)
1654
self.root = self.tt.trans_id_tree_path('')
1657
def conflict_text(tree, merge):
1658
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1659
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1662
class TestInventoryAltered(tests.TestCaseWithTransport):
1664
def test_inventory_altered_unchanged(self):
1665
tree = self.make_branch_and_tree('tree')
1666
self.build_tree(['tree/foo'])
1667
tree.add('foo', b'foo-id')
1668
with TransformPreview(tree) as tt:
1669
self.assertEqual([], tt._inventory_altered())
1671
def test_inventory_altered_changed_parent_id(self):
1672
tree = self.make_branch_and_tree('tree')
1673
self.build_tree(['tree/foo'])
1674
tree.add('foo', b'foo-id')
1675
with TransformPreview(tree) as tt:
1676
tt.unversion_file(tt.root)
1677
tt.version_file(b'new-id', tt.root)
1678
foo_trans_id = tt.trans_id_tree_path('foo')
1679
foo_tuple = ('foo', foo_trans_id)
1680
root_tuple = ('', tt.root)
1681
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1683
def test_inventory_altered_noop_changed_parent_id(self):
1684
tree = self.make_branch_and_tree('tree')
1685
self.build_tree(['tree/foo'])
1686
tree.add('foo', b'foo-id')
1687
with TransformPreview(tree) as tt:
1688
tt.unversion_file(tt.root)
1689
tt.version_file(tree.get_root_id(), tt.root)
1690
foo_trans_id = tt.trans_id_tree_path('foo')
1691
self.assertEqual([], tt._inventory_altered())
1694
class TestTransformMerge(TestCaseInTempDir):
1696
def test_text_merge(self):
1697
root_id = generate_ids.gen_root_id()
1698
base = TransformGroup("base", root_id)
1699
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1700
base.tt.new_file('b', base.root, 'b1', 'b')
1701
base.tt.new_file('c', base.root, 'c', 'c')
1702
base.tt.new_file('d', base.root, 'd', 'd')
1703
base.tt.new_file('e', base.root, 'e', 'e')
1704
base.tt.new_file('f', base.root, 'f', 'f')
1705
base.tt.new_directory('g', base.root, 'g')
1706
base.tt.new_directory('h', base.root, 'h')
1708
other = TransformGroup("other", root_id)
1709
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1710
other.tt.new_file('b', other.root, 'b2', 'b')
1711
other.tt.new_file('c', other.root, 'c2', 'c')
1712
other.tt.new_file('d', other.root, 'd', 'd')
1713
other.tt.new_file('e', other.root, 'e2', 'e')
1714
other.tt.new_file('f', other.root, 'f', 'f')
1715
other.tt.new_file('g', other.root, 'g', 'g')
1716
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1717
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1719
this = TransformGroup("this", root_id)
1720
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1721
this.tt.new_file('b', this.root, 'b', 'b')
1722
this.tt.new_file('c', this.root, 'c', 'c')
1723
this.tt.new_file('d', this.root, 'd2', 'd')
1724
this.tt.new_file('e', this.root, 'e2', 'e')
1725
this.tt.new_file('f', this.root, 'f', 'f')
1726
this.tt.new_file('g', this.root, 'g', 'g')
1727
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1728
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1730
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1733
self.assertEqual(this.wt.get_file(this.wt.id2path(b'a')).read(), 'y\nb\nc\nd\bz\n')
1734
# three-way text conflict
1735
self.assertEqual(this.wt.get_file(this.wt.id2path(b'b')).read(),
1736
conflict_text('b', 'b2'))
1738
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), 'c2')
1740
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), 'd2')
1741
# Ambigious clean merge
1742
self.assertEqual(this.wt.get_file(this.wt.id2path(b'e')).read(), 'e2')
1744
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), 'f')
1745
# Correct correct results when THIS == OTHER
1746
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), 'g')
1747
# Text conflict when THIS & OTHER are text and BASE is dir
1748
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1749
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1750
self.assertEqual(this.wt.get_file('h.THIS').read(),
1752
self.assertEqual(this.wt.get_file('h.OTHER').read(),
1754
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1755
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1756
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1757
self.assertEqual(this.wt.get_file('i.THIS').read(),
1759
self.assertEqual(this.wt.get_file('i.OTHER').read(),
1761
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1762
modified = ['a', 'b', 'c', 'h', 'i']
1763
merge_modified = this.wt.merge_modified()
1764
self.assertSubset(merge_modified, modified)
1765
self.assertEqual(len(merge_modified), len(modified))
1766
with file(this.wt.abspath(this.wt.id2path('a')), 'wb') as f: f.write('booga')
1768
merge_modified = this.wt.merge_modified()
1769
self.assertSubset(merge_modified, modified)
1770
self.assertEqual(len(merge_modified), len(modified))
1774
def test_file_merge(self):
1775
self.requireFeature(SymlinkFeature)
1776
root_id = generate_ids.gen_root_id()
1777
base = TransformGroup("BASE", root_id)
1778
this = TransformGroup("THIS", root_id)
1779
other = TransformGroup("OTHER", root_id)
1780
for tg in this, base, other:
1781
tg.tt.new_directory('a', tg.root, 'a')
1782
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1783
tg.tt.new_file('c', tg.root, 'c', 'c')
1784
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1785
targets = ((base, 'base-e', 'base-f', None, None),
1786
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1787
(other, 'other-e', None, 'other-g', 'other-h'))
1788
for tg, e_target, f_target, g_target, h_target in targets:
1789
for link, target in (('e', e_target), ('f', f_target),
1790
('g', g_target), ('h', h_target)):
1791
if target is not None:
1792
tg.tt.new_symlink(link, tg.root, target, link)
1794
for tg in this, base, other:
1796
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1797
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1798
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1799
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1800
for suffix in ('THIS', 'BASE', 'OTHER'):
1801
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1802
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1803
self.assertEqual(this.wt.id2path(b'd'), 'd.OTHER')
1804
self.assertEqual(this.wt.id2path(b'f'), 'f.THIS')
1805
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1806
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1807
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1808
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1809
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1810
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1811
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1812
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1813
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1814
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1816
def test_filename_merge(self):
1817
root_id = generate_ids.gen_root_id()
1818
base = TransformGroup("BASE", root_id)
1819
this = TransformGroup("THIS", root_id)
1820
other = TransformGroup("OTHER", root_id)
1821
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1822
for t in [base, this, other]]
1823
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1824
for t in [base, this, other]]
1825
base.tt.new_directory('c', base_a, 'c')
1826
this.tt.new_directory('c1', this_a, 'c')
1827
other.tt.new_directory('c', other_b, 'c')
1829
base.tt.new_directory('d', base_a, 'd')
1830
this.tt.new_directory('d1', this_b, 'd')
1831
other.tt.new_directory('d', other_a, 'd')
1833
base.tt.new_directory('e', base_a, 'e')
1834
this.tt.new_directory('e', this_a, 'e')
1835
other.tt.new_directory('e1', other_b, 'e')
1837
base.tt.new_directory('f', base_a, 'f')
1838
this.tt.new_directory('f1', this_b, 'f')
1839
other.tt.new_directory('f1', other_b, 'f')
1841
for tg in [this, base, other]:
1843
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1844
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1845
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1846
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1847
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
1849
def test_filename_merge_conflicts(self):
1850
root_id = generate_ids.gen_root_id()
1851
base = TransformGroup("BASE", root_id)
1852
this = TransformGroup("THIS", root_id)
1853
other = TransformGroup("OTHER", root_id)
1854
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1855
for t in [base, this, other]]
1856
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1857
for t in [base, this, other]]
1859
base.tt.new_file('g', base_a, 'g', 'g')
1860
other.tt.new_file('g1', other_b, 'g1', 'g')
1862
base.tt.new_file('h', base_a, 'h', 'h')
1863
this.tt.new_file('h1', this_b, 'h1', 'h')
1865
base.tt.new_file('i', base.root, 'i', 'i')
1866
other.tt.new_directory('i1', this_b, 'i')
1868
for tg in [this, base, other]:
1870
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1872
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
1873
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1874
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1875
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
1876
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1877
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1878
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1881
class TestBuildTree(tests.TestCaseWithTransport):
1883
def test_build_tree_with_symlinks(self):
1884
self.requireFeature(SymlinkFeature)
1886
a = ControlDir.create_standalone_workingtree('a')
1888
with file('a/foo/bar', 'wb') as f: f.write('contents')
1889
os.symlink('a/foo/bar', 'a/foo/baz')
1890
a.add(['foo', 'foo/bar', 'foo/baz'])
1891
a.commit('initial commit')
1892
b = ControlDir.create_standalone_workingtree('b')
1893
basis = a.basis_tree()
1895
self.addCleanup(basis.unlock)
1896
build_tree(basis, b)
1897
self.assertIs(os.path.isdir('b/foo'), True)
1898
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1899
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1901
def test_build_with_references(self):
1902
tree = self.make_branch_and_tree('source',
1903
format='development-subtree')
1904
subtree = self.make_branch_and_tree('source/subtree',
1905
format='development-subtree')
1906
tree.add_reference(subtree)
1907
tree.commit('a revision')
1908
tree.branch.create_checkout('target')
1909
self.assertPathExists('target')
1910
self.assertPathExists('target/subtree')
1912
def test_file_conflict_handling(self):
1913
"""Ensure that when building trees, conflict handling is done"""
1914
source = self.make_branch_and_tree('source')
1915
target = self.make_branch_and_tree('target')
1916
self.build_tree(['source/file', 'target/file'])
1917
source.add('file', 'new-file')
1918
source.commit('added file')
1919
build_tree(source.basis_tree(), target)
1920
self.assertEqual([DuplicateEntry('Moved existing file to',
1921
'file.moved', 'file', None, 'new-file')],
1923
target2 = self.make_branch_and_tree('target2')
1924
target_file = file('target2/file', 'wb')
1926
source_file = file('source/file', 'rb')
1928
target_file.write(source_file.read())
1933
build_tree(source.basis_tree(), target2)
1934
self.assertEqual([], target2.conflicts())
1936
def test_symlink_conflict_handling(self):
1937
"""Ensure that when building trees, conflict handling is done"""
1938
self.requireFeature(SymlinkFeature)
1939
source = self.make_branch_and_tree('source')
1940
os.symlink('foo', 'source/symlink')
1941
source.add('symlink', 'new-symlink')
1942
source.commit('added file')
1943
target = self.make_branch_and_tree('target')
1944
os.symlink('bar', 'target/symlink')
1945
build_tree(source.basis_tree(), target)
1946
self.assertEqual([DuplicateEntry('Moved existing file to',
1947
'symlink.moved', 'symlink', None, 'new-symlink')],
1949
target = self.make_branch_and_tree('target2')
1950
os.symlink('foo', 'target2/symlink')
1951
build_tree(source.basis_tree(), target)
1952
self.assertEqual([], target.conflicts())
1954
def test_directory_conflict_handling(self):
1955
"""Ensure that when building trees, conflict handling is done"""
1956
source = self.make_branch_and_tree('source')
1957
target = self.make_branch_and_tree('target')
1958
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1959
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1960
source.commit('added file')
1961
build_tree(source.basis_tree(), target)
1962
self.assertEqual([], target.conflicts())
1963
self.assertPathExists('target/dir1/file')
1965
# Ensure contents are merged
1966
target = self.make_branch_and_tree('target2')
1967
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1968
build_tree(source.basis_tree(), target)
1969
self.assertEqual([], target.conflicts())
1970
self.assertPathExists('target2/dir1/file2')
1971
self.assertPathExists('target2/dir1/file')
1973
# Ensure new contents are suppressed for existing branches
1974
target = self.make_branch_and_tree('target3')
1975
self.make_branch('target3/dir1')
1976
self.build_tree(['target3/dir1/file2'])
1977
build_tree(source.basis_tree(), target)
1978
self.assertPathDoesNotExist('target3/dir1/file')
1979
self.assertPathExists('target3/dir1/file2')
1980
self.assertPathExists('target3/dir1.diverted/file')
1981
self.assertEqual([DuplicateEntry('Diverted to',
1982
'dir1.diverted', 'dir1', 'new-dir1', None)],
1985
target = self.make_branch_and_tree('target4')
1986
self.build_tree(['target4/dir1/'])
1987
self.make_branch('target4/dir1/file')
1988
build_tree(source.basis_tree(), target)
1989
self.assertPathExists('target4/dir1/file')
1990
self.assertEqual('directory', file_kind('target4/dir1/file'))
1991
self.assertPathExists('target4/dir1/file.diverted')
1992
self.assertEqual([DuplicateEntry('Diverted to',
1993
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1996
def test_mixed_conflict_handling(self):
1997
"""Ensure that when building trees, conflict handling is done"""
1998
source = self.make_branch_and_tree('source')
1999
target = self.make_branch_and_tree('target')
2000
self.build_tree(['source/name', 'target/name/'])
2001
source.add('name', 'new-name')
2002
source.commit('added file')
2003
build_tree(source.basis_tree(), target)
2004
self.assertEqual([DuplicateEntry('Moved existing file to',
2005
'name.moved', 'name', None, 'new-name')], target.conflicts())
2007
def test_raises_in_populated(self):
2008
source = self.make_branch_and_tree('source')
2009
self.build_tree(['source/name'])
2011
source.commit('added name')
2012
target = self.make_branch_and_tree('target')
2013
self.build_tree(['target/name'])
2015
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2016
build_tree, source.basis_tree(), target)
2018
def test_build_tree_rename_count(self):
2019
source = self.make_branch_and_tree('source')
2020
self.build_tree(['source/file1', 'source/dir1/'])
2021
source.add(['file1', 'dir1'])
2022
source.commit('add1')
2023
target1 = self.make_branch_and_tree('target1')
2024
transform_result = build_tree(source.basis_tree(), target1)
2025
self.assertEqual(2, transform_result.rename_count)
2027
self.build_tree(['source/dir1/file2'])
2028
source.add(['dir1/file2'])
2029
source.commit('add3')
2030
target2 = self.make_branch_and_tree('target2')
2031
transform_result = build_tree(source.basis_tree(), target2)
2032
# children of non-root directories should not be renamed
2033
self.assertEqual(2, transform_result.rename_count)
2035
def create_ab_tree(self):
2036
"""Create a committed test tree with two files"""
2037
source = self.make_branch_and_tree('source')
2038
self.build_tree_contents([('source/file1', b'A')])
2039
self.build_tree_contents([('source/file2', b'B')])
2040
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2041
source.commit('commit files')
2043
self.addCleanup(source.unlock)
2046
def test_build_tree_accelerator_tree(self):
2047
source = self.create_ab_tree()
2048
self.build_tree_contents([('source/file2', b'C')])
2050
real_source_get_file = source.get_file
2051
def get_file(path, file_id=None):
2052
calls.append(file_id)
2053
return real_source_get_file(path, file_id)
2054
source.get_file = get_file
2055
target = self.make_branch_and_tree('target')
2056
revision_tree = source.basis_tree()
2057
revision_tree.lock_read()
2058
self.addCleanup(revision_tree.unlock)
2059
build_tree(revision_tree, target, source)
2060
self.assertEqual(['file1-id'], calls)
2062
self.addCleanup(target.unlock)
2063
self.assertEqual([], list(target.iter_changes(revision_tree)))
2065
def test_build_tree_accelerator_tree_observes_sha1(self):
2066
source = self.create_ab_tree()
2067
sha1 = osutils.sha_string('A')
2068
target = self.make_branch_and_tree('target')
2070
self.addCleanup(target.unlock)
2071
state = target.current_dirstate()
2072
state._cutoff_time = time.time() + 60
2073
build_tree(source.basis_tree(), target, source)
2074
entry = state._get_entry(0, path_utf8='file1')
2075
self.assertEqual(sha1, entry[1][0][1])
2077
def test_build_tree_accelerator_tree_missing_file(self):
2078
source = self.create_ab_tree()
2079
os.unlink('source/file1')
2080
source.remove(['file2'])
2081
target = self.make_branch_and_tree('target')
2082
revision_tree = source.basis_tree()
2083
revision_tree.lock_read()
2084
self.addCleanup(revision_tree.unlock)
2085
build_tree(revision_tree, target, source)
2087
self.addCleanup(target.unlock)
2088
self.assertEqual([], list(target.iter_changes(revision_tree)))
2090
def test_build_tree_accelerator_wrong_kind(self):
2091
self.requireFeature(SymlinkFeature)
2092
source = self.make_branch_and_tree('source')
2093
self.build_tree_contents([('source/file1', b'')])
2094
self.build_tree_contents([('source/file2', b'')])
2095
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2096
source.commit('commit files')
2097
os.unlink('source/file2')
2098
self.build_tree_contents([('source/file2/', b'C')])
2099
os.unlink('source/file1')
2100
os.symlink('file2', 'source/file1')
2102
real_source_get_file = source.get_file
2103
def get_file(path, file_id=None):
2104
calls.append(file_id)
2105
return real_source_get_file(path, file_id)
2106
source.get_file = get_file
2107
target = self.make_branch_and_tree('target')
2108
revision_tree = source.basis_tree()
2109
revision_tree.lock_read()
2110
self.addCleanup(revision_tree.unlock)
2111
build_tree(revision_tree, target, source)
2112
self.assertEqual([], calls)
2114
self.addCleanup(target.unlock)
2115
self.assertEqual([], list(target.iter_changes(revision_tree)))
2117
def test_build_tree_hardlink(self):
2118
self.requireFeature(HardlinkFeature)
2119
source = self.create_ab_tree()
2120
target = self.make_branch_and_tree('target')
2121
revision_tree = source.basis_tree()
2122
revision_tree.lock_read()
2123
self.addCleanup(revision_tree.unlock)
2124
build_tree(revision_tree, target, source, hardlink=True)
2126
self.addCleanup(target.unlock)
2127
self.assertEqual([], list(target.iter_changes(revision_tree)))
2128
source_stat = os.stat('source/file1')
2129
target_stat = os.stat('target/file1')
2130
self.assertEqual(source_stat, target_stat)
2132
# Explicitly disallowing hardlinks should prevent them.
2133
target2 = self.make_branch_and_tree('target2')
2134
build_tree(revision_tree, target2, source, hardlink=False)
2136
self.addCleanup(target2.unlock)
2137
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2138
source_stat = os.stat('source/file1')
2139
target2_stat = os.stat('target2/file1')
2140
self.assertNotEqual(source_stat, target2_stat)
2142
def test_build_tree_accelerator_tree_moved(self):
2143
source = self.make_branch_and_tree('source')
2144
self.build_tree_contents([('source/file1', b'A')])
2145
source.add(['file1'], [b'file1-id'])
2146
source.commit('commit files')
2147
source.rename_one('file1', 'file2')
2149
self.addCleanup(source.unlock)
2150
target = self.make_branch_and_tree('target')
2151
revision_tree = source.basis_tree()
2152
revision_tree.lock_read()
2153
self.addCleanup(revision_tree.unlock)
2154
build_tree(revision_tree, target, source)
2156
self.addCleanup(target.unlock)
2157
self.assertEqual([], list(target.iter_changes(revision_tree)))
2159
def test_build_tree_hardlinks_preserve_execute(self):
2160
self.requireFeature(HardlinkFeature)
2161
source = self.create_ab_tree()
2162
tt = TreeTransform(source)
2163
trans_id = tt.trans_id_tree_path('file1')
2164
tt.set_executability(True, trans_id)
2166
self.assertTrue(source.is_executable('file1'))
2167
target = self.make_branch_and_tree('target')
2168
revision_tree = source.basis_tree()
2169
revision_tree.lock_read()
2170
self.addCleanup(revision_tree.unlock)
2171
build_tree(revision_tree, target, source, hardlink=True)
2173
self.addCleanup(target.unlock)
2174
self.assertEqual([], list(target.iter_changes(revision_tree)))
2175
self.assertTrue(source.is_executable('file1'))
2177
def install_rot13_content_filter(self, pattern):
2179
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2180
# below, but that looks a bit... hard to read even if it's exactly
2182
original_registry = filters._reset_registry()
2183
def restore_registry():
2184
filters._reset_registry(original_registry)
2185
self.addCleanup(restore_registry)
2186
def rot13(chunks, context=None):
2187
return [''.join(chunks).encode('rot13')]
2188
rot13filter = filters.ContentFilter(rot13, rot13)
2189
filters.filter_stacks_registry.register(
2190
'rot13', {'yes': [rot13filter]}.get)
2191
os.mkdir(self.test_home_dir + '/.bazaar')
2192
rules_filename = self.test_home_dir + '/.bazaar/rules'
2193
f = open(rules_filename, 'wb')
2194
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2196
def uninstall_rules():
2197
os.remove(rules_filename)
2199
self.addCleanup(uninstall_rules)
2202
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2203
"""build_tree will not hardlink files that have content filtering rules
2204
applied to them (but will still hardlink other files from the same tree
2207
self.requireFeature(HardlinkFeature)
2208
self.install_rot13_content_filter('file1')
2209
source = self.create_ab_tree()
2210
target = self.make_branch_and_tree('target')
2211
revision_tree = source.basis_tree()
2212
revision_tree.lock_read()
2213
self.addCleanup(revision_tree.unlock)
2214
build_tree(revision_tree, target, source, hardlink=True)
2216
self.addCleanup(target.unlock)
2217
self.assertEqual([], list(target.iter_changes(revision_tree)))
2218
source_stat = os.stat('source/file1')
2219
target_stat = os.stat('target/file1')
2220
self.assertNotEqual(source_stat, target_stat)
2221
source_stat = os.stat('source/file2')
2222
target_stat = os.stat('target/file2')
2223
self.assertEqualStat(source_stat, target_stat)
2225
def test_case_insensitive_build_tree_inventory(self):
2226
if (features.CaseInsensitiveFilesystemFeature.available()
2227
or features.CaseInsCasePresFilenameFeature.available()):
2228
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2229
source = self.make_branch_and_tree('source')
2230
self.build_tree(['source/file', 'source/FILE'])
2231
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2232
source.commit('added files')
2233
# Don't try this at home, kids!
2234
# Force the tree to report that it is case insensitive
2235
target = self.make_branch_and_tree('target')
2236
target.case_sensitive = False
2237
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2238
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2239
self.assertEqual('FILE', target.id2path(b'upper-id'))
2241
def test_build_tree_observes_sha(self):
2242
source = self.make_branch_and_tree('source')
2243
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2244
source.add(['file1', 'dir', 'dir/file2'],
2245
[b'file1-id', b'dir-id', b'file2-id'])
2246
source.commit('new files')
2247
target = self.make_branch_and_tree('target')
2249
self.addCleanup(target.unlock)
2250
# We make use of the fact that DirState caches its cutoff time. So we
2251
# set the 'safe' time to one minute in the future.
2252
state = target.current_dirstate()
2253
state._cutoff_time = time.time() + 60
2254
build_tree(source.basis_tree(), target)
2255
entry1_sha = osutils.sha_file_by_name('source/file1')
2256
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2257
# entry[1] is the state information, entry[1][0] is the state of the
2258
# working tree, entry[1][0][1] is the sha value for the current working
2260
entry1 = state._get_entry(0, path_utf8='file1')
2261
self.assertEqual(entry1_sha, entry1[1][0][1])
2262
# The 'size' field must also be set.
2263
self.assertEqual(25, entry1[1][0][2])
2264
entry1_state = entry1[1][0]
2265
entry2 = state._get_entry(0, path_utf8='dir/file2')
2266
self.assertEqual(entry2_sha, entry2[1][0][1])
2267
self.assertEqual(29, entry2[1][0][2])
2268
entry2_state = entry2[1][0]
2269
# Now, make sure that we don't have to re-read the content. The
2270
# packed_stat should match exactly.
2271
self.assertEqual(entry1_sha, target.get_file_sha1('file1', b'file1-id'))
2272
self.assertEqual(entry2_sha,
2273
target.get_file_sha1('dir/file2', b'file2-id'))
2274
self.assertEqual(entry1_state, entry1[1][0])
2275
self.assertEqual(entry2_state, entry2[1][0])
2278
class TestCommitTransform(tests.TestCaseWithTransport):
2280
def get_branch(self):
2281
tree = self.make_branch_and_tree('tree')
2283
self.addCleanup(tree.unlock)
2284
tree.commit('empty commit')
2287
def get_branch_and_transform(self):
2288
branch = self.get_branch()
2289
tt = TransformPreview(branch.basis_tree())
2290
self.addCleanup(tt.finalize)
2293
def test_commit_wrong_basis(self):
2294
branch = self.get_branch()
2295
basis = branch.repository.revision_tree(
2296
_mod_revision.NULL_REVISION)
2297
tt = TransformPreview(basis)
2298
self.addCleanup(tt.finalize)
2299
e = self.assertRaises(ValueError, tt.commit, branch, '')
2300
self.assertEqual('TreeTransform not based on branch basis: null:',
2303
def test_empy_commit(self):
2304
branch, tt = self.get_branch_and_transform()
2305
rev = tt.commit(branch, 'my message')
2306
self.assertEqual(2, branch.revno())
2307
repo = branch.repository
2308
self.assertEqual('my message', repo.get_revision(rev).message)
2310
def test_merge_parents(self):
2311
branch, tt = self.get_branch_and_transform()
2312
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2313
self.assertEqual(['rev1b', 'rev1c'],
2314
branch.basis_tree().get_parent_ids()[1:])
2316
def test_first_commit(self):
2317
branch = self.make_branch('branch')
2319
self.addCleanup(branch.unlock)
2320
tt = TransformPreview(branch.basis_tree())
2321
self.addCleanup(tt.finalize)
2322
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2323
rev = tt.commit(branch, 'my message')
2324
self.assertEqual([], branch.basis_tree().get_parent_ids())
2325
self.assertNotEqual(_mod_revision.NULL_REVISION,
2326
branch.last_revision())
2328
def test_first_commit_with_merge_parents(self):
2329
branch = self.make_branch('branch')
2331
self.addCleanup(branch.unlock)
2332
tt = TransformPreview(branch.basis_tree())
2333
self.addCleanup(tt.finalize)
2334
e = self.assertRaises(ValueError, tt.commit, branch,
2335
'my message', ['rev1b-id'])
2336
self.assertEqual('Cannot supply merge parents for first commit.',
2338
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2340
def test_add_files(self):
2341
branch, tt = self.get_branch_and_transform()
2342
tt.new_file('file', tt.root, 'contents', b'file-id')
2343
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2344
if SymlinkFeature.available():
2345
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2346
rev = tt.commit(branch, 'message')
2347
tree = branch.basis_tree()
2348
self.assertEqual('file', tree.id2path(b'file-id'))
2349
self.assertEqual('contents', tree.get_file_text('file', b'file-id'))
2350
self.assertEqual('dir', tree.id2path(b'dir-id'))
2351
if SymlinkFeature.available():
2352
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2353
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2355
def test_add_unversioned(self):
2356
branch, tt = self.get_branch_and_transform()
2357
tt.new_file('file', tt.root, 'contents')
2358
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2359
'message', strict=True)
2361
def test_modify_strict(self):
2362
branch, tt = self.get_branch_and_transform()
2363
tt.new_file('file', tt.root, 'contents', b'file-id')
2364
tt.commit(branch, 'message', strict=True)
2365
tt = TransformPreview(branch.basis_tree())
2366
self.addCleanup(tt.finalize)
2367
trans_id = tt.trans_id_file_id('file-id')
2368
tt.delete_contents(trans_id)
2369
tt.create_file('contents', trans_id)
2370
tt.commit(branch, 'message', strict=True)
2372
def test_commit_malformed(self):
2373
"""Committing a malformed transform should raise an exception.
2375
In this case, we are adding a file without adding its parent.
2377
branch, tt = self.get_branch_and_transform()
2378
parent_id = tt.trans_id_file_id('parent-id')
2379
tt.new_file('file', parent_id, 'contents', b'file-id')
2380
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2383
def test_commit_rich_revision_data(self):
2384
branch, tt = self.get_branch_and_transform()
2385
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2386
committer='me <me@example.com>',
2387
revprops={'foo': 'bar'}, revision_id='revid-1',
2388
authors=['Author1 <author1@example.com>',
2389
'Author2 <author2@example.com>',
2391
self.assertEqual('revid-1', rev_id)
2392
revision = branch.repository.get_revision(rev_id)
2393
self.assertEqual(1, revision.timestamp)
2394
self.assertEqual(43201, revision.timezone)
2395
self.assertEqual('me <me@example.com>', revision.committer)
2396
self.assertEqual(['Author1 <author1@example.com>',
2397
'Author2 <author2@example.com>'],
2398
revision.get_apparent_authors())
2399
del revision.properties['authors']
2400
self.assertEqual({'foo': 'bar',
2401
'branch-nick': 'tree'},
2402
revision.properties)
2404
def test_no_explicit_revprops(self):
2405
branch, tt = self.get_branch_and_transform()
2406
rev_id = tt.commit(branch, 'message', authors=[
2407
'Author1 <author1@example.com>',
2408
'Author2 <author2@example.com>', ])
2409
revision = branch.repository.get_revision(rev_id)
2410
self.assertEqual(['Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>'],
2412
revision.get_apparent_authors())
2413
self.assertEqual('tree', revision.properties['branch-nick'])
2416
class TestFileMover(tests.TestCaseWithTransport):
2418
def test_file_mover(self):
2419
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2420
mover = _FileMover()
2421
mover.rename('a', 'q')
2422
self.assertPathExists('q')
2423
self.assertPathDoesNotExist('a')
2424
self.assertPathExists('q/b')
2425
self.assertPathExists('c')
2426
self.assertPathExists('c/d')
2428
def test_pre_delete_rollback(self):
2429
self.build_tree(['a/'])
2430
mover = _FileMover()
2431
mover.pre_delete('a', 'q')
2432
self.assertPathExists('q')
2433
self.assertPathDoesNotExist('a')
2435
self.assertPathDoesNotExist('q')
2436
self.assertPathExists('a')
2438
def test_apply_deletions(self):
2439
self.build_tree(['a/', 'b/'])
2440
mover = _FileMover()
2441
mover.pre_delete('a', 'q')
2442
mover.pre_delete('b', 'r')
2443
self.assertPathExists('q')
2444
self.assertPathExists('r')
2445
self.assertPathDoesNotExist('a')
2446
self.assertPathDoesNotExist('b')
2447
mover.apply_deletions()
2448
self.assertPathDoesNotExist('q')
2449
self.assertPathDoesNotExist('r')
2450
self.assertPathDoesNotExist('a')
2451
self.assertPathDoesNotExist('b')
2453
def test_file_mover_rollback(self):
2454
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2455
mover = _FileMover()
2456
mover.rename('c/d', 'c/f')
2457
mover.rename('c/e', 'c/d')
2459
mover.rename('a', 'c')
2460
except errors.FileExists as e:
2462
self.assertPathExists('a')
2463
self.assertPathExists('c/d')
2466
class Bogus(Exception):
2470
class TestTransformRollback(tests.TestCaseWithTransport):
2472
class ExceptionFileMover(_FileMover):
2474
def __init__(self, bad_source=None, bad_target=None):
2475
_FileMover.__init__(self)
2476
self.bad_source = bad_source
2477
self.bad_target = bad_target
2479
def rename(self, source, target):
2480
if (self.bad_source is not None and
2481
source.endswith(self.bad_source)):
2483
elif (self.bad_target is not None and
2484
target.endswith(self.bad_target)):
2487
_FileMover.rename(self, source, target)
2489
def test_rollback_rename(self):
2490
tree = self.make_branch_and_tree('.')
2491
self.build_tree(['a/', 'a/b'])
2492
tt = TreeTransform(tree)
2493
self.addCleanup(tt.finalize)
2494
a_id = tt.trans_id_tree_path('a')
2495
tt.adjust_path('c', tt.root, a_id)
2496
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2497
self.assertRaises(Bogus, tt.apply,
2498
_mover=self.ExceptionFileMover(bad_source='a'))
2499
self.assertPathExists('a')
2500
self.assertPathExists('a/b')
2502
self.assertPathExists('c')
2503
self.assertPathExists('c/d')
2505
def test_rollback_rename_into_place(self):
2506
tree = self.make_branch_and_tree('.')
2507
self.build_tree(['a/', 'a/b'])
2508
tt = TreeTransform(tree)
2509
self.addCleanup(tt.finalize)
2510
a_id = tt.trans_id_tree_path('a')
2511
tt.adjust_path('c', tt.root, a_id)
2512
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2513
self.assertRaises(Bogus, tt.apply,
2514
_mover=self.ExceptionFileMover(bad_target='c/d'))
2515
self.assertPathExists('a')
2516
self.assertPathExists('a/b')
2518
self.assertPathExists('c')
2519
self.assertPathExists('c/d')
2521
def test_rollback_deletion(self):
2522
tree = self.make_branch_and_tree('.')
2523
self.build_tree(['a/', 'a/b'])
2524
tt = TreeTransform(tree)
2525
self.addCleanup(tt.finalize)
2526
a_id = tt.trans_id_tree_path('a')
2527
tt.delete_contents(a_id)
2528
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2529
self.assertRaises(Bogus, tt.apply,
2530
_mover=self.ExceptionFileMover(bad_target='d'))
2531
self.assertPathExists('a')
2532
self.assertPathExists('a/b')
2535
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2536
"""Ensure treetransform creation errors can be safely cleaned up after"""
2538
def _override_globals_in_method(self, instance, method_name, globals):
2539
"""Replace method on instance with one with updated globals"""
2541
func = getattr(instance, method_name).__func__
2542
new_globals = dict(func.__globals__)
2543
new_globals.update(globals)
2544
new_func = types.FunctionType(func.__code__, new_globals,
2545
func.__name__, func.__defaults__)
2546
setattr(instance, method_name,
2547
types.MethodType(new_func, instance, instance.__class__))
2548
self.addCleanup(delattr, instance, method_name)
2551
def _fake_open_raises_before(name, mode):
2552
"""Like open() but raises before doing anything"""
2556
def _fake_open_raises_after(name, mode):
2557
"""Like open() but raises after creating file without returning"""
2558
open(name, mode).close()
2561
def create_transform_and_root_trans_id(self):
2562
"""Setup a transform creating a file in limbo"""
2563
tree = self.make_branch_and_tree('.')
2564
tt = TreeTransform(tree)
2565
return tt, tt.create_path("a", tt.root)
2567
def create_transform_and_subdir_trans_id(self):
2568
"""Setup a transform creating a directory containing a file in limbo"""
2569
tree = self.make_branch_and_tree('.')
2570
tt = TreeTransform(tree)
2571
d_trans_id = tt.create_path("d", tt.root)
2572
tt.create_directory(d_trans_id)
2573
f_trans_id = tt.create_path("a", d_trans_id)
2574
tt.adjust_path("a", d_trans_id, f_trans_id)
2575
return tt, f_trans_id
2577
def test_root_create_file_open_raises_before_creation(self):
2578
tt, trans_id = self.create_transform_and_root_trans_id()
2579
self._override_globals_in_method(tt, "create_file",
2580
{"open": self._fake_open_raises_before})
2581
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2582
path = tt._limbo_name(trans_id)
2583
self.assertPathDoesNotExist(path)
2585
self.assertPathDoesNotExist(tt._limbodir)
2587
def test_root_create_file_open_raises_after_creation(self):
2588
tt, trans_id = self.create_transform_and_root_trans_id()
2589
self._override_globals_in_method(tt, "create_file",
2590
{"open": self._fake_open_raises_after})
2591
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2592
path = tt._limbo_name(trans_id)
2593
self.assertPathExists(path)
2595
self.assertPathDoesNotExist(path)
2596
self.assertPathDoesNotExist(tt._limbodir)
2598
def test_subdir_create_file_open_raises_before_creation(self):
2599
tt, trans_id = self.create_transform_and_subdir_trans_id()
2600
self._override_globals_in_method(tt, "create_file",
2601
{"open": self._fake_open_raises_before})
2602
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2603
path = tt._limbo_name(trans_id)
2604
self.assertPathDoesNotExist(path)
2606
self.assertPathDoesNotExist(tt._limbodir)
2608
def test_subdir_create_file_open_raises_after_creation(self):
2609
tt, trans_id = self.create_transform_and_subdir_trans_id()
2610
self._override_globals_in_method(tt, "create_file",
2611
{"open": self._fake_open_raises_after})
2612
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2613
path = tt._limbo_name(trans_id)
2614
self.assertPathExists(path)
2616
self.assertPathDoesNotExist(path)
2617
self.assertPathDoesNotExist(tt._limbodir)
2619
def test_rename_in_limbo_rename_raises_after_rename(self):
2620
tt, trans_id = self.create_transform_and_root_trans_id()
2621
parent1 = tt.new_directory('parent1', tt.root)
2622
child1 = tt.new_file('child1', parent1, 'contents')
2623
parent2 = tt.new_directory('parent2', tt.root)
2625
class FakeOSModule(object):
2626
def rename(self, old, new):
2629
self._override_globals_in_method(tt, "_rename_in_limbo",
2630
{"os": FakeOSModule()})
2632
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2633
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2634
self.assertPathExists(path)
2636
self.assertPathDoesNotExist(path)
2637
self.assertPathDoesNotExist(tt._limbodir)
2639
def test_rename_in_limbo_rename_raises_before_rename(self):
2640
tt, trans_id = self.create_transform_and_root_trans_id()
2641
parent1 = tt.new_directory('parent1', tt.root)
2642
child1 = tt.new_file('child1', parent1, 'contents')
2643
parent2 = tt.new_directory('parent2', tt.root)
2645
class FakeOSModule(object):
2646
def rename(self, old, new):
2648
self._override_globals_in_method(tt, "_rename_in_limbo",
2649
{"os": FakeOSModule()})
2651
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2652
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2653
self.assertPathExists(path)
2655
self.assertPathDoesNotExist(path)
2656
self.assertPathDoesNotExist(tt._limbodir)
2659
class TestTransformMissingParent(tests.TestCaseWithTransport):
2661
def make_tt_with_versioned_dir(self):
2662
wt = self.make_branch_and_tree('.')
2663
self.build_tree(['dir/',])
2664
wt.add(['dir'], [b'dir-id'])
2665
wt.commit('Create dir')
2666
tt = TreeTransform(wt)
2667
self.addCleanup(tt.finalize)
2670
def test_resolve_create_parent_for_versioned_file(self):
2671
wt, tt = self.make_tt_with_versioned_dir()
2672
dir_tid = tt.trans_id_tree_path('dir')
2673
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id=b'file-id')
2674
tt.delete_contents(dir_tid)
2675
tt.unversion_file(dir_tid)
2676
conflicts = resolve_conflicts(tt)
2677
# one conflict for the missing directory, one for the unversioned
2679
self.assertLength(2, conflicts)
2681
def test_non_versioned_file_create_conflict(self):
2682
wt, tt = self.make_tt_with_versioned_dir()
2683
dir_tid = tt.trans_id_tree_path('dir')
2684
tt.new_file('file', dir_tid, 'Contents')
2685
tt.delete_contents(dir_tid)
2686
tt.unversion_file(dir_tid)
2687
conflicts = resolve_conflicts(tt)
2688
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2689
self.assertLength(1, conflicts)
2690
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2694
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2695
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2697
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2698
('', ''), ('directory', 'directory'), (False, False))
2701
class TestTransformPreview(tests.TestCaseWithTransport):
2703
def create_tree(self):
2704
tree = self.make_branch_and_tree('.')
2705
self.build_tree_contents([('a', b'content 1')])
2706
tree.set_root_id(b'TREE_ROOT')
2707
tree.add('a', b'a-id')
2708
tree.commit('rev1', rev_id=b'rev1')
2709
return tree.branch.repository.revision_tree('rev1')
2711
def get_empty_preview(self):
2712
repository = self.make_repository('repo')
2713
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2714
preview = TransformPreview(tree)
2715
self.addCleanup(preview.finalize)
2718
def test_transform_preview(self):
2719
revision_tree = self.create_tree()
2720
preview = TransformPreview(revision_tree)
2721
self.addCleanup(preview.finalize)
2723
def test_transform_preview_tree(self):
2724
revision_tree = self.create_tree()
2725
preview = TransformPreview(revision_tree)
2726
self.addCleanup(preview.finalize)
2727
preview.get_preview_tree()
2729
def test_transform_new_file(self):
2730
revision_tree = self.create_tree()
2731
preview = TransformPreview(revision_tree)
2732
self.addCleanup(preview.finalize)
2733
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2734
preview_tree = preview.get_preview_tree()
2735
self.assertEqual(preview_tree.kind('file2'), 'file')
2737
preview_tree.get_file('file2', 'file2-id').read(), 'content B\n')
2739
def test_diff_preview_tree(self):
2740
revision_tree = self.create_tree()
2741
preview = TransformPreview(revision_tree)
2742
self.addCleanup(preview.finalize)
2743
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2744
preview_tree = preview.get_preview_tree()
2746
show_diff_trees(revision_tree, preview_tree, out)
2747
lines = out.getvalue().splitlines()
2748
self.assertEqual(lines[0], "=== added file 'file2'")
2749
# 3 lines of diff administrivia
2750
self.assertEqual(lines[4], "+content B")
2752
def test_transform_conflicts(self):
2753
revision_tree = self.create_tree()
2754
preview = TransformPreview(revision_tree)
2755
self.addCleanup(preview.finalize)
2756
preview.new_file('a', preview.root, 'content 2')
2757
resolve_conflicts(preview)
2758
trans_id = preview.trans_id_file_id('a-id')
2759
self.assertEqual('a.moved', preview.final_name(trans_id))
2761
def get_tree_and_preview_tree(self):
2762
revision_tree = self.create_tree()
2763
preview = TransformPreview(revision_tree)
2764
self.addCleanup(preview.finalize)
2765
a_trans_id = preview.trans_id_file_id('a-id')
2766
preview.delete_contents(a_trans_id)
2767
preview.create_file('b content', a_trans_id)
2768
preview_tree = preview.get_preview_tree()
2769
return revision_tree, preview_tree
2771
def test_iter_changes(self):
2772
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2773
root = revision_tree.get_root_id()
2774
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2775
(root, root), ('a', 'a'), ('file', 'file'),
2777
list(preview_tree.iter_changes(revision_tree)))
2779
def test_include_unchanged_succeeds(self):
2780
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2781
changes = preview_tree.iter_changes(revision_tree,
2782
include_unchanged=True)
2783
root = revision_tree.get_root_id()
2785
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2787
def test_specific_files(self):
2788
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2789
changes = preview_tree.iter_changes(revision_tree,
2790
specific_files=[''])
2791
self.assertEqual([A_ENTRY], list(changes))
2793
def test_want_unversioned(self):
2794
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2795
changes = preview_tree.iter_changes(revision_tree,
2796
want_unversioned=True)
2797
self.assertEqual([A_ENTRY], list(changes))
2799
def test_ignore_extra_trees_no_specific_files(self):
2800
# extra_trees is harmless without specific_files, so we'll silently
2801
# accept it, even though we won't use it.
2802
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2803
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2805
def test_ignore_require_versioned_no_specific_files(self):
2806
# require_versioned is meaningless without specific_files.
2807
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2808
preview_tree.iter_changes(revision_tree, require_versioned=False)
2810
def test_ignore_pb(self):
2811
# pb could be supported, but TT.iter_changes doesn't support it.
2812
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2813
preview_tree.iter_changes(revision_tree)
2815
def test_kind(self):
2816
revision_tree = self.create_tree()
2817
preview = TransformPreview(revision_tree)
2818
self.addCleanup(preview.finalize)
2819
preview.new_file('file', preview.root, 'contents', b'file-id')
2820
preview.new_directory('directory', preview.root, 'dir-id')
2821
preview_tree = preview.get_preview_tree()
2822
self.assertEqual('file', preview_tree.kind('file'))
2823
self.assertEqual('directory', preview_tree.kind('directory'))
2825
def test_get_file_mtime(self):
2826
preview = self.get_empty_preview()
2827
file_trans_id = preview.new_file('file', preview.root, 'contents',
2829
limbo_path = preview._limbo_name(file_trans_id)
2830
preview_tree = preview.get_preview_tree()
2831
self.assertEqual(os.stat(limbo_path).st_mtime,
2832
preview_tree.get_file_mtime('file', b'file-id'))
2834
def test_get_file_mtime_renamed(self):
2835
work_tree = self.make_branch_and_tree('tree')
2836
self.build_tree(['tree/file'])
2837
work_tree.add('file', b'file-id')
2838
preview = TransformPreview(work_tree)
2839
self.addCleanup(preview.finalize)
2840
file_trans_id = preview.trans_id_tree_path('file')
2841
preview.adjust_path('renamed', preview.root, file_trans_id)
2842
preview_tree = preview.get_preview_tree()
2843
preview_mtime = preview_tree.get_file_mtime('renamed', b'file-id')
2844
work_mtime = work_tree.get_file_mtime('file', b'file-id')
2846
def test_get_file_size(self):
2847
work_tree = self.make_branch_and_tree('tree')
2848
self.build_tree_contents([('tree/old', b'old')])
2849
work_tree.add('old', b'old-id')
2850
preview = TransformPreview(work_tree)
2851
self.addCleanup(preview.finalize)
2852
new_id = preview.new_file('name', preview.root, 'contents', b'new-id',
2854
tree = preview.get_preview_tree()
2855
self.assertEqual(len('old'), tree.get_file_size('old'))
2856
self.assertEqual(len('contents'), tree.get_file_size('name'))
2858
def test_get_file(self):
2859
preview = self.get_empty_preview()
2860
preview.new_file('file', preview.root, 'contents', b'file-id')
2861
preview_tree = preview.get_preview_tree()
2862
tree_file = preview_tree.get_file('file')
2864
self.assertEqual('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', '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
'contents', '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, '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, 'contents', '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, 'contents', '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, '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, 'path-id')
3058
preview.set_tree_reference('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('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, '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, '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, '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\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', 'a\n'),
3160
('killed-b', 'b\n'),
3161
('unchanged', 'c\n'),
3162
('unchanged', '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\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', 'a\n'),
3183
('killed-b', 'b\n'),
3184
('unchanged', 'c\n'),
3185
('unchanged', '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, '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, '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, 'contents')
3210
preview.new_file('new-versioned-file', preview.root, '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('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, '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('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, 'contents')
3284
modified_id = tt.trans_id_tree_path('modified')
3285
tt.delete_contents(modified_id)
3286
tt.create_file('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, 'bar', 'baz-id')
3300
preview_tree = preview.get_preview_tree()
3301
self.assertEqual(False, preview_tree.is_executable('tree/foo', '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, 'contents', '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('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, 'contents')
3329
limbo_path = tt._limbo_name(bar_id)
3330
self.assertEqual(limbo_path.encode('ascii', 'replace'), 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
'_new_executability': {},
3366
'_tree_path_ids': {'': 'new-0'},
3368
'_removed_contents': [],
3369
'_non_present_ids': {},
3372
def make_records(self, attribs, contents):
3374
(((('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['_id_number'] = 3
3381
attribs['_new_name'] = {
3382
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3383
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3384
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3385
attribs['_new_executability'] = {'new-1': 1}
3387
('new-1', 'file', 'i 1\nbar\n'),
3388
('new-2', 'directory', ''),
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, 'bar', 'baz', True)
3395
tt.new_directory('qux', tt.root, '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': 'baz', 'new-2': 'quxx'}, tt._new_id)
3405
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3406
self.assertEqual({'baz': 'new-1', '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('bar', foo_content)
3417
def symlink_creation_records(self):
3418
attribs = self.default_attribs()
3419
attribs['_id_number'] = 2
3420
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3421
attribs['_new_parent'] = {'new-1': 'new-0'}
3422
contents = [('new-1', '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['_id_number'] = 3
3448
attribs['_removed_id'] = ['new-1']
3449
attribs['_removed_contents'] = ['new-2']
3450
attribs['_tree_path_ids'] = {
3452
u'foo\u1234'.encode('utf-8'): '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['_id_number'] = 2
3480
attribs['_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('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({'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['_id_number'] = 2
3505
attribs['_tree_path_ids'] = {
3508
attribs['_removed_contents'] = ['new-1']
3509
contents = [('new-1', 'file',
3510
'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('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(LINES, tt._limbo_name('new-1'))
3525
def make_kind_change_preview(self):
3526
LINES = '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['_id_number'] = 2
3535
attribs['_tree_path_ids'] = {
3538
attribs['_removed_contents'] = ['new-1']
3539
contents = [('new-1', 'file',
3540
'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(LINES, tt._limbo_name('new-1'))
3555
def make_add_contents_preview(self):
3556
LINES = '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['_id_number'] = 2
3566
attribs['_tree_path_ids'] = {
3569
contents = [('new-1', 'file',
3570
'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((['aa\n', 'bb\n', 'cc\n', '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())