1
# Copyright (C) 2006-2011 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
19
from StringIO import StringIO
29
revision as _mod_revision,
37
from bzrlib.bzrdir import BzrDir
38
from bzrlib.conflicts import (
47
from bzrlib.diff import show_diff_trees
48
from bzrlib.errors import (
51
ExistingPendingDeletion,
53
ImmortalPendingDeletion,
58
from bzrlib.osutils import (
62
from bzrlib.merge import Merge3Merger, Merger
63
from bzrlib.mutabletree import MutableTree
64
from bzrlib.tests import (
69
from bzrlib.tests.features import (
73
from bzrlib.transform import (
87
class TestTreeTransform(tests.TestCaseWithTransport):
90
super(TestTreeTransform, self).setUp()
91
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
94
def get_transform(self):
95
transform = TreeTransform(self.wt)
96
self.addCleanup(transform.finalize)
97
return transform, transform.root
99
def get_transform_for_sha1_test(self):
100
trans, root = self.get_transform()
101
self.wt.lock_tree_write()
102
self.addCleanup(self.wt.unlock)
103
contents = ['just some content\n']
104
sha1 = osutils.sha_strings(contents)
105
# Roll back the clock
106
trans._creation_mtime = time.time() - 20.0
107
return trans, root, contents, sha1
109
def test_existing_limbo(self):
110
transform, root = self.get_transform()
111
limbo_name = transform._limbodir
112
deletion_path = transform._deletiondir
113
os.mkdir(pathjoin(limbo_name, 'hehe'))
114
self.assertRaises(ImmortalLimbo, transform.apply)
115
self.assertRaises(LockError, self.wt.unlock)
116
self.assertRaises(ExistingLimbo, self.get_transform)
117
self.assertRaises(LockError, self.wt.unlock)
118
os.rmdir(pathjoin(limbo_name, 'hehe'))
120
os.rmdir(deletion_path)
121
transform, root = self.get_transform()
124
def test_existing_pending_deletion(self):
125
transform, root = self.get_transform()
126
deletion_path = self._limbodir = urlutils.local_path_from_url(
127
transform._tree._transport.abspath('pending-deletion'))
128
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
129
self.assertRaises(ImmortalPendingDeletion, transform.apply)
130
self.assertRaises(LockError, self.wt.unlock)
131
self.assertRaises(ExistingPendingDeletion, self.get_transform)
133
def test_build(self):
134
transform, root = self.get_transform()
135
self.wt.lock_tree_write()
136
self.addCleanup(self.wt.unlock)
137
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
138
imaginary_id = transform.trans_id_tree_path('imaginary')
139
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
140
self.assertEqual(imaginary_id, imaginary_id2)
141
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
142
self.assertEqual('directory', transform.final_kind(root))
143
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
144
trans_id = transform.create_path('name', root)
145
self.assertIs(transform.final_file_id(trans_id), None)
146
self.assertIs(None, transform.final_kind(trans_id))
147
transform.create_file('contents', trans_id)
148
transform.set_executability(True, trans_id)
149
transform.version_file('my_pretties', trans_id)
150
self.assertRaises(DuplicateKey, transform.version_file,
151
'my_pretties', trans_id)
152
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
153
self.assertEqual(transform.final_parent(trans_id), root)
154
self.assertIs(transform.final_parent(root), ROOT_PARENT)
155
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
156
oz_id = transform.create_path('oz', root)
157
transform.create_directory(oz_id)
158
transform.version_file('ozzie', oz_id)
159
trans_id2 = transform.create_path('name2', root)
160
transform.create_file('contents', trans_id2)
161
transform.set_executability(False, trans_id2)
162
transform.version_file('my_pretties2', trans_id2)
163
modified_paths = transform.apply().modified_paths
164
self.assertEqual('contents', self.wt.get_file_byname('name').read())
165
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
166
self.assertIs(self.wt.is_executable('my_pretties'), True)
167
self.assertIs(self.wt.is_executable('my_pretties2'), False)
168
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
169
self.assertEqual(len(modified_paths), 3)
170
tree_mod_paths = [self.wt.id2abspath(f) for f in
171
('ozzie', 'my_pretties', 'my_pretties2')]
172
self.assertSubset(tree_mod_paths, modified_paths)
173
# is it safe to finalize repeatedly?
177
def test_apply_informs_tree_of_observed_sha1(self):
178
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
179
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
182
orig = self.wt._observed_sha1
183
def _observed_sha1(*args):
186
self.wt._observed_sha1 = _observed_sha1
188
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
191
def test_create_file_caches_sha1(self):
192
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
193
trans_id = trans.create_path('file1', root)
194
trans.create_file(contents, trans_id, sha1=sha1)
195
st_val = osutils.lstat(trans._limbo_name(trans_id))
196
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
197
self.assertEqual(o_sha1, sha1)
198
self.assertEqualStat(o_st_val, st_val)
200
def test__apply_insertions_updates_sha1(self):
201
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
202
trans_id = trans.create_path('file1', root)
203
trans.create_file(contents, trans_id, sha1=sha1)
204
st_val = osutils.lstat(trans._limbo_name(trans_id))
205
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
206
self.assertEqual(o_sha1, sha1)
207
self.assertEqualStat(o_st_val, st_val)
208
creation_mtime = trans._creation_mtime + 10.0
209
# We fake a time difference from when the file was created until now it
210
# is being renamed by using os.utime. Note that the change we actually
211
# want to see is the real ctime change from 'os.rename()', but as long
212
# as we observe a new stat value, we should be fine.
213
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
215
new_st_val = osutils.lstat(self.wt.abspath('file1'))
216
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
217
self.assertEqual(o_sha1, sha1)
218
self.assertEqualStat(o_st_val, new_st_val)
219
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
221
def test_new_file_caches_sha1(self):
222
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
223
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
225
st_val = osutils.lstat(trans._limbo_name(trans_id))
226
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
227
self.assertEqual(o_sha1, sha1)
228
self.assertEqualStat(o_st_val, st_val)
230
def test_cancel_creation_removes_observed_sha1(self):
231
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
232
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
234
self.assertTrue(trans_id in trans._observed_sha1s)
235
trans.cancel_creation(trans_id)
236
self.assertFalse(trans_id in trans._observed_sha1s)
238
def test_create_files_same_timestamp(self):
239
transform, root = self.get_transform()
240
self.wt.lock_tree_write()
241
self.addCleanup(self.wt.unlock)
242
# Roll back the clock, so that we know everything is being set to the
244
transform._creation_mtime = creation_mtime = time.time() - 20.0
245
transform.create_file('content-one',
246
transform.create_path('one', root))
247
time.sleep(1) # *ugly*
248
transform.create_file('content-two',
249
transform.create_path('two', root))
251
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
253
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
255
# We only guarantee 2s resolution
256
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
257
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
258
# But if we have more than that, all files should get the same result
259
self.assertEqual(st1.st_mtime, st2.st_mtime)
261
def test_change_root_id(self):
262
transform, root = self.get_transform()
263
self.assertNotEqual('new-root-id', self.wt.get_root_id())
264
transform.new_directory('', ROOT_PARENT, 'new-root-id')
265
transform.delete_contents(root)
266
transform.unversion_file(root)
267
transform.fixup_new_roots()
269
self.assertEqual('new-root-id', self.wt.get_root_id())
271
def test_change_root_id_add_files(self):
272
transform, root = self.get_transform()
273
self.assertNotEqual('new-root-id', self.wt.get_root_id())
274
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
275
transform.new_file('file', new_trans_id, ['new-contents\n'],
277
transform.delete_contents(root)
278
transform.unversion_file(root)
279
transform.fixup_new_roots()
281
self.assertEqual('new-root-id', self.wt.get_root_id())
282
self.assertEqual('new-file-id', self.wt.path2id('file'))
283
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
285
def test_add_two_roots(self):
286
transform, root = self.get_transform()
287
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
288
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
289
self.assertRaises(ValueError, transform.fixup_new_roots)
291
def test_retain_existing_root(self):
292
tt, root = self.get_transform()
294
tt.new_directory('', ROOT_PARENT, 'new-root-id')
296
self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
298
def test_retain_existing_root_added_file(self):
299
tt, root = self.get_transform()
300
new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
301
child = tt.new_directory('child', new_trans_id, 'child-id')
303
self.assertEqual(tt.root, tt.final_parent(child))
305
def test_add_unversioned_root(self):
306
transform, root = self.get_transform()
307
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
308
transform.delete_contents(transform.root)
309
transform.fixup_new_roots()
310
self.assertNotIn(transform.root, transform._new_id)
312
def test_remove_root_fixup(self):
313
transform, root = self.get_transform()
314
old_root_id = self.wt.get_root_id()
315
self.assertNotEqual('new-root-id', old_root_id)
316
transform.delete_contents(root)
317
transform.unversion_file(root)
318
transform.fixup_new_roots()
320
self.assertEqual(old_root_id, self.wt.get_root_id())
322
transform, root = self.get_transform()
323
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
324
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
325
self.assertRaises(ValueError, transform.fixup_new_roots)
327
def test_fixup_new_roots_permits_empty_tree(self):
328
transform, root = self.get_transform()
329
transform.delete_contents(root)
330
transform.unversion_file(root)
331
transform.fixup_new_roots()
332
self.assertIs(None, transform.final_kind(root))
333
self.assertIs(None, transform.final_file_id(root))
335
def test_apply_retains_root_directory(self):
336
# Do not attempt to delete the physical root directory, because that
338
transform, root = self.get_transform()
340
transform.delete_contents(root)
341
e = self.assertRaises(AssertionError, self.assertRaises,
342
errors.TransformRenameFailed,
344
self.assertContainsRe('TransformRenameFailed not raised', str(e))
346
def test_apply_retains_file_id(self):
347
transform, root = self.get_transform()
348
old_root_id = transform.tree_file_id(root)
349
transform.unversion_file(root)
351
self.assertEqual(old_root_id, self.wt.get_root_id())
353
def test_hardlink(self):
354
self.requireFeature(HardlinkFeature)
355
transform, root = self.get_transform()
356
transform.new_file('file1', root, 'contents')
358
target = self.make_branch_and_tree('target')
359
target_transform = TreeTransform(target)
360
trans_id = target_transform.create_path('file1', target_transform.root)
361
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
362
target_transform.apply()
363
self.assertPathExists('target/file1')
364
source_stat = os.stat(self.wt.abspath('file1'))
365
target_stat = os.stat('target/file1')
366
self.assertEqual(source_stat, target_stat)
368
def test_convenience(self):
369
transform, root = self.get_transform()
370
self.wt.lock_tree_write()
371
self.addCleanup(self.wt.unlock)
372
trans_id = transform.new_file('name', root, 'contents',
374
oz = transform.new_directory('oz', root, 'oz-id')
375
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
376
toto = transform.new_file('toto', dorothy, 'toto-contents',
379
self.assertEqual(len(transform.find_conflicts()), 0)
381
self.assertRaises(ReusingTransform, transform.find_conflicts)
382
self.assertEqual('contents', file(self.wt.abspath('name')).read())
383
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
384
self.assertIs(self.wt.is_executable('my_pretties'), True)
385
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
386
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
387
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
389
self.assertEqual('toto-contents',
390
self.wt.get_file_byname('oz/dorothy/toto').read())
391
self.assertIs(self.wt.is_executable('toto-id'), False)
393
def test_tree_reference(self):
394
transform, root = self.get_transform()
395
tree = transform._tree
396
trans_id = transform.new_directory('reference', root, 'subtree-id')
397
transform.set_tree_reference('subtree-revision', trans_id)
400
self.addCleanup(tree.unlock)
401
self.assertEqual('subtree-revision',
402
tree.inventory['subtree-id'].reference_revision)
404
def test_conflicts(self):
405
transform, root = self.get_transform()
406
trans_id = transform.new_file('name', root, 'contents',
408
self.assertEqual(len(transform.find_conflicts()), 0)
409
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
410
self.assertEqual(transform.find_conflicts(),
411
[('duplicate', trans_id, trans_id2, 'name')])
412
self.assertRaises(MalformedTransform, transform.apply)
413
transform.adjust_path('name', trans_id, trans_id2)
414
self.assertEqual(transform.find_conflicts(),
415
[('non-directory parent', trans_id)])
416
tinman_id = transform.trans_id_tree_path('tinman')
417
transform.adjust_path('name', tinman_id, trans_id2)
418
self.assertEqual(transform.find_conflicts(),
419
[('unversioned parent', tinman_id),
420
('missing parent', tinman_id)])
421
lion_id = transform.create_path('lion', root)
422
self.assertEqual(transform.find_conflicts(),
423
[('unversioned parent', tinman_id),
424
('missing parent', tinman_id)])
425
transform.adjust_path('name', lion_id, trans_id2)
426
self.assertEqual(transform.find_conflicts(),
427
[('unversioned parent', lion_id),
428
('missing parent', lion_id)])
429
transform.version_file("Courage", lion_id)
430
self.assertEqual(transform.find_conflicts(),
431
[('missing parent', lion_id),
432
('versioning no contents', lion_id)])
433
transform.adjust_path('name2', root, trans_id2)
434
self.assertEqual(transform.find_conflicts(),
435
[('versioning no contents', lion_id)])
436
transform.create_file('Contents, okay?', lion_id)
437
transform.adjust_path('name2', trans_id2, trans_id2)
438
self.assertEqual(transform.find_conflicts(),
439
[('parent loop', trans_id2),
440
('non-directory parent', trans_id2)])
441
transform.adjust_path('name2', root, trans_id2)
442
oz_id = transform.new_directory('oz', root)
443
transform.set_executability(True, oz_id)
444
self.assertEqual(transform.find_conflicts(),
445
[('unversioned executability', oz_id)])
446
transform.version_file('oz-id', oz_id)
447
self.assertEqual(transform.find_conflicts(),
448
[('non-file executability', oz_id)])
449
transform.set_executability(None, oz_id)
450
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
452
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
453
self.assertEqual('contents', file(self.wt.abspath('name')).read())
454
transform2, root = self.get_transform()
455
oz_id = transform2.trans_id_tree_file_id('oz-id')
456
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
457
result = transform2.find_conflicts()
458
fp = FinalPaths(transform2)
459
self.assert_('oz/tip' in transform2._tree_path_ids)
460
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
461
self.assertEqual(len(result), 2)
462
self.assertEqual((result[0][0], result[0][1]),
463
('duplicate', newtip))
464
self.assertEqual((result[1][0], result[1][2]),
465
('duplicate id', newtip))
466
transform2.finalize()
467
transform3 = TreeTransform(self.wt)
468
self.addCleanup(transform3.finalize)
469
oz_id = transform3.trans_id_tree_file_id('oz-id')
470
transform3.delete_contents(oz_id)
471
self.assertEqual(transform3.find_conflicts(),
472
[('missing parent', oz_id)])
473
root_id = transform3.root
474
tip_id = transform3.trans_id_tree_file_id('tip-id')
475
transform3.adjust_path('tip', root_id, tip_id)
478
def test_conflict_on_case_insensitive(self):
479
tree = self.make_branch_and_tree('tree')
480
# Don't try this at home, kids!
481
# Force the tree to report that it is case sensitive, for conflict
483
tree.case_sensitive = True
484
transform = TreeTransform(tree)
485
self.addCleanup(transform.finalize)
486
transform.new_file('file', transform.root, 'content')
487
transform.new_file('FiLe', transform.root, 'content')
488
result = transform.find_conflicts()
489
self.assertEqual([], result)
491
# Force the tree to report that it is case insensitive, for conflict
493
tree.case_sensitive = False
494
transform = TreeTransform(tree)
495
self.addCleanup(transform.finalize)
496
transform.new_file('file', transform.root, 'content')
497
transform.new_file('FiLe', transform.root, 'content')
498
result = transform.find_conflicts()
499
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
501
def test_conflict_on_case_insensitive_existing(self):
502
tree = self.make_branch_and_tree('tree')
503
self.build_tree(['tree/FiLe'])
504
# Don't try this at home, kids!
505
# Force the tree to report that it is case sensitive, for conflict
507
tree.case_sensitive = True
508
transform = TreeTransform(tree)
509
self.addCleanup(transform.finalize)
510
transform.new_file('file', transform.root, 'content')
511
result = transform.find_conflicts()
512
self.assertEqual([], result)
514
# Force the tree to report that it is case insensitive, for conflict
516
tree.case_sensitive = False
517
transform = TreeTransform(tree)
518
self.addCleanup(transform.finalize)
519
transform.new_file('file', transform.root, 'content')
520
result = transform.find_conflicts()
521
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
523
def test_resolve_case_insensitive_conflict(self):
524
tree = self.make_branch_and_tree('tree')
525
# Don't try this at home, kids!
526
# Force the tree to report that it is case insensitive, for conflict
528
tree.case_sensitive = False
529
transform = TreeTransform(tree)
530
self.addCleanup(transform.finalize)
531
transform.new_file('file', transform.root, 'content')
532
transform.new_file('FiLe', transform.root, 'content')
533
resolve_conflicts(transform)
535
self.assertPathExists('tree/file')
536
self.assertPathExists('tree/FiLe.moved')
538
def test_resolve_checkout_case_conflict(self):
539
tree = self.make_branch_and_tree('tree')
540
# Don't try this at home, kids!
541
# Force the tree to report that it is case insensitive, for conflict
543
tree.case_sensitive = False
544
transform = TreeTransform(tree)
545
self.addCleanup(transform.finalize)
546
transform.new_file('file', transform.root, 'content')
547
transform.new_file('FiLe', transform.root, 'content')
548
resolve_conflicts(transform,
549
pass_func=lambda t, c: resolve_checkout(t, c, []))
551
self.assertPathExists('tree/file')
552
self.assertPathExists('tree/FiLe.moved')
554
def test_apply_case_conflict(self):
555
"""Ensure that a transform with case conflicts can always be applied"""
556
tree = self.make_branch_and_tree('tree')
557
transform = TreeTransform(tree)
558
self.addCleanup(transform.finalize)
559
transform.new_file('file', transform.root, 'content')
560
transform.new_file('FiLe', transform.root, 'content')
561
dir = transform.new_directory('dir', transform.root)
562
transform.new_file('dirfile', dir, 'content')
563
transform.new_file('dirFiLe', dir, 'content')
564
resolve_conflicts(transform)
566
self.assertPathExists('tree/file')
567
if not os.path.exists('tree/FiLe.moved'):
568
self.assertPathExists('tree/FiLe')
569
self.assertPathExists('tree/dir/dirfile')
570
if not os.path.exists('tree/dir/dirFiLe.moved'):
571
self.assertPathExists('tree/dir/dirFiLe')
573
def test_case_insensitive_limbo(self):
574
tree = self.make_branch_and_tree('tree')
575
# Don't try this at home, kids!
576
# Force the tree to report that it is case insensitive
577
tree.case_sensitive = False
578
transform = TreeTransform(tree)
579
self.addCleanup(transform.finalize)
580
dir = transform.new_directory('dir', transform.root)
581
first = transform.new_file('file', dir, 'content')
582
second = transform.new_file('FiLe', dir, 'content')
583
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
584
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
586
def test_adjust_path_updates_child_limbo_names(self):
587
tree = self.make_branch_and_tree('tree')
588
transform = TreeTransform(tree)
589
self.addCleanup(transform.finalize)
590
foo_id = transform.new_directory('foo', transform.root)
591
bar_id = transform.new_directory('bar', foo_id)
592
baz_id = transform.new_directory('baz', bar_id)
593
qux_id = transform.new_directory('qux', baz_id)
594
transform.adjust_path('quxx', foo_id, bar_id)
595
self.assertStartsWith(transform._limbo_name(qux_id),
596
transform._limbo_name(bar_id))
598
def test_add_del(self):
599
start, root = self.get_transform()
600
start.new_directory('a', root, 'a')
602
transform, root = self.get_transform()
603
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
604
transform.new_directory('a', root, 'a')
607
def test_unversioning(self):
608
create_tree, root = self.get_transform()
609
parent_id = create_tree.new_directory('parent', root, 'parent-id')
610
create_tree.new_file('child', parent_id, 'child', 'child-id')
612
unversion = TreeTransform(self.wt)
613
self.addCleanup(unversion.finalize)
614
parent = unversion.trans_id_tree_path('parent')
615
unversion.unversion_file(parent)
616
self.assertEqual(unversion.find_conflicts(),
617
[('unversioned parent', parent_id)])
618
file_id = unversion.trans_id_tree_file_id('child-id')
619
unversion.unversion_file(file_id)
622
def test_name_invariants(self):
623
create_tree, root = self.get_transform()
625
root = create_tree.root
626
create_tree.new_file('name1', root, 'hello1', 'name1')
627
create_tree.new_file('name2', root, 'hello2', 'name2')
628
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
629
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
630
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
631
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
634
mangle_tree,root = self.get_transform()
635
root = mangle_tree.root
637
name1 = mangle_tree.trans_id_tree_file_id('name1')
638
name2 = mangle_tree.trans_id_tree_file_id('name2')
639
mangle_tree.adjust_path('name2', root, name1)
640
mangle_tree.adjust_path('name1', root, name2)
642
#tests for deleting parent directories
643
ddir = mangle_tree.trans_id_tree_file_id('ddir')
644
mangle_tree.delete_contents(ddir)
645
dfile = mangle_tree.trans_id_tree_file_id('dfile')
646
mangle_tree.delete_versioned(dfile)
647
mangle_tree.unversion_file(dfile)
648
mfile = mangle_tree.trans_id_tree_file_id('mfile')
649
mangle_tree.adjust_path('mfile', root, mfile)
651
#tests for adding parent directories
652
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
653
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
654
mangle_tree.adjust_path('mfile2', newdir, mfile2)
655
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
656
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
657
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
658
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
660
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
661
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
662
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
663
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
664
self.assertEqual(file(mfile2_path).read(), 'later2')
665
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
666
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
667
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
668
self.assertEqual(file(newfile_path).read(), 'hello3')
669
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
670
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
671
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
673
def test_both_rename(self):
674
create_tree,root = self.get_transform()
675
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
676
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
678
mangle_tree,root = self.get_transform()
679
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
680
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
681
mangle_tree.adjust_path('test', root, selftest)
682
mangle_tree.adjust_path('test_too_much', root, selftest)
683
mangle_tree.set_executability(True, blackbox)
686
def test_both_rename2(self):
687
create_tree,root = self.get_transform()
688
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
689
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
690
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
691
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
694
mangle_tree,root = self.get_transform()
695
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
696
tests = mangle_tree.trans_id_tree_file_id('tests-id')
697
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
698
mangle_tree.adjust_path('selftest', bzrlib, tests)
699
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
700
mangle_tree.set_executability(True, test_too_much)
703
def test_both_rename3(self):
704
create_tree,root = self.get_transform()
705
tests = create_tree.new_directory('tests', root, 'tests-id')
706
create_tree.new_file('test_too_much.py', tests, 'hello1',
709
mangle_tree,root = self.get_transform()
710
tests = mangle_tree.trans_id_tree_file_id('tests-id')
711
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
712
mangle_tree.adjust_path('selftest', root, tests)
713
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
714
mangle_tree.set_executability(True, test_too_much)
717
def test_move_dangling_ie(self):
718
create_tree, root = self.get_transform()
720
root = create_tree.root
721
create_tree.new_file('name1', root, 'hello1', 'name1')
723
delete_contents, root = self.get_transform()
724
file = delete_contents.trans_id_tree_file_id('name1')
725
delete_contents.delete_contents(file)
726
delete_contents.apply()
727
move_id, root = self.get_transform()
728
name1 = move_id.trans_id_tree_file_id('name1')
729
newdir = move_id.new_directory('dir', root, 'newdir')
730
move_id.adjust_path('name2', newdir, name1)
733
def test_replace_dangling_ie(self):
734
create_tree, root = self.get_transform()
736
root = create_tree.root
737
create_tree.new_file('name1', root, 'hello1', 'name1')
739
delete_contents = TreeTransform(self.wt)
740
self.addCleanup(delete_contents.finalize)
741
file = delete_contents.trans_id_tree_file_id('name1')
742
delete_contents.delete_contents(file)
743
delete_contents.apply()
744
delete_contents.finalize()
745
replace = TreeTransform(self.wt)
746
self.addCleanup(replace.finalize)
747
name2 = replace.new_file('name2', root, 'hello2', 'name1')
748
conflicts = replace.find_conflicts()
749
name1 = replace.trans_id_tree_file_id('name1')
750
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
751
resolve_conflicts(replace)
754
def _test_symlinks(self, link_name1,link_target1,
755
link_name2, link_target2):
757
def ozpath(p): return 'oz/' + p
759
self.requireFeature(SymlinkFeature)
760
transform, root = self.get_transform()
761
oz_id = transform.new_directory('oz', root, 'oz-id')
762
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
764
wiz_id = transform.create_path(link_name2, oz_id)
765
transform.create_symlink(link_target2, wiz_id)
766
transform.version_file('wiz-id2', wiz_id)
767
transform.set_executability(True, wiz_id)
768
self.assertEqual(transform.find_conflicts(),
769
[('non-file executability', wiz_id)])
770
transform.set_executability(None, wiz_id)
772
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
773
self.assertEqual('symlink',
774
file_kind(self.wt.abspath(ozpath(link_name1))))
775
self.assertEqual(link_target2,
776
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
777
self.assertEqual(link_target1,
778
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
780
def test_symlinks(self):
781
self._test_symlinks('wizard', 'wizard-target',
782
'wizard2', 'behind_curtain')
784
def test_symlinks_unicode(self):
785
self.requireFeature(features.UnicodeFilenameFeature)
786
self._test_symlinks(u'\N{Euro Sign}wizard',
787
u'wizard-targ\N{Euro Sign}t',
788
u'\N{Euro Sign}wizard2',
789
u'b\N{Euro Sign}hind_curtain')
791
def test_unable_create_symlink(self):
793
wt = self.make_branch_and_tree('.')
794
tt = TreeTransform(wt) # TreeTransform obtains write lock
796
tt.new_symlink('foo', tt.root, 'bar')
800
os_symlink = getattr(os, 'symlink', None)
803
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
805
"Unable to create symlink 'foo' on this platform",
809
os.symlink = os_symlink
811
def get_conflicted(self):
812
create,root = self.get_transform()
813
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
814
oz = create.new_directory('oz', root, 'oz-id')
815
create.new_directory('emeraldcity', oz, 'emerald-id')
817
conflicts,root = self.get_transform()
818
# set up duplicate entry, duplicate id
819
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
821
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
822
oz = conflicts.trans_id_tree_file_id('oz-id')
823
# set up DeletedParent parent conflict
824
conflicts.delete_versioned(oz)
825
emerald = conflicts.trans_id_tree_file_id('emerald-id')
826
# set up MissingParent conflict
827
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
828
conflicts.adjust_path('munchkincity', root, munchkincity)
829
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
831
conflicts.adjust_path('emeraldcity', emerald, emerald)
832
return conflicts, emerald, oz, old_dorothy, new_dorothy
834
def test_conflict_resolution(self):
835
conflicts, emerald, oz, old_dorothy, new_dorothy =\
836
self.get_conflicted()
837
resolve_conflicts(conflicts)
838
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
839
self.assertIs(conflicts.final_file_id(old_dorothy), None)
840
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
841
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
842
self.assertEqual(conflicts.final_parent(emerald), oz)
845
def test_cook_conflicts(self):
846
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
847
raw_conflicts = resolve_conflicts(tt)
848
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
849
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
850
'dorothy', None, 'dorothy-id')
851
self.assertEqual(cooked_conflicts[0], duplicate)
852
duplicate_id = DuplicateID('Unversioned existing file',
853
'dorothy.moved', 'dorothy', None,
855
self.assertEqual(cooked_conflicts[1], duplicate_id)
856
missing_parent = MissingParent('Created directory', 'munchkincity',
858
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
859
self.assertEqual(cooked_conflicts[2], missing_parent)
860
unversioned_parent = UnversionedParent('Versioned directory',
863
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
865
self.assertEqual(cooked_conflicts[3], unversioned_parent)
866
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
867
'oz/emeraldcity', 'emerald-id', 'emerald-id')
868
self.assertEqual(cooked_conflicts[4], deleted_parent)
869
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
870
self.assertEqual(cooked_conflicts[6], parent_loop)
871
self.assertEqual(len(cooked_conflicts), 7)
874
def test_string_conflicts(self):
875
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
876
raw_conflicts = resolve_conflicts(tt)
877
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
879
conflicts_s = [unicode(c) for c in cooked_conflicts]
880
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
881
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
882
'Moved existing file to '
884
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
885
'Unversioned existing file '
887
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
888
' munchkincity. Created directory.')
889
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
890
' versioned, but has versioned'
891
' children. Versioned directory.')
892
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
893
" is not empty. Not deleting.")
894
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
895
' versioned, but has versioned'
896
' children. Versioned directory.')
897
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
898
' oz/emeraldcity. Cancelled move.')
900
def prepare_wrong_parent_kind(self):
901
tt, root = self.get_transform()
902
tt.new_file('parent', root, 'contents', 'parent-id')
904
tt, root = self.get_transform()
905
parent_id = tt.trans_id_file_id('parent-id')
906
tt.new_file('child,', parent_id, 'contents2', 'file-id')
909
def test_find_conflicts_wrong_parent_kind(self):
910
tt = self.prepare_wrong_parent_kind()
913
def test_resolve_conflicts_wrong_existing_parent_kind(self):
914
tt = self.prepare_wrong_parent_kind()
915
raw_conflicts = resolve_conflicts(tt)
916
self.assertEqual(set([('non-directory parent', 'Created directory',
917
'new-3')]), raw_conflicts)
918
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
919
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
920
'parent-id')], cooked_conflicts)
922
self.assertEqual(None, self.wt.path2id('parent'))
923
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
925
def test_resolve_conflicts_wrong_new_parent_kind(self):
926
tt, root = self.get_transform()
927
parent_id = tt.new_directory('parent', root, 'parent-id')
928
tt.new_file('child,', parent_id, 'contents2', 'file-id')
930
tt, root = self.get_transform()
931
parent_id = tt.trans_id_file_id('parent-id')
932
tt.delete_contents(parent_id)
933
tt.create_file('contents', parent_id)
934
raw_conflicts = resolve_conflicts(tt)
935
self.assertEqual(set([('non-directory parent', 'Created directory',
936
'new-3')]), raw_conflicts)
938
self.assertEqual(None, self.wt.path2id('parent'))
939
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
941
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
942
tt, root = self.get_transform()
943
parent_id = tt.new_directory('parent', root)
944
tt.new_file('child,', parent_id, 'contents2')
946
tt, root = self.get_transform()
947
parent_id = tt.trans_id_tree_path('parent')
948
tt.delete_contents(parent_id)
949
tt.create_file('contents', parent_id)
950
resolve_conflicts(tt)
952
self.assertIs(None, self.wt.path2id('parent'))
953
self.assertIs(None, self.wt.path2id('parent.new'))
955
def test_resolve_conflicts_missing_parent(self):
956
wt = self.make_branch_and_tree('.')
957
tt = TreeTransform(wt)
958
self.addCleanup(tt.finalize)
959
parent = tt.trans_id_file_id('parent-id')
960
tt.new_file('file', parent, 'Contents')
961
raw_conflicts = resolve_conflicts(tt)
962
# Since the directory doesn't exist it's seen as 'missing'. So
963
# 'resolve_conflicts' create a conflict asking for it to be created.
964
self.assertLength(1, raw_conflicts)
965
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
967
# apply fail since the missing directory doesn't exist
968
self.assertRaises(errors.NoFinalPath, tt.apply)
970
def test_moving_versioned_directories(self):
971
create, root = self.get_transform()
972
kansas = create.new_directory('kansas', root, 'kansas-id')
973
create.new_directory('house', kansas, 'house-id')
974
create.new_directory('oz', root, 'oz-id')
976
cyclone, root = self.get_transform()
977
oz = cyclone.trans_id_tree_file_id('oz-id')
978
house = cyclone.trans_id_tree_file_id('house-id')
979
cyclone.adjust_path('house', oz, house)
982
def test_moving_root(self):
983
create, root = self.get_transform()
984
fun = create.new_directory('fun', root, 'fun-id')
985
create.new_directory('sun', root, 'sun-id')
986
create.new_directory('moon', root, 'moon')
988
transform, root = self.get_transform()
989
transform.adjust_root_path('oldroot', fun)
990
new_root = transform.trans_id_tree_path('')
991
transform.version_file('new-root', new_root)
994
def test_renames(self):
995
create, root = self.get_transform()
996
old = create.new_directory('old-parent', root, 'old-id')
997
intermediate = create.new_directory('intermediate', old, 'im-id')
998
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1001
rename, root = self.get_transform()
1002
old = rename.trans_id_file_id('old-id')
1003
rename.adjust_path('new', root, old)
1004
myfile = rename.trans_id_file_id('myfile-id')
1005
rename.set_executability(True, myfile)
1008
def test_rename_fails(self):
1009
self.requireFeature(features.not_running_as_root)
1010
# see https://bugs.launchpad.net/bzr/+bug/491763
1011
create, root_id = self.get_transform()
1012
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1013
myfile = create.new_file('myfile', root_id, 'myfile-text',
1016
if os.name == "posix" and sys.platform != "cygwin":
1017
# posix filesystems fail on renaming if the readonly bit is set
1018
osutils.make_readonly(self.wt.abspath('first-dir'))
1019
elif os.name == "nt":
1020
# windows filesystems fail on renaming open files
1021
self.addCleanup(file(self.wt.abspath('myfile')).close)
1023
self.skip("Don't know how to force a permissions error on rename")
1024
# now transform to rename
1025
rename_transform, root_id = self.get_transform()
1026
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1027
dir_id = rename_transform.trans_id_file_id('first-id')
1028
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1029
e = self.assertRaises(errors.TransformRenameFailed,
1030
rename_transform.apply)
1031
# On nix looks like:
1032
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1033
# to .../first-dir/newname: [Errno 13] Permission denied"
1034
# On windows looks like:
1035
# "Failed to rename .../work/myfile to
1036
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1037
# This test isn't concerned with exactly what the error looks like,
1038
# and the strerror will vary across OS and locales, but the assert
1039
# that the exeception attributes are what we expect
1040
self.assertEqual(e.errno, errno.EACCES)
1041
if os.name == "posix":
1042
self.assertEndsWith(e.to_path, "/first-dir/newname")
1044
self.assertEqual(os.path.basename(e.from_path), "myfile")
1046
def test_set_executability_order(self):
1047
"""Ensure that executability behaves the same, no matter what order.
1049
- create file and set executability simultaneously
1050
- create file and set executability afterward
1051
- unsetting the executability of a file whose executability has not been
1052
declared should throw an exception (this may happen when a
1053
merge attempts to create a file with a duplicate ID)
1055
transform, root = self.get_transform()
1056
wt = transform._tree
1058
self.addCleanup(wt.unlock)
1059
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1061
sac = transform.new_file('set_after_creation', root,
1062
'Set after creation', 'sac')
1063
transform.set_executability(True, sac)
1064
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1066
self.assertRaises(KeyError, transform.set_executability, None, uws)
1068
self.assertTrue(wt.is_executable('soc'))
1069
self.assertTrue(wt.is_executable('sac'))
1071
def test_preserve_mode(self):
1072
"""File mode is preserved when replacing content"""
1073
if sys.platform == 'win32':
1074
raise TestSkipped('chmod has no effect on win32')
1075
transform, root = self.get_transform()
1076
transform.new_file('file1', root, 'contents', 'file1-id', True)
1078
self.wt.lock_write()
1079
self.addCleanup(self.wt.unlock)
1080
self.assertTrue(self.wt.is_executable('file1-id'))
1081
transform, root = self.get_transform()
1082
file1_id = transform.trans_id_tree_file_id('file1-id')
1083
transform.delete_contents(file1_id)
1084
transform.create_file('contents2', file1_id)
1086
self.assertTrue(self.wt.is_executable('file1-id'))
1088
def test__set_mode_stats_correctly(self):
1089
"""_set_mode stats to determine file mode."""
1090
if sys.platform == 'win32':
1091
raise TestSkipped('chmod has no effect on win32')
1095
def instrumented_stat(path):
1096
stat_paths.append(path)
1097
return real_stat(path)
1099
transform, root = self.get_transform()
1101
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1102
file_id='bar-id-1', executable=False)
1105
transform, root = self.get_transform()
1106
bar1_id = transform.trans_id_tree_path('bar')
1107
bar2_id = transform.trans_id_tree_path('bar2')
1109
os.stat = instrumented_stat
1110
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1113
transform.finalize()
1115
bar1_abspath = self.wt.abspath('bar')
1116
self.assertEqual([bar1_abspath], stat_paths)
1118
def test_iter_changes(self):
1119
self.wt.set_root_id('eert_toor')
1120
transform, root = self.get_transform()
1121
transform.new_file('old', root, 'blah', 'id-1', True)
1123
transform, root = self.get_transform()
1125
self.assertEqual([], list(transform.iter_changes()))
1126
old = transform.trans_id_tree_file_id('id-1')
1127
transform.unversion_file(old)
1128
self.assertEqual([('id-1', ('old', None), False, (True, False),
1129
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1130
(True, True))], list(transform.iter_changes()))
1131
transform.new_directory('new', root, 'id-1')
1132
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1133
('eert_toor', 'eert_toor'), ('old', 'new'),
1134
('file', 'directory'),
1135
(True, False))], list(transform.iter_changes()))
1137
transform.finalize()
1139
def test_iter_changes_new(self):
1140
self.wt.set_root_id('eert_toor')
1141
transform, root = self.get_transform()
1142
transform.new_file('old', root, 'blah')
1144
transform, root = self.get_transform()
1146
old = transform.trans_id_tree_path('old')
1147
transform.version_file('id-1', old)
1148
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1149
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1150
(False, False))], list(transform.iter_changes()))
1152
transform.finalize()
1154
def test_iter_changes_modifications(self):
1155
self.wt.set_root_id('eert_toor')
1156
transform, root = self.get_transform()
1157
transform.new_file('old', root, 'blah', 'id-1')
1158
transform.new_file('new', root, 'blah')
1159
transform.new_directory('subdir', root, 'subdir-id')
1161
transform, root = self.get_transform()
1163
old = transform.trans_id_tree_path('old')
1164
subdir = transform.trans_id_tree_file_id('subdir-id')
1165
new = transform.trans_id_tree_path('new')
1166
self.assertEqual([], list(transform.iter_changes()))
1169
transform.delete_contents(old)
1170
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1171
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1172
(False, False))], list(transform.iter_changes()))
1175
transform.create_file('blah', old)
1176
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1177
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1178
(False, False))], list(transform.iter_changes()))
1179
transform.cancel_deletion(old)
1180
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1181
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1182
(False, False))], list(transform.iter_changes()))
1183
transform.cancel_creation(old)
1185
# move file_id to a different file
1186
self.assertEqual([], list(transform.iter_changes()))
1187
transform.unversion_file(old)
1188
transform.version_file('id-1', new)
1189
transform.adjust_path('old', root, new)
1190
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1191
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1192
(False, False))], list(transform.iter_changes()))
1193
transform.cancel_versioning(new)
1194
transform._removed_id = set()
1197
self.assertEqual([], list(transform.iter_changes()))
1198
transform.set_executability(True, old)
1199
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1200
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1201
(False, True))], list(transform.iter_changes()))
1202
transform.set_executability(None, old)
1205
self.assertEqual([], list(transform.iter_changes()))
1206
transform.adjust_path('new', root, old)
1207
transform._new_parent = {}
1208
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1209
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1210
(False, False))], list(transform.iter_changes()))
1211
transform._new_name = {}
1214
self.assertEqual([], list(transform.iter_changes()))
1215
transform.adjust_path('new', subdir, old)
1216
transform._new_name = {}
1217
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1218
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1219
('file', 'file'), (False, False))],
1220
list(transform.iter_changes()))
1221
transform._new_path = {}
1224
transform.finalize()
1226
def test_iter_changes_modified_bleed(self):
1227
self.wt.set_root_id('eert_toor')
1228
"""Modified flag should not bleed from one change to another"""
1229
# unfortunately, we have no guarantee that file1 (which is modified)
1230
# will be applied before file2. And if it's applied after file2, it
1231
# obviously can't bleed into file2's change output. But for now, it
1233
transform, root = self.get_transform()
1234
transform.new_file('file1', root, 'blah', 'id-1')
1235
transform.new_file('file2', root, 'blah', 'id-2')
1237
transform, root = self.get_transform()
1239
transform.delete_contents(transform.trans_id_file_id('id-1'))
1240
transform.set_executability(True,
1241
transform.trans_id_file_id('id-2'))
1242
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1243
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1244
('file', None), (False, False)),
1245
('id-2', (u'file2', u'file2'), False, (True, True),
1246
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1247
('file', 'file'), (False, True))],
1248
list(transform.iter_changes()))
1250
transform.finalize()
1252
def test_iter_changes_move_missing(self):
1253
"""Test moving ids with no files around"""
1254
self.wt.set_root_id('toor_eert')
1255
# Need two steps because versioning a non-existant file is a conflict.
1256
transform, root = self.get_transform()
1257
transform.new_directory('floater', root, 'floater-id')
1259
transform, root = self.get_transform()
1260
transform.delete_contents(transform.trans_id_tree_path('floater'))
1262
transform, root = self.get_transform()
1263
floater = transform.trans_id_tree_path('floater')
1265
transform.adjust_path('flitter', root, floater)
1266
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1267
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1268
(None, None), (False, False))], list(transform.iter_changes()))
1270
transform.finalize()
1272
def test_iter_changes_pointless(self):
1273
"""Ensure that no-ops are not treated as modifications"""
1274
self.wt.set_root_id('eert_toor')
1275
transform, root = self.get_transform()
1276
transform.new_file('old', root, 'blah', 'id-1')
1277
transform.new_directory('subdir', root, 'subdir-id')
1279
transform, root = self.get_transform()
1281
old = transform.trans_id_tree_path('old')
1282
subdir = transform.trans_id_tree_file_id('subdir-id')
1283
self.assertEqual([], list(transform.iter_changes()))
1284
transform.delete_contents(subdir)
1285
transform.create_directory(subdir)
1286
transform.set_executability(False, old)
1287
transform.unversion_file(old)
1288
transform.version_file('id-1', old)
1289
transform.adjust_path('old', root, old)
1290
self.assertEqual([], list(transform.iter_changes()))
1292
transform.finalize()
1294
def test_rename_count(self):
1295
transform, root = self.get_transform()
1296
transform.new_file('name1', root, 'contents')
1297
self.assertEqual(transform.rename_count, 0)
1299
self.assertEqual(transform.rename_count, 1)
1300
transform2, root = self.get_transform()
1301
transform2.adjust_path('name2', root,
1302
transform2.trans_id_tree_path('name1'))
1303
self.assertEqual(transform2.rename_count, 0)
1305
self.assertEqual(transform2.rename_count, 2)
1307
def test_change_parent(self):
1308
"""Ensure that after we change a parent, the results are still right.
1310
Renames and parent changes on pending transforms can happen as part
1311
of conflict resolution, and are explicitly permitted by the
1314
This test ensures they work correctly with the rename-avoidance
1317
transform, root = self.get_transform()
1318
parent1 = transform.new_directory('parent1', root)
1319
child1 = transform.new_file('child1', parent1, 'contents')
1320
parent2 = transform.new_directory('parent2', root)
1321
transform.adjust_path('child1', parent2, child1)
1323
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1324
self.assertPathExists(self.wt.abspath('parent2/child1'))
1325
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1326
# no rename for child1 (counting only renames during apply)
1327
self.assertEqual(2, transform.rename_count)
1329
def test_cancel_parent(self):
1330
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1332
This is like the test_change_parent, except that we cancel the parent
1333
before adjusting the path. The transform must detect that the
1334
directory is non-empty, and move children to safe locations.
1336
transform, root = self.get_transform()
1337
parent1 = transform.new_directory('parent1', root)
1338
child1 = transform.new_file('child1', parent1, 'contents')
1339
child2 = transform.new_file('child2', parent1, 'contents')
1341
transform.cancel_creation(parent1)
1343
self.fail('Failed to move child1 before deleting parent1')
1344
transform.cancel_creation(child2)
1345
transform.create_directory(parent1)
1347
transform.cancel_creation(parent1)
1348
# If the transform incorrectly believes that child2 is still in
1349
# parent1's limbo directory, it will try to rename it and fail
1350
# because was already moved by the first cancel_creation.
1352
self.fail('Transform still thinks child2 is a child of parent1')
1353
parent2 = transform.new_directory('parent2', root)
1354
transform.adjust_path('child1', parent2, child1)
1356
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1357
self.assertPathExists(self.wt.abspath('parent2/child1'))
1358
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1359
self.assertEqual(2, transform.rename_count)
1361
def test_adjust_and_cancel(self):
1362
"""Make sure adjust_path keeps track of limbo children properly"""
1363
transform, root = self.get_transform()
1364
parent1 = transform.new_directory('parent1', root)
1365
child1 = transform.new_file('child1', parent1, 'contents')
1366
parent2 = transform.new_directory('parent2', root)
1367
transform.adjust_path('child1', parent2, child1)
1368
transform.cancel_creation(child1)
1370
transform.cancel_creation(parent1)
1371
# if the transform thinks child1 is still in parent1's limbo
1372
# directory, it will attempt to move it and fail.
1374
self.fail('Transform still thinks child1 is a child of parent1')
1375
transform.finalize()
1377
def test_noname_contents(self):
1378
"""TreeTransform should permit deferring naming files."""
1379
transform, root = self.get_transform()
1380
parent = transform.trans_id_file_id('parent-id')
1382
transform.create_directory(parent)
1384
self.fail("Can't handle contents with no name")
1385
transform.finalize()
1387
def test_noname_contents_nested(self):
1388
"""TreeTransform should permit deferring naming files."""
1389
transform, root = self.get_transform()
1390
parent = transform.trans_id_file_id('parent-id')
1392
transform.create_directory(parent)
1394
self.fail("Can't handle contents with no name")
1395
child = transform.new_directory('child', parent)
1396
transform.adjust_path('parent', root, parent)
1398
self.assertPathExists(self.wt.abspath('parent/child'))
1399
self.assertEqual(1, transform.rename_count)
1401
def test_reuse_name(self):
1402
"""Avoid reusing the same limbo name for different files"""
1403
transform, root = self.get_transform()
1404
parent = transform.new_directory('parent', root)
1405
child1 = transform.new_directory('child', parent)
1407
child2 = transform.new_directory('child', parent)
1409
self.fail('Tranform tried to use the same limbo name twice')
1410
transform.adjust_path('child2', parent, child2)
1412
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1413
# child2 is put into top-level limbo because child1 has already
1414
# claimed the direct limbo path when child2 is created. There is no
1415
# advantage in renaming files once they're in top-level limbo, except
1417
self.assertEqual(2, transform.rename_count)
1419
def test_reuse_when_first_moved(self):
1420
"""Don't avoid direct paths when it is safe to use them"""
1421
transform, root = self.get_transform()
1422
parent = transform.new_directory('parent', root)
1423
child1 = transform.new_directory('child', parent)
1424
transform.adjust_path('child1', parent, child1)
1425
child2 = transform.new_directory('child', parent)
1427
# limbo/new-1 => parent
1428
self.assertEqual(1, transform.rename_count)
1430
def test_reuse_after_cancel(self):
1431
"""Don't avoid direct paths when it is safe to use them"""
1432
transform, root = self.get_transform()
1433
parent2 = transform.new_directory('parent2', root)
1434
child1 = transform.new_directory('child1', parent2)
1435
transform.cancel_creation(parent2)
1436
transform.create_directory(parent2)
1437
child2 = transform.new_directory('child1', parent2)
1438
transform.adjust_path('child2', parent2, child1)
1440
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1441
self.assertEqual(2, transform.rename_count)
1443
def test_finalize_order(self):
1444
"""Finalize must be done in child-to-parent order"""
1445
transform, root = self.get_transform()
1446
parent = transform.new_directory('parent', root)
1447
child = transform.new_directory('child', parent)
1449
transform.finalize()
1451
self.fail('Tried to remove parent before child1')
1453
def test_cancel_with_cancelled_child_should_succeed(self):
1454
transform, root = self.get_transform()
1455
parent = transform.new_directory('parent', root)
1456
child = transform.new_directory('child', parent)
1457
transform.cancel_creation(child)
1458
transform.cancel_creation(parent)
1459
transform.finalize()
1461
def test_rollback_on_directory_clash(self):
1463
wt = self.make_branch_and_tree('.')
1464
tt = TreeTransform(wt) # TreeTransform obtains write lock
1466
foo = tt.new_directory('foo', tt.root)
1467
tt.new_file('bar', foo, 'foobar')
1468
baz = tt.new_directory('baz', tt.root)
1469
tt.new_file('qux', baz, 'quux')
1470
# Ask for a rename 'foo' -> 'baz'
1471
tt.adjust_path('baz', tt.root, foo)
1472
# Lie to tt that we've already resolved all conflicts.
1473
tt.apply(no_conflicts=True)
1477
# The rename will fail because the target directory is not empty (but
1478
# raises FileExists anyway).
1479
err = self.assertRaises(errors.FileExists, tt_helper)
1480
self.assertEndsWith(err.path, "/baz")
1482
def test_two_directories_clash(self):
1484
wt = self.make_branch_and_tree('.')
1485
tt = TreeTransform(wt) # TreeTransform obtains write lock
1487
foo_1 = tt.new_directory('foo', tt.root)
1488
tt.new_directory('bar', foo_1)
1489
# Adding the same directory with a different content
1490
foo_2 = tt.new_directory('foo', tt.root)
1491
tt.new_directory('baz', foo_2)
1492
# Lie to tt that we've already resolved all conflicts.
1493
tt.apply(no_conflicts=True)
1497
err = self.assertRaises(errors.FileExists, tt_helper)
1498
self.assertEndsWith(err.path, "/foo")
1500
def test_two_directories_clash_finalize(self):
1502
wt = self.make_branch_and_tree('.')
1503
tt = TreeTransform(wt) # TreeTransform obtains write lock
1505
foo_1 = tt.new_directory('foo', tt.root)
1506
tt.new_directory('bar', foo_1)
1507
# Adding the same directory with a different content
1508
foo_2 = tt.new_directory('foo', tt.root)
1509
tt.new_directory('baz', foo_2)
1510
# Lie to tt that we've already resolved all conflicts.
1511
tt.apply(no_conflicts=True)
1515
err = self.assertRaises(errors.FileExists, tt_helper)
1516
self.assertEndsWith(err.path, "/foo")
1518
def test_file_to_directory(self):
1519
wt = self.make_branch_and_tree('.')
1520
self.build_tree(['foo'])
1523
tt = TreeTransform(wt)
1524
self.addCleanup(tt.finalize)
1525
foo_trans_id = tt.trans_id_tree_path("foo")
1526
tt.delete_contents(foo_trans_id)
1527
tt.create_directory(foo_trans_id)
1528
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1529
tt.create_file(["aa\n"], bar_trans_id)
1530
tt.version_file("bar-1", bar_trans_id)
1532
self.assertPathExists("foo/bar")
1535
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
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.inventory.get_file_kind(wt.path2id("foo")),
1561
def test_dir_to_file(self):
1562
wt = self.make_branch_and_tree('.')
1563
self.build_tree(['foo/', 'foo/bar'])
1564
wt.add(['foo', 'foo/bar'])
1566
tt = TreeTransform(wt)
1567
self.addCleanup(tt.finalize)
1568
foo_trans_id = tt.trans_id_tree_path("foo")
1569
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1570
tt.delete_contents(foo_trans_id)
1571
tt.delete_versioned(bar_trans_id)
1572
tt.create_file(["aa\n"], foo_trans_id)
1574
self.assertPathExists("foo")
1576
self.addCleanup(wt.unlock)
1577
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1580
def test_dir_to_hardlink(self):
1581
self.requireFeature(HardlinkFeature)
1582
wt = self.make_branch_and_tree('.')
1583
self.build_tree(['foo/', 'foo/bar'])
1584
wt.add(['foo', 'foo/bar'])
1586
tt = TreeTransform(wt)
1587
self.addCleanup(tt.finalize)
1588
foo_trans_id = tt.trans_id_tree_path("foo")
1589
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1590
tt.delete_contents(foo_trans_id)
1591
tt.delete_versioned(bar_trans_id)
1592
self.build_tree(['baz'])
1593
tt.create_hardlink("baz", foo_trans_id)
1595
self.assertPathExists("foo")
1596
self.assertPathExists("baz")
1598
self.addCleanup(wt.unlock)
1599
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1602
def test_no_final_path(self):
1603
transform, root = self.get_transform()
1604
trans_id = transform.trans_id_file_id('foo')
1605
transform.create_file('bar', trans_id)
1606
transform.cancel_creation(trans_id)
1609
def test_create_from_tree(self):
1610
tree1 = self.make_branch_and_tree('tree1')
1611
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1612
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1613
tree2 = self.make_branch_and_tree('tree2')
1614
tt = TreeTransform(tree2)
1615
foo_trans_id = tt.create_path('foo', tt.root)
1616
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1617
bar_trans_id = tt.create_path('bar', tt.root)
1618
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1620
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1621
self.assertFileEqual('baz', 'tree2/bar')
1623
def test_create_from_tree_bytes(self):
1624
"""Provided lines are used instead of tree content."""
1625
tree1 = self.make_branch_and_tree('tree1')
1626
self.build_tree_contents([('tree1/foo', 'bar'),])
1627
tree1.add('foo', 'foo-id')
1628
tree2 = self.make_branch_and_tree('tree2')
1629
tt = TreeTransform(tree2)
1630
foo_trans_id = tt.create_path('foo', tt.root)
1631
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1633
self.assertFileEqual('qux', 'tree2/foo')
1635
def test_create_from_tree_symlink(self):
1636
self.requireFeature(SymlinkFeature)
1637
tree1 = self.make_branch_and_tree('tree1')
1638
os.symlink('bar', 'tree1/foo')
1639
tree1.add('foo', 'foo-id')
1640
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1641
foo_trans_id = tt.create_path('foo', tt.root)
1642
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1644
self.assertEqual('bar', os.readlink('tree2/foo'))
1647
class TransformGroup(object):
1649
def __init__(self, dirname, root_id):
1652
self.wt = BzrDir.create_standalone_workingtree(dirname)
1653
self.wt.set_root_id(root_id)
1654
self.b = self.wt.branch
1655
self.tt = TreeTransform(self.wt)
1656
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1659
def conflict_text(tree, merge):
1660
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1661
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1664
class TestInventoryAltered(tests.TestCaseWithTransport):
1666
def test_inventory_altered_unchanged(self):
1667
tree = self.make_branch_and_tree('tree')
1668
self.build_tree(['tree/foo'])
1669
tree.add('foo', 'foo-id')
1670
with TransformPreview(tree) as tt:
1671
self.assertEqual([], tt._inventory_altered())
1673
def test_inventory_altered_changed_parent_id(self):
1674
tree = self.make_branch_and_tree('tree')
1675
self.build_tree(['tree/foo'])
1676
tree.add('foo', 'foo-id')
1677
with TransformPreview(tree) as tt:
1678
tt.unversion_file(tt.root)
1679
tt.version_file('new-id', tt.root)
1680
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1681
foo_tuple = ('foo', foo_trans_id)
1682
root_tuple = ('', tt.root)
1683
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1685
def test_inventory_altered_noop_changed_parent_id(self):
1686
tree = self.make_branch_and_tree('tree')
1687
self.build_tree(['tree/foo'])
1688
tree.add('foo', 'foo-id')
1689
with TransformPreview(tree) as tt:
1690
tt.unversion_file(tt.root)
1691
tt.version_file(tree.get_root_id(), tt.root)
1692
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1693
self.assertEqual([], tt._inventory_altered())
1696
class TestTransformMerge(TestCaseInTempDir):
1698
def test_text_merge(self):
1699
root_id = generate_ids.gen_root_id()
1700
base = TransformGroup("base", root_id)
1701
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1702
base.tt.new_file('b', base.root, 'b1', 'b')
1703
base.tt.new_file('c', base.root, 'c', 'c')
1704
base.tt.new_file('d', base.root, 'd', 'd')
1705
base.tt.new_file('e', base.root, 'e', 'e')
1706
base.tt.new_file('f', base.root, 'f', 'f')
1707
base.tt.new_directory('g', base.root, 'g')
1708
base.tt.new_directory('h', base.root, 'h')
1710
other = TransformGroup("other", root_id)
1711
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1712
other.tt.new_file('b', other.root, 'b2', 'b')
1713
other.tt.new_file('c', other.root, 'c2', 'c')
1714
other.tt.new_file('d', other.root, 'd', 'd')
1715
other.tt.new_file('e', other.root, 'e2', 'e')
1716
other.tt.new_file('f', other.root, 'f', 'f')
1717
other.tt.new_file('g', other.root, 'g', 'g')
1718
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1719
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1721
this = TransformGroup("this", root_id)
1722
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1723
this.tt.new_file('b', this.root, 'b', 'b')
1724
this.tt.new_file('c', this.root, 'c', 'c')
1725
this.tt.new_file('d', this.root, 'd2', 'd')
1726
this.tt.new_file('e', this.root, 'e2', 'e')
1727
this.tt.new_file('f', this.root, 'f', 'f')
1728
this.tt.new_file('g', this.root, 'g', 'g')
1729
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1730
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1732
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1735
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1736
# three-way text conflict
1737
self.assertEqual(this.wt.get_file('b').read(),
1738
conflict_text('b', 'b2'))
1740
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1742
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1743
# Ambigious clean merge
1744
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1746
self.assertEqual(this.wt.get_file('f').read(), 'f')
1747
# Correct correct results when THIS == OTHER
1748
self.assertEqual(this.wt.get_file('g').read(), 'g')
1749
# Text conflict when THIS & OTHER are text and BASE is dir
1750
self.assertEqual(this.wt.get_file('h').read(),
1751
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1752
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1754
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1756
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1757
self.assertEqual(this.wt.get_file('i').read(),
1758
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1759
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1761
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1763
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1764
modified = ['a', 'b', 'c', 'h', 'i']
1765
merge_modified = this.wt.merge_modified()
1766
self.assertSubset(merge_modified, modified)
1767
self.assertEqual(len(merge_modified), len(modified))
1768
with file(this.wt.id2abspath('a'), 'wb') as f: f.write('booga')
1770
merge_modified = this.wt.merge_modified()
1771
self.assertSubset(merge_modified, modified)
1772
self.assertEqual(len(merge_modified), len(modified))
1776
def test_file_merge(self):
1777
self.requireFeature(SymlinkFeature)
1778
root_id = generate_ids.gen_root_id()
1779
base = TransformGroup("BASE", root_id)
1780
this = TransformGroup("THIS", root_id)
1781
other = TransformGroup("OTHER", root_id)
1782
for tg in this, base, other:
1783
tg.tt.new_directory('a', tg.root, 'a')
1784
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1785
tg.tt.new_file('c', tg.root, 'c', 'c')
1786
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1787
targets = ((base, 'base-e', 'base-f', None, None),
1788
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1789
(other, 'other-e', None, 'other-g', 'other-h'))
1790
for tg, e_target, f_target, g_target, h_target in targets:
1791
for link, target in (('e', e_target), ('f', f_target),
1792
('g', g_target), ('h', h_target)):
1793
if target is not None:
1794
tg.tt.new_symlink(link, tg.root, target, link)
1796
for tg in this, base, other:
1798
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1799
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1800
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1801
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1802
for suffix in ('THIS', 'BASE', 'OTHER'):
1803
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1804
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1805
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1806
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1807
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1808
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1809
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1810
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1811
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1812
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1813
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1814
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1815
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1816
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1818
def test_filename_merge(self):
1819
root_id = generate_ids.gen_root_id()
1820
base = TransformGroup("BASE", root_id)
1821
this = TransformGroup("THIS", root_id)
1822
other = TransformGroup("OTHER", root_id)
1823
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1824
for t in [base, this, other]]
1825
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1826
for t in [base, this, other]]
1827
base.tt.new_directory('c', base_a, 'c')
1828
this.tt.new_directory('c1', this_a, 'c')
1829
other.tt.new_directory('c', other_b, 'c')
1831
base.tt.new_directory('d', base_a, 'd')
1832
this.tt.new_directory('d1', this_b, 'd')
1833
other.tt.new_directory('d', other_a, 'd')
1835
base.tt.new_directory('e', base_a, 'e')
1836
this.tt.new_directory('e', this_a, 'e')
1837
other.tt.new_directory('e1', other_b, 'e')
1839
base.tt.new_directory('f', base_a, 'f')
1840
this.tt.new_directory('f1', this_b, 'f')
1841
other.tt.new_directory('f1', other_b, 'f')
1843
for tg in [this, base, other]:
1845
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1846
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1847
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1848
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1849
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1851
def test_filename_merge_conflicts(self):
1852
root_id = generate_ids.gen_root_id()
1853
base = TransformGroup("BASE", root_id)
1854
this = TransformGroup("THIS", root_id)
1855
other = TransformGroup("OTHER", root_id)
1856
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1857
for t in [base, this, other]]
1858
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1859
for t in [base, this, other]]
1861
base.tt.new_file('g', base_a, 'g', 'g')
1862
other.tt.new_file('g1', other_b, 'g1', 'g')
1864
base.tt.new_file('h', base_a, 'h', 'h')
1865
this.tt.new_file('h1', this_b, 'h1', 'h')
1867
base.tt.new_file('i', base.root, 'i', 'i')
1868
other.tt.new_directory('i1', this_b, 'i')
1870
for tg in [this, base, other]:
1872
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1874
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1875
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1876
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1877
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1878
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1879
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1880
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1883
class TestBuildTree(tests.TestCaseWithTransport):
1885
def test_build_tree_with_symlinks(self):
1886
self.requireFeature(SymlinkFeature)
1888
a = BzrDir.create_standalone_workingtree('a')
1890
with file('a/foo/bar', 'wb') as f: f.write('contents')
1891
os.symlink('a/foo/bar', 'a/foo/baz')
1892
a.add(['foo', 'foo/bar', 'foo/baz'])
1893
a.commit('initial commit')
1894
b = BzrDir.create_standalone_workingtree('b')
1895
basis = a.basis_tree()
1897
self.addCleanup(basis.unlock)
1898
build_tree(basis, b)
1899
self.assertIs(os.path.isdir('b/foo'), True)
1900
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1901
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1903
def test_build_with_references(self):
1904
tree = self.make_branch_and_tree('source',
1905
format='dirstate-with-subtree')
1906
subtree = self.make_branch_and_tree('source/subtree',
1907
format='dirstate-with-subtree')
1908
tree.add_reference(subtree)
1909
tree.commit('a revision')
1910
tree.branch.create_checkout('target')
1911
self.assertPathExists('target')
1912
self.assertPathExists('target/subtree')
1914
def test_file_conflict_handling(self):
1915
"""Ensure that when building trees, conflict handling is done"""
1916
source = self.make_branch_and_tree('source')
1917
target = self.make_branch_and_tree('target')
1918
self.build_tree(['source/file', 'target/file'])
1919
source.add('file', 'new-file')
1920
source.commit('added file')
1921
build_tree(source.basis_tree(), target)
1922
self.assertEqual([DuplicateEntry('Moved existing file to',
1923
'file.moved', 'file', None, 'new-file')],
1925
target2 = self.make_branch_and_tree('target2')
1926
target_file = file('target2/file', 'wb')
1928
source_file = file('source/file', 'rb')
1930
target_file.write(source_file.read())
1935
build_tree(source.basis_tree(), target2)
1936
self.assertEqual([], target2.conflicts())
1938
def test_symlink_conflict_handling(self):
1939
"""Ensure that when building trees, conflict handling is done"""
1940
self.requireFeature(SymlinkFeature)
1941
source = self.make_branch_and_tree('source')
1942
os.symlink('foo', 'source/symlink')
1943
source.add('symlink', 'new-symlink')
1944
source.commit('added file')
1945
target = self.make_branch_and_tree('target')
1946
os.symlink('bar', 'target/symlink')
1947
build_tree(source.basis_tree(), target)
1948
self.assertEqual([DuplicateEntry('Moved existing file to',
1949
'symlink.moved', 'symlink', None, 'new-symlink')],
1951
target = self.make_branch_and_tree('target2')
1952
os.symlink('foo', 'target2/symlink')
1953
build_tree(source.basis_tree(), target)
1954
self.assertEqual([], target.conflicts())
1956
def test_directory_conflict_handling(self):
1957
"""Ensure that when building trees, conflict handling is done"""
1958
source = self.make_branch_and_tree('source')
1959
target = self.make_branch_and_tree('target')
1960
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1961
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1962
source.commit('added file')
1963
build_tree(source.basis_tree(), target)
1964
self.assertEqual([], target.conflicts())
1965
self.assertPathExists('target/dir1/file')
1967
# Ensure contents are merged
1968
target = self.make_branch_and_tree('target2')
1969
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1970
build_tree(source.basis_tree(), target)
1971
self.assertEqual([], target.conflicts())
1972
self.assertPathExists('target2/dir1/file2')
1973
self.assertPathExists('target2/dir1/file')
1975
# Ensure new contents are suppressed for existing branches
1976
target = self.make_branch_and_tree('target3')
1977
self.make_branch('target3/dir1')
1978
self.build_tree(['target3/dir1/file2'])
1979
build_tree(source.basis_tree(), target)
1980
self.assertPathDoesNotExist('target3/dir1/file')
1981
self.assertPathExists('target3/dir1/file2')
1982
self.assertPathExists('target3/dir1.diverted/file')
1983
self.assertEqual([DuplicateEntry('Diverted to',
1984
'dir1.diverted', 'dir1', 'new-dir1', None)],
1987
target = self.make_branch_and_tree('target4')
1988
self.build_tree(['target4/dir1/'])
1989
self.make_branch('target4/dir1/file')
1990
build_tree(source.basis_tree(), target)
1991
self.assertPathExists('target4/dir1/file')
1992
self.assertEqual('directory', file_kind('target4/dir1/file'))
1993
self.assertPathExists('target4/dir1/file.diverted')
1994
self.assertEqual([DuplicateEntry('Diverted to',
1995
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1998
def test_mixed_conflict_handling(self):
1999
"""Ensure that when building trees, conflict handling is done"""
2000
source = self.make_branch_and_tree('source')
2001
target = self.make_branch_and_tree('target')
2002
self.build_tree(['source/name', 'target/name/'])
2003
source.add('name', 'new-name')
2004
source.commit('added file')
2005
build_tree(source.basis_tree(), target)
2006
self.assertEqual([DuplicateEntry('Moved existing file to',
2007
'name.moved', 'name', None, 'new-name')], target.conflicts())
2009
def test_raises_in_populated(self):
2010
source = self.make_branch_and_tree('source')
2011
self.build_tree(['source/name'])
2013
source.commit('added name')
2014
target = self.make_branch_and_tree('target')
2015
self.build_tree(['target/name'])
2017
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2018
build_tree, source.basis_tree(), target)
2020
def test_build_tree_rename_count(self):
2021
source = self.make_branch_and_tree('source')
2022
self.build_tree(['source/file1', 'source/dir1/'])
2023
source.add(['file1', 'dir1'])
2024
source.commit('add1')
2025
target1 = self.make_branch_and_tree('target1')
2026
transform_result = build_tree(source.basis_tree(), target1)
2027
self.assertEqual(2, transform_result.rename_count)
2029
self.build_tree(['source/dir1/file2'])
2030
source.add(['dir1/file2'])
2031
source.commit('add3')
2032
target2 = self.make_branch_and_tree('target2')
2033
transform_result = build_tree(source.basis_tree(), target2)
2034
# children of non-root directories should not be renamed
2035
self.assertEqual(2, transform_result.rename_count)
2037
def create_ab_tree(self):
2038
"""Create a committed test tree with two files"""
2039
source = self.make_branch_and_tree('source')
2040
self.build_tree_contents([('source/file1', 'A')])
2041
self.build_tree_contents([('source/file2', 'B')])
2042
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2043
source.commit('commit files')
2045
self.addCleanup(source.unlock)
2048
def test_build_tree_accelerator_tree(self):
2049
source = self.create_ab_tree()
2050
self.build_tree_contents([('source/file2', 'C')])
2052
real_source_get_file = source.get_file
2053
def get_file(file_id, path=None):
2054
calls.append(file_id)
2055
return real_source_get_file(file_id, path)
2056
source.get_file = get_file
2057
target = self.make_branch_and_tree('target')
2058
revision_tree = source.basis_tree()
2059
revision_tree.lock_read()
2060
self.addCleanup(revision_tree.unlock)
2061
build_tree(revision_tree, target, source)
2062
self.assertEqual(['file1-id'], calls)
2064
self.addCleanup(target.unlock)
2065
self.assertEqual([], list(target.iter_changes(revision_tree)))
2067
def test_build_tree_accelerator_tree_observes_sha1(self):
2068
source = self.create_ab_tree()
2069
sha1 = osutils.sha_string('A')
2070
target = self.make_branch_and_tree('target')
2072
self.addCleanup(target.unlock)
2073
state = target.current_dirstate()
2074
state._cutoff_time = time.time() + 60
2075
build_tree(source.basis_tree(), target, source)
2076
entry = state._get_entry(0, path_utf8='file1')
2077
self.assertEqual(sha1, entry[1][0][1])
2079
def test_build_tree_accelerator_tree_missing_file(self):
2080
source = self.create_ab_tree()
2081
os.unlink('source/file1')
2082
source.remove(['file2'])
2083
target = self.make_branch_and_tree('target')
2084
revision_tree = source.basis_tree()
2085
revision_tree.lock_read()
2086
self.addCleanup(revision_tree.unlock)
2087
build_tree(revision_tree, target, source)
2089
self.addCleanup(target.unlock)
2090
self.assertEqual([], list(target.iter_changes(revision_tree)))
2092
def test_build_tree_accelerator_wrong_kind(self):
2093
self.requireFeature(SymlinkFeature)
2094
source = self.make_branch_and_tree('source')
2095
self.build_tree_contents([('source/file1', '')])
2096
self.build_tree_contents([('source/file2', '')])
2097
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2098
source.commit('commit files')
2099
os.unlink('source/file2')
2100
self.build_tree_contents([('source/file2/', 'C')])
2101
os.unlink('source/file1')
2102
os.symlink('file2', 'source/file1')
2104
real_source_get_file = source.get_file
2105
def get_file(file_id, path=None):
2106
calls.append(file_id)
2107
return real_source_get_file(file_id, path)
2108
source.get_file = get_file
2109
target = self.make_branch_and_tree('target')
2110
revision_tree = source.basis_tree()
2111
revision_tree.lock_read()
2112
self.addCleanup(revision_tree.unlock)
2113
build_tree(revision_tree, target, source)
2114
self.assertEqual([], calls)
2116
self.addCleanup(target.unlock)
2117
self.assertEqual([], list(target.iter_changes(revision_tree)))
2119
def test_build_tree_hardlink(self):
2120
self.requireFeature(HardlinkFeature)
2121
source = self.create_ab_tree()
2122
target = self.make_branch_and_tree('target')
2123
revision_tree = source.basis_tree()
2124
revision_tree.lock_read()
2125
self.addCleanup(revision_tree.unlock)
2126
build_tree(revision_tree, target, source, hardlink=True)
2128
self.addCleanup(target.unlock)
2129
self.assertEqual([], list(target.iter_changes(revision_tree)))
2130
source_stat = os.stat('source/file1')
2131
target_stat = os.stat('target/file1')
2132
self.assertEqual(source_stat, target_stat)
2134
# Explicitly disallowing hardlinks should prevent them.
2135
target2 = self.make_branch_and_tree('target2')
2136
build_tree(revision_tree, target2, source, hardlink=False)
2138
self.addCleanup(target2.unlock)
2139
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2140
source_stat = os.stat('source/file1')
2141
target2_stat = os.stat('target2/file1')
2142
self.assertNotEqual(source_stat, target2_stat)
2144
def test_build_tree_accelerator_tree_moved(self):
2145
source = self.make_branch_and_tree('source')
2146
self.build_tree_contents([('source/file1', 'A')])
2147
source.add(['file1'], ['file1-id'])
2148
source.commit('commit files')
2149
source.rename_one('file1', 'file2')
2151
self.addCleanup(source.unlock)
2152
target = self.make_branch_and_tree('target')
2153
revision_tree = source.basis_tree()
2154
revision_tree.lock_read()
2155
self.addCleanup(revision_tree.unlock)
2156
build_tree(revision_tree, target, source)
2158
self.addCleanup(target.unlock)
2159
self.assertEqual([], list(target.iter_changes(revision_tree)))
2161
def test_build_tree_hardlinks_preserve_execute(self):
2162
self.requireFeature(HardlinkFeature)
2163
source = self.create_ab_tree()
2164
tt = TreeTransform(source)
2165
trans_id = tt.trans_id_tree_file_id('file1-id')
2166
tt.set_executability(True, trans_id)
2168
self.assertTrue(source.is_executable('file1-id'))
2169
target = self.make_branch_and_tree('target')
2170
revision_tree = source.basis_tree()
2171
revision_tree.lock_read()
2172
self.addCleanup(revision_tree.unlock)
2173
build_tree(revision_tree, target, source, hardlink=True)
2175
self.addCleanup(target.unlock)
2176
self.assertEqual([], list(target.iter_changes(revision_tree)))
2177
self.assertTrue(source.is_executable('file1-id'))
2179
def install_rot13_content_filter(self, pattern):
2181
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2182
# below, but that looks a bit... hard to read even if it's exactly
2184
original_registry = filters._reset_registry()
2185
def restore_registry():
2186
filters._reset_registry(original_registry)
2187
self.addCleanup(restore_registry)
2188
def rot13(chunks, context=None):
2189
return [''.join(chunks).encode('rot13')]
2190
rot13filter = filters.ContentFilter(rot13, rot13)
2191
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2192
os.mkdir(self.test_home_dir + '/.bazaar')
2193
rules_filename = self.test_home_dir + '/.bazaar/rules'
2194
f = open(rules_filename, 'wb')
2195
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2197
def uninstall_rules():
2198
os.remove(rules_filename)
2200
self.addCleanup(uninstall_rules)
2203
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2204
"""build_tree will not hardlink files that have content filtering rules
2205
applied to them (but will still hardlink other files from the same tree
2208
self.requireFeature(HardlinkFeature)
2209
self.install_rot13_content_filter('file1')
2210
source = self.create_ab_tree()
2211
target = self.make_branch_and_tree('target')
2212
revision_tree = source.basis_tree()
2213
revision_tree.lock_read()
2214
self.addCleanup(revision_tree.unlock)
2215
build_tree(revision_tree, target, source, hardlink=True)
2217
self.addCleanup(target.unlock)
2218
self.assertEqual([], list(target.iter_changes(revision_tree)))
2219
source_stat = os.stat('source/file1')
2220
target_stat = os.stat('target/file1')
2221
self.assertNotEqual(source_stat, target_stat)
2222
source_stat = os.stat('source/file2')
2223
target_stat = os.stat('target/file2')
2224
self.assertEqualStat(source_stat, target_stat)
2226
def test_case_insensitive_build_tree_inventory(self):
2227
if (features.CaseInsensitiveFilesystemFeature.available()
2228
or features.CaseInsCasePresFilenameFeature.available()):
2229
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2230
source = self.make_branch_and_tree('source')
2231
self.build_tree(['source/file', 'source/FILE'])
2232
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2233
source.commit('added files')
2234
# Don't try this at home, kids!
2235
# Force the tree to report that it is case insensitive
2236
target = self.make_branch_and_tree('target')
2237
target.case_sensitive = False
2238
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2239
self.assertEqual('file.moved', target.id2path('lower-id'))
2240
self.assertEqual('FILE', target.id2path('upper-id'))
2242
def test_build_tree_observes_sha(self):
2243
source = self.make_branch_and_tree('source')
2244
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2245
source.add(['file1', 'dir', 'dir/file2'],
2246
['file1-id', 'dir-id', 'file2-id'])
2247
source.commit('new files')
2248
target = self.make_branch_and_tree('target')
2250
self.addCleanup(target.unlock)
2251
# We make use of the fact that DirState caches its cutoff time. So we
2252
# set the 'safe' time to one minute in the future.
2253
state = target.current_dirstate()
2254
state._cutoff_time = time.time() + 60
2255
build_tree(source.basis_tree(), target)
2256
entry1_sha = osutils.sha_file_by_name('source/file1')
2257
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2258
# entry[1] is the state information, entry[1][0] is the state of the
2259
# working tree, entry[1][0][1] is the sha value for the current working
2261
entry1 = state._get_entry(0, path_utf8='file1')
2262
self.assertEqual(entry1_sha, entry1[1][0][1])
2263
# The 'size' field must also be set.
2264
self.assertEqual(25, entry1[1][0][2])
2265
entry1_state = entry1[1][0]
2266
entry2 = state._get_entry(0, path_utf8='dir/file2')
2267
self.assertEqual(entry2_sha, entry2[1][0][1])
2268
self.assertEqual(29, entry2[1][0][2])
2269
entry2_state = entry2[1][0]
2270
# Now, make sure that we don't have to re-read the content. The
2271
# packed_stat should match exactly.
2272
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2273
self.assertEqual(entry2_sha,
2274
target.get_file_sha1('file2-id', 'dir/file2'))
2275
self.assertEqual(entry1_state, entry1[1][0])
2276
self.assertEqual(entry2_state, entry2[1][0])
2279
class TestCommitTransform(tests.TestCaseWithTransport):
2281
def get_branch(self):
2282
tree = self.make_branch_and_tree('tree')
2284
self.addCleanup(tree.unlock)
2285
tree.commit('empty commit')
2288
def get_branch_and_transform(self):
2289
branch = self.get_branch()
2290
tt = TransformPreview(branch.basis_tree())
2291
self.addCleanup(tt.finalize)
2294
def test_commit_wrong_basis(self):
2295
branch = self.get_branch()
2296
basis = branch.repository.revision_tree(
2297
_mod_revision.NULL_REVISION)
2298
tt = TransformPreview(basis)
2299
self.addCleanup(tt.finalize)
2300
e = self.assertRaises(ValueError, tt.commit, branch, '')
2301
self.assertEqual('TreeTransform not based on branch basis: null:',
2304
def test_empy_commit(self):
2305
branch, tt = self.get_branch_and_transform()
2306
rev = tt.commit(branch, 'my message')
2307
self.assertEqual(2, branch.revno())
2308
repo = branch.repository
2309
self.assertEqual('my message', repo.get_revision(rev).message)
2311
def test_merge_parents(self):
2312
branch, tt = self.get_branch_and_transform()
2313
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2314
self.assertEqual(['rev1b', 'rev1c'],
2315
branch.basis_tree().get_parent_ids()[1:])
2317
def test_first_commit(self):
2318
branch = self.make_branch('branch')
2320
self.addCleanup(branch.unlock)
2321
tt = TransformPreview(branch.basis_tree())
2322
self.addCleanup(tt.finalize)
2323
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2324
rev = tt.commit(branch, 'my message')
2325
self.assertEqual([], branch.basis_tree().get_parent_ids())
2326
self.assertNotEqual(_mod_revision.NULL_REVISION,
2327
branch.last_revision())
2329
def test_first_commit_with_merge_parents(self):
2330
branch = self.make_branch('branch')
2332
self.addCleanup(branch.unlock)
2333
tt = TransformPreview(branch.basis_tree())
2334
self.addCleanup(tt.finalize)
2335
e = self.assertRaises(ValueError, tt.commit, branch,
2336
'my message', ['rev1b-id'])
2337
self.assertEqual('Cannot supply merge parents for first commit.',
2339
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2341
def test_add_files(self):
2342
branch, tt = self.get_branch_and_transform()
2343
tt.new_file('file', tt.root, 'contents', 'file-id')
2344
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2345
if SymlinkFeature.available():
2346
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2347
rev = tt.commit(branch, 'message')
2348
tree = branch.basis_tree()
2349
self.assertEqual('file', tree.id2path('file-id'))
2350
self.assertEqual('contents', tree.get_file_text('file-id'))
2351
self.assertEqual('dir', tree.id2path('dir-id'))
2352
if SymlinkFeature.available():
2353
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2354
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2356
def test_add_unversioned(self):
2357
branch, tt = self.get_branch_and_transform()
2358
tt.new_file('file', tt.root, 'contents')
2359
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2360
'message', strict=True)
2362
def test_modify_strict(self):
2363
branch, tt = self.get_branch_and_transform()
2364
tt.new_file('file', tt.root, 'contents', 'file-id')
2365
tt.commit(branch, 'message', strict=True)
2366
tt = TransformPreview(branch.basis_tree())
2367
self.addCleanup(tt.finalize)
2368
trans_id = tt.trans_id_file_id('file-id')
2369
tt.delete_contents(trans_id)
2370
tt.create_file('contents', trans_id)
2371
tt.commit(branch, 'message', strict=True)
2373
def test_commit_malformed(self):
2374
"""Committing a malformed transform should raise an exception.
2376
In this case, we are adding a file without adding its parent.
2378
branch, tt = self.get_branch_and_transform()
2379
parent_id = tt.trans_id_file_id('parent-id')
2380
tt.new_file('file', parent_id, 'contents', 'file-id')
2381
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2384
def test_commit_rich_revision_data(self):
2385
branch, tt = self.get_branch_and_transform()
2386
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2387
committer='me <me@example.com>',
2388
revprops={'foo': 'bar'}, revision_id='revid-1',
2389
authors=['Author1 <author1@example.com>',
2390
'Author2 <author2@example.com>',
2392
self.assertEqual('revid-1', rev_id)
2393
revision = branch.repository.get_revision(rev_id)
2394
self.assertEqual(1, revision.timestamp)
2395
self.assertEqual(43201, revision.timezone)
2396
self.assertEqual('me <me@example.com>', revision.committer)
2397
self.assertEqual(['Author1 <author1@example.com>',
2398
'Author2 <author2@example.com>'],
2399
revision.get_apparent_authors())
2400
del revision.properties['authors']
2401
self.assertEqual({'foo': 'bar',
2402
'branch-nick': 'tree'},
2403
revision.properties)
2405
def test_no_explicit_revprops(self):
2406
branch, tt = self.get_branch_and_transform()
2407
rev_id = tt.commit(branch, 'message', authors=[
2408
'Author1 <author1@example.com>',
2409
'Author2 <author2@example.com>', ])
2410
revision = branch.repository.get_revision(rev_id)
2411
self.assertEqual(['Author1 <author1@example.com>',
2412
'Author2 <author2@example.com>'],
2413
revision.get_apparent_authors())
2414
self.assertEqual('tree', revision.properties['branch-nick'])
2417
class TestBackupName(tests.TestCase):
2419
def test_deprecations(self):
2420
class MockTransform(object):
2422
def has_named_child(self, by_parent, parent_id, name):
2423
return name in by_parent.get(parent_id, [])
2425
class MockEntry(object):
2428
object.__init__(self)
2431
tt = MockTransform()
2432
name1 = self.applyDeprecated(
2433
symbol_versioning.deprecated_in((2, 3, 0)),
2434
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2435
self.assertEqual('name.~1~', name1)
2436
name2 = self.applyDeprecated(
2437
symbol_versioning.deprecated_in((2, 3, 0)),
2438
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2439
self.assertEqual('name.~2~', name2)
2442
class TestFileMover(tests.TestCaseWithTransport):
2444
def test_file_mover(self):
2445
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2446
mover = _FileMover()
2447
mover.rename('a', 'q')
2448
self.assertPathExists('q')
2449
self.assertPathDoesNotExist('a')
2450
self.assertPathExists('q/b')
2451
self.assertPathExists('c')
2452
self.assertPathExists('c/d')
2454
def test_pre_delete_rollback(self):
2455
self.build_tree(['a/'])
2456
mover = _FileMover()
2457
mover.pre_delete('a', 'q')
2458
self.assertPathExists('q')
2459
self.assertPathDoesNotExist('a')
2461
self.assertPathDoesNotExist('q')
2462
self.assertPathExists('a')
2464
def test_apply_deletions(self):
2465
self.build_tree(['a/', 'b/'])
2466
mover = _FileMover()
2467
mover.pre_delete('a', 'q')
2468
mover.pre_delete('b', 'r')
2469
self.assertPathExists('q')
2470
self.assertPathExists('r')
2471
self.assertPathDoesNotExist('a')
2472
self.assertPathDoesNotExist('b')
2473
mover.apply_deletions()
2474
self.assertPathDoesNotExist('q')
2475
self.assertPathDoesNotExist('r')
2476
self.assertPathDoesNotExist('a')
2477
self.assertPathDoesNotExist('b')
2479
def test_file_mover_rollback(self):
2480
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2481
mover = _FileMover()
2482
mover.rename('c/d', 'c/f')
2483
mover.rename('c/e', 'c/d')
2485
mover.rename('a', 'c')
2486
except errors.FileExists, e:
2488
self.assertPathExists('a')
2489
self.assertPathExists('c/d')
2492
class Bogus(Exception):
2496
class TestTransformRollback(tests.TestCaseWithTransport):
2498
class ExceptionFileMover(_FileMover):
2500
def __init__(self, bad_source=None, bad_target=None):
2501
_FileMover.__init__(self)
2502
self.bad_source = bad_source
2503
self.bad_target = bad_target
2505
def rename(self, source, target):
2506
if (self.bad_source is not None and
2507
source.endswith(self.bad_source)):
2509
elif (self.bad_target is not None and
2510
target.endswith(self.bad_target)):
2513
_FileMover.rename(self, source, target)
2515
def test_rollback_rename(self):
2516
tree = self.make_branch_and_tree('.')
2517
self.build_tree(['a/', 'a/b'])
2518
tt = TreeTransform(tree)
2519
self.addCleanup(tt.finalize)
2520
a_id = tt.trans_id_tree_path('a')
2521
tt.adjust_path('c', tt.root, a_id)
2522
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2523
self.assertRaises(Bogus, tt.apply,
2524
_mover=self.ExceptionFileMover(bad_source='a'))
2525
self.assertPathExists('a')
2526
self.assertPathExists('a/b')
2528
self.assertPathExists('c')
2529
self.assertPathExists('c/d')
2531
def test_rollback_rename_into_place(self):
2532
tree = self.make_branch_and_tree('.')
2533
self.build_tree(['a/', 'a/b'])
2534
tt = TreeTransform(tree)
2535
self.addCleanup(tt.finalize)
2536
a_id = tt.trans_id_tree_path('a')
2537
tt.adjust_path('c', tt.root, a_id)
2538
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2539
self.assertRaises(Bogus, tt.apply,
2540
_mover=self.ExceptionFileMover(bad_target='c/d'))
2541
self.assertPathExists('a')
2542
self.assertPathExists('a/b')
2544
self.assertPathExists('c')
2545
self.assertPathExists('c/d')
2547
def test_rollback_deletion(self):
2548
tree = self.make_branch_and_tree('.')
2549
self.build_tree(['a/', 'a/b'])
2550
tt = TreeTransform(tree)
2551
self.addCleanup(tt.finalize)
2552
a_id = tt.trans_id_tree_path('a')
2553
tt.delete_contents(a_id)
2554
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2555
self.assertRaises(Bogus, tt.apply,
2556
_mover=self.ExceptionFileMover(bad_target='d'))
2557
self.assertPathExists('a')
2558
self.assertPathExists('a/b')
2561
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2562
"""Ensure treetransform creation errors can be safely cleaned up after"""
2564
def _override_globals_in_method(self, instance, method_name, globals):
2565
"""Replace method on instance with one with updated globals"""
2567
func = getattr(instance, method_name).im_func
2568
new_globals = dict(func.func_globals)
2569
new_globals.update(globals)
2570
new_func = types.FunctionType(func.func_code, new_globals,
2571
func.func_name, func.func_defaults)
2572
setattr(instance, method_name,
2573
types.MethodType(new_func, instance, instance.__class__))
2574
self.addCleanup(delattr, instance, method_name)
2577
def _fake_open_raises_before(name, mode):
2578
"""Like open() but raises before doing anything"""
2582
def _fake_open_raises_after(name, mode):
2583
"""Like open() but raises after creating file without returning"""
2584
open(name, mode).close()
2587
def create_transform_and_root_trans_id(self):
2588
"""Setup a transform creating a file in limbo"""
2589
tree = self.make_branch_and_tree('.')
2590
tt = TreeTransform(tree)
2591
return tt, tt.create_path("a", tt.root)
2593
def create_transform_and_subdir_trans_id(self):
2594
"""Setup a transform creating a directory containing a file in limbo"""
2595
tree = self.make_branch_and_tree('.')
2596
tt = TreeTransform(tree)
2597
d_trans_id = tt.create_path("d", tt.root)
2598
tt.create_directory(d_trans_id)
2599
f_trans_id = tt.create_path("a", d_trans_id)
2600
tt.adjust_path("a", d_trans_id, f_trans_id)
2601
return tt, f_trans_id
2603
def test_root_create_file_open_raises_before_creation(self):
2604
tt, trans_id = self.create_transform_and_root_trans_id()
2605
self._override_globals_in_method(tt, "create_file",
2606
{"open": self._fake_open_raises_before})
2607
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2608
path = tt._limbo_name(trans_id)
2609
self.assertPathDoesNotExist(path)
2611
self.assertPathDoesNotExist(tt._limbodir)
2613
def test_root_create_file_open_raises_after_creation(self):
2614
tt, trans_id = self.create_transform_and_root_trans_id()
2615
self._override_globals_in_method(tt, "create_file",
2616
{"open": self._fake_open_raises_after})
2617
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2618
path = tt._limbo_name(trans_id)
2619
self.assertPathExists(path)
2621
self.assertPathDoesNotExist(path)
2622
self.assertPathDoesNotExist(tt._limbodir)
2624
def test_subdir_create_file_open_raises_before_creation(self):
2625
tt, trans_id = self.create_transform_and_subdir_trans_id()
2626
self._override_globals_in_method(tt, "create_file",
2627
{"open": self._fake_open_raises_before})
2628
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2629
path = tt._limbo_name(trans_id)
2630
self.assertPathDoesNotExist(path)
2632
self.assertPathDoesNotExist(tt._limbodir)
2634
def test_subdir_create_file_open_raises_after_creation(self):
2635
tt, trans_id = self.create_transform_and_subdir_trans_id()
2636
self._override_globals_in_method(tt, "create_file",
2637
{"open": self._fake_open_raises_after})
2638
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2639
path = tt._limbo_name(trans_id)
2640
self.assertPathExists(path)
2642
self.assertPathDoesNotExist(path)
2643
self.assertPathDoesNotExist(tt._limbodir)
2645
def test_rename_in_limbo_rename_raises_after_rename(self):
2646
tt, trans_id = self.create_transform_and_root_trans_id()
2647
parent1 = tt.new_directory('parent1', tt.root)
2648
child1 = tt.new_file('child1', parent1, 'contents')
2649
parent2 = tt.new_directory('parent2', tt.root)
2651
class FakeOSModule(object):
2652
def rename(self, old, new):
2655
self._override_globals_in_method(tt, "_rename_in_limbo",
2656
{"os": FakeOSModule()})
2658
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2659
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2660
self.assertPathExists(path)
2662
self.assertPathDoesNotExist(path)
2663
self.assertPathDoesNotExist(tt._limbodir)
2665
def test_rename_in_limbo_rename_raises_before_rename(self):
2666
tt, trans_id = self.create_transform_and_root_trans_id()
2667
parent1 = tt.new_directory('parent1', tt.root)
2668
child1 = tt.new_file('child1', parent1, 'contents')
2669
parent2 = tt.new_directory('parent2', tt.root)
2671
class FakeOSModule(object):
2672
def rename(self, old, new):
2674
self._override_globals_in_method(tt, "_rename_in_limbo",
2675
{"os": FakeOSModule()})
2677
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2678
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2679
self.assertPathExists(path)
2681
self.assertPathDoesNotExist(path)
2682
self.assertPathDoesNotExist(tt._limbodir)
2685
class TestTransformMissingParent(tests.TestCaseWithTransport):
2687
def make_tt_with_versioned_dir(self):
2688
wt = self.make_branch_and_tree('.')
2689
self.build_tree(['dir/',])
2690
wt.add(['dir'], ['dir-id'])
2691
wt.commit('Create dir')
2692
tt = TreeTransform(wt)
2693
self.addCleanup(tt.finalize)
2696
def test_resolve_create_parent_for_versioned_file(self):
2697
wt, tt = self.make_tt_with_versioned_dir()
2698
dir_tid = tt.trans_id_tree_file_id('dir-id')
2699
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2700
tt.delete_contents(dir_tid)
2701
tt.unversion_file(dir_tid)
2702
conflicts = resolve_conflicts(tt)
2703
# one conflict for the missing directory, one for the unversioned
2705
self.assertLength(2, conflicts)
2707
def test_non_versioned_file_create_conflict(self):
2708
wt, tt = self.make_tt_with_versioned_dir()
2709
dir_tid = tt.trans_id_tree_file_id('dir-id')
2710
tt.new_file('file', dir_tid, 'Contents')
2711
tt.delete_contents(dir_tid)
2712
tt.unversion_file(dir_tid)
2713
conflicts = resolve_conflicts(tt)
2714
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2715
self.assertLength(1, conflicts)
2716
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2720
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2721
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2723
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2724
('', ''), ('directory', 'directory'), (False, False))
2727
class TestTransformPreview(tests.TestCaseWithTransport):
2729
def create_tree(self):
2730
tree = self.make_branch_and_tree('.')
2731
self.build_tree_contents([('a', 'content 1')])
2732
tree.set_root_id('TREE_ROOT')
2733
tree.add('a', 'a-id')
2734
tree.commit('rev1', rev_id='rev1')
2735
return tree.branch.repository.revision_tree('rev1')
2737
def get_empty_preview(self):
2738
repository = self.make_repository('repo')
2739
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2740
preview = TransformPreview(tree)
2741
self.addCleanup(preview.finalize)
2744
def test_transform_preview(self):
2745
revision_tree = self.create_tree()
2746
preview = TransformPreview(revision_tree)
2747
self.addCleanup(preview.finalize)
2749
def test_transform_preview_tree(self):
2750
revision_tree = self.create_tree()
2751
preview = TransformPreview(revision_tree)
2752
self.addCleanup(preview.finalize)
2753
preview.get_preview_tree()
2755
def test_transform_new_file(self):
2756
revision_tree = self.create_tree()
2757
preview = TransformPreview(revision_tree)
2758
self.addCleanup(preview.finalize)
2759
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2760
preview_tree = preview.get_preview_tree()
2761
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2763
preview_tree.get_file('file2-id').read(), 'content B\n')
2765
def test_diff_preview_tree(self):
2766
revision_tree = self.create_tree()
2767
preview = TransformPreview(revision_tree)
2768
self.addCleanup(preview.finalize)
2769
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2770
preview_tree = preview.get_preview_tree()
2772
show_diff_trees(revision_tree, preview_tree, out)
2773
lines = out.getvalue().splitlines()
2774
self.assertEqual(lines[0], "=== added file 'file2'")
2775
# 3 lines of diff administrivia
2776
self.assertEqual(lines[4], "+content B")
2778
def test_transform_conflicts(self):
2779
revision_tree = self.create_tree()
2780
preview = TransformPreview(revision_tree)
2781
self.addCleanup(preview.finalize)
2782
preview.new_file('a', preview.root, 'content 2')
2783
resolve_conflicts(preview)
2784
trans_id = preview.trans_id_file_id('a-id')
2785
self.assertEqual('a.moved', preview.final_name(trans_id))
2787
def get_tree_and_preview_tree(self):
2788
revision_tree = self.create_tree()
2789
preview = TransformPreview(revision_tree)
2790
self.addCleanup(preview.finalize)
2791
a_trans_id = preview.trans_id_file_id('a-id')
2792
preview.delete_contents(a_trans_id)
2793
preview.create_file('b content', a_trans_id)
2794
preview_tree = preview.get_preview_tree()
2795
return revision_tree, preview_tree
2797
def test_iter_changes(self):
2798
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2799
root = revision_tree.inventory.root.file_id
2800
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2801
(root, root), ('a', 'a'), ('file', 'file'),
2803
list(preview_tree.iter_changes(revision_tree)))
2805
def test_include_unchanged_succeeds(self):
2806
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2807
changes = preview_tree.iter_changes(revision_tree,
2808
include_unchanged=True)
2809
root = revision_tree.inventory.root.file_id
2811
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2813
def test_specific_files(self):
2814
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2815
changes = preview_tree.iter_changes(revision_tree,
2816
specific_files=[''])
2817
self.assertEqual([A_ENTRY], list(changes))
2819
def test_want_unversioned(self):
2820
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2821
changes = preview_tree.iter_changes(revision_tree,
2822
want_unversioned=True)
2823
self.assertEqual([A_ENTRY], list(changes))
2825
def test_ignore_extra_trees_no_specific_files(self):
2826
# extra_trees is harmless without specific_files, so we'll silently
2827
# accept it, even though we won't use it.
2828
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2829
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2831
def test_ignore_require_versioned_no_specific_files(self):
2832
# require_versioned is meaningless without specific_files.
2833
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2834
preview_tree.iter_changes(revision_tree, require_versioned=False)
2836
def test_ignore_pb(self):
2837
# pb could be supported, but TT.iter_changes doesn't support it.
2838
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2839
preview_tree.iter_changes(revision_tree)
2841
def test_kind(self):
2842
revision_tree = self.create_tree()
2843
preview = TransformPreview(revision_tree)
2844
self.addCleanup(preview.finalize)
2845
preview.new_file('file', preview.root, 'contents', 'file-id')
2846
preview.new_directory('directory', preview.root, 'dir-id')
2847
preview_tree = preview.get_preview_tree()
2848
self.assertEqual('file', preview_tree.kind('file-id'))
2849
self.assertEqual('directory', preview_tree.kind('dir-id'))
2851
def test_get_file_mtime(self):
2852
preview = self.get_empty_preview()
2853
file_trans_id = preview.new_file('file', preview.root, 'contents',
2855
limbo_path = preview._limbo_name(file_trans_id)
2856
preview_tree = preview.get_preview_tree()
2857
self.assertEqual(os.stat(limbo_path).st_mtime,
2858
preview_tree.get_file_mtime('file-id'))
2860
def test_get_file_mtime_renamed(self):
2861
work_tree = self.make_branch_and_tree('tree')
2862
self.build_tree(['tree/file'])
2863
work_tree.add('file', 'file-id')
2864
preview = TransformPreview(work_tree)
2865
self.addCleanup(preview.finalize)
2866
file_trans_id = preview.trans_id_tree_file_id('file-id')
2867
preview.adjust_path('renamed', preview.root, file_trans_id)
2868
preview_tree = preview.get_preview_tree()
2869
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2870
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2872
def test_get_file_size(self):
2873
work_tree = self.make_branch_and_tree('tree')
2874
self.build_tree_contents([('tree/old', 'old')])
2875
work_tree.add('old', 'old-id')
2876
preview = TransformPreview(work_tree)
2877
self.addCleanup(preview.finalize)
2878
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2880
tree = preview.get_preview_tree()
2881
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2882
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2884
def test_get_file(self):
2885
preview = self.get_empty_preview()
2886
preview.new_file('file', preview.root, 'contents', 'file-id')
2887
preview_tree = preview.get_preview_tree()
2888
tree_file = preview_tree.get_file('file-id')
2890
self.assertEqual('contents', tree_file.read())
2894
def test_get_symlink_target(self):
2895
self.requireFeature(SymlinkFeature)
2896
preview = self.get_empty_preview()
2897
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2898
preview_tree = preview.get_preview_tree()
2899
self.assertEqual('target',
2900
preview_tree.get_symlink_target('symlink-id'))
2902
def test_all_file_ids(self):
2903
tree = self.make_branch_and_tree('tree')
2904
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2905
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2906
preview = TransformPreview(tree)
2907
self.addCleanup(preview.finalize)
2908
preview.unversion_file(preview.trans_id_file_id('b-id'))
2909
c_trans_id = preview.trans_id_file_id('c-id')
2910
preview.unversion_file(c_trans_id)
2911
preview.version_file('c-id', c_trans_id)
2912
preview_tree = preview.get_preview_tree()
2913
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2914
preview_tree.all_file_ids())
2916
def test_path2id_deleted_unchanged(self):
2917
tree = self.make_branch_and_tree('tree')
2918
self.build_tree(['tree/unchanged', 'tree/deleted'])
2919
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2920
preview = TransformPreview(tree)
2921
self.addCleanup(preview.finalize)
2922
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2923
preview_tree = preview.get_preview_tree()
2924
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2925
self.assertIs(None, preview_tree.path2id('deleted'))
2927
def test_path2id_created(self):
2928
tree = self.make_branch_and_tree('tree')
2929
self.build_tree(['tree/unchanged'])
2930
tree.add(['unchanged'], ['unchanged-id'])
2931
preview = TransformPreview(tree)
2932
self.addCleanup(preview.finalize)
2933
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2934
'contents', 'new-id')
2935
preview_tree = preview.get_preview_tree()
2936
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2938
def test_path2id_moved(self):
2939
tree = self.make_branch_and_tree('tree')
2940
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2941
tree.add(['old_parent', 'old_parent/child'],
2942
['old_parent-id', 'child-id'])
2943
preview = TransformPreview(tree)
2944
self.addCleanup(preview.finalize)
2945
new_parent = preview.new_directory('new_parent', preview.root,
2947
preview.adjust_path('child', new_parent,
2948
preview.trans_id_file_id('child-id'))
2949
preview_tree = preview.get_preview_tree()
2950
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2951
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2953
def test_path2id_renamed_parent(self):
2954
tree = self.make_branch_and_tree('tree')
2955
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2956
tree.add(['old_name', 'old_name/child'],
2957
['parent-id', 'child-id'])
2958
preview = TransformPreview(tree)
2959
self.addCleanup(preview.finalize)
2960
preview.adjust_path('new_name', preview.root,
2961
preview.trans_id_file_id('parent-id'))
2962
preview_tree = preview.get_preview_tree()
2963
self.assertIs(None, preview_tree.path2id('old_name/child'))
2964
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2966
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2967
preview_tree = tt.get_preview_tree()
2968
preview_result = list(preview_tree.iter_entries_by_dir(
2972
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2973
self.assertEqual(actual_result, preview_result)
2975
def test_iter_entries_by_dir_new(self):
2976
tree = self.make_branch_and_tree('tree')
2977
tt = TreeTransform(tree)
2978
tt.new_file('new', tt.root, 'contents', 'new-id')
2979
self.assertMatchingIterEntries(tt)
2981
def test_iter_entries_by_dir_deleted(self):
2982
tree = self.make_branch_and_tree('tree')
2983
self.build_tree(['tree/deleted'])
2984
tree.add('deleted', 'deleted-id')
2985
tt = TreeTransform(tree)
2986
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2987
self.assertMatchingIterEntries(tt)
2989
def test_iter_entries_by_dir_unversioned(self):
2990
tree = self.make_branch_and_tree('tree')
2991
self.build_tree(['tree/removed'])
2992
tree.add('removed', 'removed-id')
2993
tt = TreeTransform(tree)
2994
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2995
self.assertMatchingIterEntries(tt)
2997
def test_iter_entries_by_dir_moved(self):
2998
tree = self.make_branch_and_tree('tree')
2999
self.build_tree(['tree/moved', 'tree/new_parent/'])
3000
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
3001
tt = TreeTransform(tree)
3002
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
3003
tt.trans_id_file_id('moved-id'))
3004
self.assertMatchingIterEntries(tt)
3006
def test_iter_entries_by_dir_specific_file_ids(self):
3007
tree = self.make_branch_and_tree('tree')
3008
tree.set_root_id('tree-root-id')
3009
self.build_tree(['tree/parent/', 'tree/parent/child'])
3010
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
3011
tt = TreeTransform(tree)
3012
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
3014
def test_symlink_content_summary(self):
3015
self.requireFeature(SymlinkFeature)
3016
preview = self.get_empty_preview()
3017
preview.new_symlink('path', preview.root, 'target', 'path-id')
3018
summary = preview.get_preview_tree().path_content_summary('path')
3019
self.assertEqual(('symlink', None, None, 'target'), summary)
3021
def test_missing_content_summary(self):
3022
preview = self.get_empty_preview()
3023
summary = preview.get_preview_tree().path_content_summary('path')
3024
self.assertEqual(('missing', None, None, None), summary)
3026
def test_deleted_content_summary(self):
3027
tree = self.make_branch_and_tree('tree')
3028
self.build_tree(['tree/path/'])
3030
preview = TransformPreview(tree)
3031
self.addCleanup(preview.finalize)
3032
preview.delete_contents(preview.trans_id_tree_path('path'))
3033
summary = preview.get_preview_tree().path_content_summary('path')
3034
self.assertEqual(('missing', None, None, None), summary)
3036
def test_file_content_summary_executable(self):
3037
preview = self.get_empty_preview()
3038
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3039
preview.set_executability(True, path_id)
3040
summary = preview.get_preview_tree().path_content_summary('path')
3041
self.assertEqual(4, len(summary))
3042
self.assertEqual('file', summary[0])
3043
# size must be known
3044
self.assertEqual(len('contents'), summary[1])
3046
self.assertEqual(True, summary[2])
3047
# will not have hash (not cheap to determine)
3048
self.assertIs(None, summary[3])
3050
def test_change_executability(self):
3051
tree = self.make_branch_and_tree('tree')
3052
self.build_tree(['tree/path'])
3054
preview = TransformPreview(tree)
3055
self.addCleanup(preview.finalize)
3056
path_id = preview.trans_id_tree_path('path')
3057
preview.set_executability(True, path_id)
3058
summary = preview.get_preview_tree().path_content_summary('path')
3059
self.assertEqual(True, summary[2])
3061
def test_file_content_summary_non_exec(self):
3062
preview = self.get_empty_preview()
3063
preview.new_file('path', preview.root, 'contents', 'path-id')
3064
summary = preview.get_preview_tree().path_content_summary('path')
3065
self.assertEqual(4, len(summary))
3066
self.assertEqual('file', summary[0])
3067
# size must be known
3068
self.assertEqual(len('contents'), summary[1])
3070
self.assertEqual(False, summary[2])
3071
# will not have hash (not cheap to determine)
3072
self.assertIs(None, summary[3])
3074
def test_dir_content_summary(self):
3075
preview = self.get_empty_preview()
3076
preview.new_directory('path', preview.root, 'path-id')
3077
summary = preview.get_preview_tree().path_content_summary('path')
3078
self.assertEqual(('directory', None, None, None), summary)
3080
def test_tree_content_summary(self):
3081
preview = self.get_empty_preview()
3082
path = preview.new_directory('path', preview.root, 'path-id')
3083
preview.set_tree_reference('rev-1', path)
3084
summary = preview.get_preview_tree().path_content_summary('path')
3085
self.assertEqual(4, len(summary))
3086
self.assertEqual('tree-reference', summary[0])
3088
def test_annotate(self):
3089
tree = self.make_branch_and_tree('tree')
3090
self.build_tree_contents([('tree/file', 'a\n')])
3091
tree.add('file', 'file-id')
3092
tree.commit('a', rev_id='one')
3093
self.build_tree_contents([('tree/file', 'a\nb\n')])
3094
preview = TransformPreview(tree)
3095
self.addCleanup(preview.finalize)
3096
file_trans_id = preview.trans_id_file_id('file-id')
3097
preview.delete_contents(file_trans_id)
3098
preview.create_file('a\nb\nc\n', file_trans_id)
3099
preview_tree = preview.get_preview_tree()
3105
annotation = preview_tree.annotate_iter('file-id', 'me:')
3106
self.assertEqual(expected, annotation)
3108
def test_annotate_missing(self):
3109
preview = self.get_empty_preview()
3110
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3111
preview_tree = preview.get_preview_tree()
3117
annotation = preview_tree.annotate_iter('file-id', 'me:')
3118
self.assertEqual(expected, annotation)
3120
def test_annotate_rename(self):
3121
tree = self.make_branch_and_tree('tree')
3122
self.build_tree_contents([('tree/file', 'a\n')])
3123
tree.add('file', 'file-id')
3124
tree.commit('a', rev_id='one')
3125
preview = TransformPreview(tree)
3126
self.addCleanup(preview.finalize)
3127
file_trans_id = preview.trans_id_file_id('file-id')
3128
preview.adjust_path('newname', preview.root, file_trans_id)
3129
preview_tree = preview.get_preview_tree()
3133
annotation = preview_tree.annotate_iter('file-id', 'me:')
3134
self.assertEqual(expected, annotation)
3136
def test_annotate_deleted(self):
3137
tree = self.make_branch_and_tree('tree')
3138
self.build_tree_contents([('tree/file', 'a\n')])
3139
tree.add('file', 'file-id')
3140
tree.commit('a', rev_id='one')
3141
self.build_tree_contents([('tree/file', 'a\nb\n')])
3142
preview = TransformPreview(tree)
3143
self.addCleanup(preview.finalize)
3144
file_trans_id = preview.trans_id_file_id('file-id')
3145
preview.delete_contents(file_trans_id)
3146
preview_tree = preview.get_preview_tree()
3147
annotation = preview_tree.annotate_iter('file-id', 'me:')
3148
self.assertIs(None, annotation)
3150
def test_stored_kind(self):
3151
preview = self.get_empty_preview()
3152
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3153
preview_tree = preview.get_preview_tree()
3154
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3156
def test_is_executable(self):
3157
preview = self.get_empty_preview()
3158
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3159
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3160
preview_tree = preview.get_preview_tree()
3161
self.assertEqual(True, preview_tree.is_executable('file-id'))
3163
def test_get_set_parent_ids(self):
3164
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3165
self.assertEqual([], preview_tree.get_parent_ids())
3166
preview_tree.set_parent_ids(['rev-1'])
3167
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3169
def test_plan_file_merge(self):
3170
work_a = self.make_branch_and_tree('wta')
3171
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3172
work_a.add('file', 'file-id')
3173
base_id = work_a.commit('base version')
3174
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3175
preview = TransformPreview(work_a)
3176
self.addCleanup(preview.finalize)
3177
trans_id = preview.trans_id_file_id('file-id')
3178
preview.delete_contents(trans_id)
3179
preview.create_file('b\nc\nd\ne\n', trans_id)
3180
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3181
tree_a = preview.get_preview_tree()
3182
tree_a.set_parent_ids([base_id])
3184
('killed-a', 'a\n'),
3185
('killed-b', 'b\n'),
3186
('unchanged', 'c\n'),
3187
('unchanged', 'd\n'),
3190
], list(tree_a.plan_file_merge('file-id', tree_b)))
3192
def test_plan_file_merge_revision_tree(self):
3193
work_a = self.make_branch_and_tree('wta')
3194
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3195
work_a.add('file', 'file-id')
3196
base_id = work_a.commit('base version')
3197
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3198
preview = TransformPreview(work_a.basis_tree())
3199
self.addCleanup(preview.finalize)
3200
trans_id = preview.trans_id_file_id('file-id')
3201
preview.delete_contents(trans_id)
3202
preview.create_file('b\nc\nd\ne\n', trans_id)
3203
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3204
tree_a = preview.get_preview_tree()
3205
tree_a.set_parent_ids([base_id])
3207
('killed-a', 'a\n'),
3208
('killed-b', 'b\n'),
3209
('unchanged', 'c\n'),
3210
('unchanged', 'd\n'),
3213
], list(tree_a.plan_file_merge('file-id', tree_b)))
3215
def test_walkdirs(self):
3216
preview = self.get_empty_preview()
3217
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3218
# FIXME: new_directory should mark root.
3219
preview.fixup_new_roots()
3220
preview_tree = preview.get_preview_tree()
3221
file_trans_id = preview.new_file('a', preview.root, 'contents',
3223
expected = [(('', 'tree-root'),
3224
[('a', 'a', 'file', None, 'a-id', 'file')])]
3225
self.assertEqual(expected, list(preview_tree.walkdirs()))
3227
def test_extras(self):
3228
work_tree = self.make_branch_and_tree('tree')
3229
self.build_tree(['tree/removed-file', 'tree/existing-file',
3230
'tree/not-removed-file'])
3231
work_tree.add(['removed-file', 'not-removed-file'])
3232
preview = TransformPreview(work_tree)
3233
self.addCleanup(preview.finalize)
3234
preview.new_file('new-file', preview.root, 'contents')
3235
preview.new_file('new-versioned-file', preview.root, 'contents',
3237
tree = preview.get_preview_tree()
3238
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3239
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3242
def test_merge_into_preview(self):
3243
work_tree = self.make_branch_and_tree('tree')
3244
self.build_tree_contents([('tree/file','b\n')])
3245
work_tree.add('file', 'file-id')
3246
work_tree.commit('first commit')
3247
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3248
self.build_tree_contents([('child/file','b\nc\n')])
3249
child_tree.commit('child commit')
3250
child_tree.lock_write()
3251
self.addCleanup(child_tree.unlock)
3252
work_tree.lock_write()
3253
self.addCleanup(work_tree.unlock)
3254
preview = TransformPreview(work_tree)
3255
self.addCleanup(preview.finalize)
3256
file_trans_id = preview.trans_id_file_id('file-id')
3257
preview.delete_contents(file_trans_id)
3258
preview.create_file('a\nb\n', file_trans_id)
3259
preview_tree = preview.get_preview_tree()
3260
merger = Merger.from_revision_ids(None, preview_tree,
3261
child_tree.branch.last_revision(),
3262
other_branch=child_tree.branch,
3263
tree_branch=work_tree.branch)
3264
merger.merge_type = Merge3Merger
3265
tt = merger.make_merger().make_preview_transform()
3266
self.addCleanup(tt.finalize)
3267
final_tree = tt.get_preview_tree()
3268
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3270
def test_merge_preview_into_workingtree(self):
3271
tree = self.make_branch_and_tree('tree')
3272
tree.set_root_id('TREE_ROOT')
3273
tt = TransformPreview(tree)
3274
self.addCleanup(tt.finalize)
3275
tt.new_file('name', tt.root, 'content', 'file-id')
3276
tree2 = self.make_branch_and_tree('tree2')
3277
tree2.set_root_id('TREE_ROOT')
3278
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3279
None, tree.basis_tree())
3280
merger.merge_type = Merge3Merger
3283
def test_merge_preview_into_workingtree_handles_conflicts(self):
3284
tree = self.make_branch_and_tree('tree')
3285
self.build_tree_contents([('tree/foo', 'bar')])
3286
tree.add('foo', 'foo-id')
3288
tt = TransformPreview(tree)
3289
self.addCleanup(tt.finalize)
3290
trans_id = tt.trans_id_file_id('foo-id')
3291
tt.delete_contents(trans_id)
3292
tt.create_file('baz', trans_id)
3293
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3294
self.build_tree_contents([('tree2/foo', 'qux')])
3296
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3297
pb, tree.basis_tree())
3298
merger.merge_type = Merge3Merger
3301
def test_has_filename(self):
3302
wt = self.make_branch_and_tree('tree')
3303
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3304
tt = TransformPreview(wt)
3305
removed_id = tt.trans_id_tree_path('removed')
3306
tt.delete_contents(removed_id)
3307
tt.new_file('new', tt.root, 'contents')
3308
modified_id = tt.trans_id_tree_path('modified')
3309
tt.delete_contents(modified_id)
3310
tt.create_file('modified-contents', modified_id)
3311
self.addCleanup(tt.finalize)
3312
tree = tt.get_preview_tree()
3313
self.assertTrue(tree.has_filename('unmodified'))
3314
self.assertFalse(tree.has_filename('not-present'))
3315
self.assertFalse(tree.has_filename('removed'))
3316
self.assertTrue(tree.has_filename('new'))
3317
self.assertTrue(tree.has_filename('modified'))
3319
def test_is_executable(self):
3320
tree = self.make_branch_and_tree('tree')
3321
preview = TransformPreview(tree)
3322
self.addCleanup(preview.finalize)
3323
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3324
preview_tree = preview.get_preview_tree()
3325
self.assertEqual(False, preview_tree.is_executable('baz-id',
3327
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3329
def test_commit_preview_tree(self):
3330
tree = self.make_branch_and_tree('tree')
3331
rev_id = tree.commit('rev1')
3332
tree.branch.lock_write()
3333
self.addCleanup(tree.branch.unlock)
3334
tt = TransformPreview(tree)
3335
tt.new_file('file', tt.root, 'contents', 'file_id')
3336
self.addCleanup(tt.finalize)
3337
preview = tt.get_preview_tree()
3338
preview.set_parent_ids([rev_id])
3339
builder = tree.branch.get_commit_builder([rev_id])
3340
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3341
builder.finish_inventory()
3342
rev2_id = builder.commit('rev2')
3343
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3344
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3346
def test_ascii_limbo_paths(self):
3347
self.requireFeature(features.UnicodeFilenameFeature)
3348
branch = self.make_branch('any')
3349
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3350
tt = TransformPreview(tree)
3351
self.addCleanup(tt.finalize)
3352
foo_id = tt.new_directory('', ROOT_PARENT)
3353
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3354
limbo_path = tt._limbo_name(bar_id)
3355
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3358
class FakeSerializer(object):
3359
"""Serializer implementation that simply returns the input.
3361
The input is returned in the order used by pack.ContainerPushParser.
3364
def bytes_record(bytes, names):
3368
class TestSerializeTransform(tests.TestCaseWithTransport):
3370
_test_needs_features = [features.UnicodeFilenameFeature]
3372
def get_preview(self, tree=None):
3374
tree = self.make_branch_and_tree('tree')
3375
tt = TransformPreview(tree)
3376
self.addCleanup(tt.finalize)
3379
def assertSerializesTo(self, expected, tt):
3380
records = list(tt.serialize(FakeSerializer()))
3381
self.assertEqual(expected, records)
3384
def default_attribs():
3389
'_new_executability': {},
3391
'_tree_path_ids': {'': 'new-0'},
3393
'_removed_contents': [],
3394
'_non_present_ids': {},
3397
def make_records(self, attribs, contents):
3399
(((('attribs'),),), bencode.bencode(attribs))]
3400
records.extend([(((n, k),), c) for n, k, c in contents])
3403
def creation_records(self):
3404
attribs = self.default_attribs()
3405
attribs['_id_number'] = 3
3406
attribs['_new_name'] = {
3407
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3408
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3409
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3410
attribs['_new_executability'] = {'new-1': 1}
3412
('new-1', 'file', 'i 1\nbar\n'),
3413
('new-2', 'directory', ''),
3415
return self.make_records(attribs, contents)
3417
def test_serialize_creation(self):
3418
tt = self.get_preview()
3419
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3420
tt.new_directory('qux', tt.root, 'quxx')
3421
self.assertSerializesTo(self.creation_records(), tt)
3423
def test_deserialize_creation(self):
3424
tt = self.get_preview()
3425
tt.deserialize(iter(self.creation_records()))
3426
self.assertEqual(3, tt._id_number)
3427
self.assertEqual({'new-1': u'foo\u1234',
3428
'new-2': 'qux'}, tt._new_name)
3429
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3430
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3431
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3432
self.assertEqual({'new-1': True}, tt._new_executability)
3433
self.assertEqual({'new-1': 'file',
3434
'new-2': 'directory'}, tt._new_contents)
3435
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3437
foo_content = foo_limbo.read()
3440
self.assertEqual('bar', foo_content)
3442
def symlink_creation_records(self):
3443
attribs = self.default_attribs()
3444
attribs['_id_number'] = 2
3445
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3446
attribs['_new_parent'] = {'new-1': 'new-0'}
3447
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3448
return self.make_records(attribs, contents)
3450
def test_serialize_symlink_creation(self):
3451
self.requireFeature(features.SymlinkFeature)
3452
tt = self.get_preview()
3453
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3454
self.assertSerializesTo(self.symlink_creation_records(), tt)
3456
def test_deserialize_symlink_creation(self):
3457
self.requireFeature(features.SymlinkFeature)
3458
tt = self.get_preview()
3459
tt.deserialize(iter(self.symlink_creation_records()))
3460
abspath = tt._limbo_name('new-1')
3461
foo_content = osutils.readlink(abspath)
3462
self.assertEqual(u'bar\u1234', foo_content)
3464
def make_destruction_preview(self):
3465
tree = self.make_branch_and_tree('.')
3466
self.build_tree([u'foo\u1234', 'bar'])
3467
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3468
return self.get_preview(tree)
3470
def destruction_records(self):
3471
attribs = self.default_attribs()
3472
attribs['_id_number'] = 3
3473
attribs['_removed_id'] = ['new-1']
3474
attribs['_removed_contents'] = ['new-2']
3475
attribs['_tree_path_ids'] = {
3477
u'foo\u1234'.encode('utf-8'): 'new-1',
3480
return self.make_records(attribs, [])
3482
def test_serialize_destruction(self):
3483
tt = self.make_destruction_preview()
3484
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3485
tt.unversion_file(foo_trans_id)
3486
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3487
tt.delete_contents(bar_trans_id)
3488
self.assertSerializesTo(self.destruction_records(), tt)
3490
def test_deserialize_destruction(self):
3491
tt = self.make_destruction_preview()
3492
tt.deserialize(iter(self.destruction_records()))
3493
self.assertEqual({u'foo\u1234': 'new-1',
3495
'': tt.root}, tt._tree_path_ids)
3496
self.assertEqual({'new-1': u'foo\u1234',
3498
tt.root: ''}, tt._tree_id_paths)
3499
self.assertEqual(set(['new-1']), tt._removed_id)
3500
self.assertEqual(set(['new-2']), tt._removed_contents)
3502
def missing_records(self):
3503
attribs = self.default_attribs()
3504
attribs['_id_number'] = 2
3505
attribs['_non_present_ids'] = {
3507
return self.make_records(attribs, [])
3509
def test_serialize_missing(self):
3510
tt = self.get_preview()
3511
boo_trans_id = tt.trans_id_file_id('boo')
3512
self.assertSerializesTo(self.missing_records(), tt)
3514
def test_deserialize_missing(self):
3515
tt = self.get_preview()
3516
tt.deserialize(iter(self.missing_records()))
3517
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3519
def make_modification_preview(self):
3520
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3521
LINES_TWO = 'z\nbb\nx\ndd\n'
3522
tree = self.make_branch_and_tree('tree')
3523
self.build_tree_contents([('tree/file', LINES_ONE)])
3524
tree.add('file', 'file-id')
3525
return self.get_preview(tree), LINES_TWO
3527
def modification_records(self):
3528
attribs = self.default_attribs()
3529
attribs['_id_number'] = 2
3530
attribs['_tree_path_ids'] = {
3533
attribs['_removed_contents'] = ['new-1']
3534
contents = [('new-1', 'file',
3535
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3536
return self.make_records(attribs, contents)
3538
def test_serialize_modification(self):
3539
tt, LINES = self.make_modification_preview()
3540
trans_id = tt.trans_id_file_id('file-id')
3541
tt.delete_contents(trans_id)
3542
tt.create_file(LINES, trans_id)
3543
self.assertSerializesTo(self.modification_records(), tt)
3545
def test_deserialize_modification(self):
3546
tt, LINES = self.make_modification_preview()
3547
tt.deserialize(iter(self.modification_records()))
3548
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3550
def make_kind_change_preview(self):
3551
LINES = 'a\nb\nc\nd\n'
3552
tree = self.make_branch_and_tree('tree')
3553
self.build_tree(['tree/foo/'])
3554
tree.add('foo', 'foo-id')
3555
return self.get_preview(tree), LINES
3557
def kind_change_records(self):
3558
attribs = self.default_attribs()
3559
attribs['_id_number'] = 2
3560
attribs['_tree_path_ids'] = {
3563
attribs['_removed_contents'] = ['new-1']
3564
contents = [('new-1', 'file',
3565
'i 4\na\nb\nc\nd\n\n')]
3566
return self.make_records(attribs, contents)
3568
def test_serialize_kind_change(self):
3569
tt, LINES = self.make_kind_change_preview()
3570
trans_id = tt.trans_id_file_id('foo-id')
3571
tt.delete_contents(trans_id)
3572
tt.create_file(LINES, trans_id)
3573
self.assertSerializesTo(self.kind_change_records(), tt)
3575
def test_deserialize_kind_change(self):
3576
tt, LINES = self.make_kind_change_preview()
3577
tt.deserialize(iter(self.kind_change_records()))
3578
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3580
def make_add_contents_preview(self):
3581
LINES = 'a\nb\nc\nd\n'
3582
tree = self.make_branch_and_tree('tree')
3583
self.build_tree(['tree/foo'])
3585
os.unlink('tree/foo')
3586
return self.get_preview(tree), LINES
3588
def add_contents_records(self):
3589
attribs = self.default_attribs()
3590
attribs['_id_number'] = 2
3591
attribs['_tree_path_ids'] = {
3594
contents = [('new-1', 'file',
3595
'i 4\na\nb\nc\nd\n\n')]
3596
return self.make_records(attribs, contents)
3598
def test_serialize_add_contents(self):
3599
tt, LINES = self.make_add_contents_preview()
3600
trans_id = tt.trans_id_tree_path('foo')
3601
tt.create_file(LINES, trans_id)
3602
self.assertSerializesTo(self.add_contents_records(), tt)
3604
def test_deserialize_add_contents(self):
3605
tt, LINES = self.make_add_contents_preview()
3606
tt.deserialize(iter(self.add_contents_records()))
3607
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3609
def test_get_parents_lines(self):
3610
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3611
LINES_TWO = 'z\nbb\nx\ndd\n'
3612
tree = self.make_branch_and_tree('tree')
3613
self.build_tree_contents([('tree/file', LINES_ONE)])
3614
tree.add('file', 'file-id')
3615
tt = self.get_preview(tree)
3616
trans_id = tt.trans_id_tree_path('file')
3617
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3618
tt._get_parents_lines(trans_id))
3620
def test_get_parents_texts(self):
3621
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3622
LINES_TWO = 'z\nbb\nx\ndd\n'
3623
tree = self.make_branch_and_tree('tree')
3624
self.build_tree_contents([('tree/file', LINES_ONE)])
3625
tree.add('file', 'file-id')
3626
tt = self.get_preview(tree)
3627
trans_id = tt.trans_id_tree_path('file')
3628
self.assertEqual((LINES_ONE,),
3629
tt._get_parents_texts(trans_id))
3632
class TestOrphan(tests.TestCaseWithTransport):
3634
def test_no_orphan_for_transform_preview(self):
3635
tree = self.make_branch_and_tree('tree')
3636
tt = transform.TransformPreview(tree)
3637
self.addCleanup(tt.finalize)
3638
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3640
def _set_orphan_policy(self, wt, policy):
3641
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3644
def _prepare_orphan(self, wt):
3645
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3646
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3647
wt.commit('add dir and file ignoring foo')
3648
tt = transform.TreeTransform(wt)
3649
self.addCleanup(tt.finalize)
3650
# dir and bar are deleted
3651
dir_tid = tt.trans_id_tree_path('dir')
3652
file_tid = tt.trans_id_tree_path('dir/file')
3653
orphan_tid = tt.trans_id_tree_path('dir/foo')
3654
tt.delete_contents(file_tid)
3655
tt.unversion_file(file_tid)
3656
tt.delete_contents(dir_tid)
3657
tt.unversion_file(dir_tid)
3658
# There should be a conflict because dir still contain foo
3659
raw_conflicts = tt.find_conflicts()
3660
self.assertLength(1, raw_conflicts)
3661
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3662
return tt, orphan_tid
3664
def test_new_orphan_created(self):
3665
wt = self.make_branch_and_tree('.')
3666
self._set_orphan_policy(wt, 'move')
3667
tt, orphan_tid = self._prepare_orphan(wt)
3670
warnings.append(args[0] % args[1:])
3671
self.overrideAttr(trace, 'warning', warning)
3672
remaining_conflicts = resolve_conflicts(tt)
3673
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3675
# Yeah for resolved conflicts !
3676
self.assertLength(0, remaining_conflicts)
3677
# We have a new orphan
3678
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3679
self.assertEquals('bzr-orphans',
3680
tt.final_name(tt.final_parent(orphan_tid)))
3682
def test_never_orphan(self):
3683
wt = self.make_branch_and_tree('.')
3684
self._set_orphan_policy(wt, 'conflict')
3685
tt, orphan_tid = self._prepare_orphan(wt)
3686
remaining_conflicts = resolve_conflicts(tt)
3687
self.assertLength(1, remaining_conflicts)
3688
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3689
remaining_conflicts.pop())
3691
def test_orphan_error(self):
3692
def bogus_orphan(tt, orphan_id, parent_id):
3693
raise transform.OrphaningError(tt.final_name(orphan_id),
3694
tt.final_name(parent_id))
3695
transform.orphaning_registry.register('bogus', bogus_orphan,
3696
'Raise an error when orphaning')
3697
wt = self.make_branch_and_tree('.')
3698
self._set_orphan_policy(wt, 'bogus')
3699
tt, orphan_tid = self._prepare_orphan(wt)
3700
remaining_conflicts = resolve_conflicts(tt)
3701
self.assertLength(1, remaining_conflicts)
3702
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3703
remaining_conflicts.pop())
3705
def test_unknown_orphan_policy(self):
3706
wt = self.make_branch_and_tree('.')
3707
# Set a fictional policy nobody ever implemented
3708
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3709
tt, orphan_tid = self._prepare_orphan(wt)
3712
warnings.append(args[0] % args[1:])
3713
self.overrideAttr(trace, 'warning', warning)
3714
remaining_conflicts = resolve_conflicts(tt)
3715
# We fallback to the default policy which create a conflict
3716
self.assertLength(1, remaining_conflicts)
3717
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3718
remaining_conflicts.pop())
3719
self.assertLength(1, warnings)
3720
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
3723
class TestTransformHooks(tests.TestCaseWithTransport):
3726
super(TestTransformHooks, self).setUp()
3727
self.wt = self.make_branch_and_tree('.')
3730
def get_transform(self):
3731
transform = TreeTransform(self.wt)
3732
self.addCleanup(transform.finalize)
3733
return transform, transform.root
3735
def test_pre_commit_hooks(self):
3737
def record_pre_transform(tree, tt):
3738
calls.append((tree, tt))
3739
MutableTree.hooks.install_named_hook('pre_transform',
3740
record_pre_transform, "Pre transform")
3741
transform, root = self.get_transform()
3742
old_root_id = transform.tree_file_id(root)
3744
self.assertEqual(old_root_id, self.wt.get_root_id())
3745
self.assertEquals([(self.wt, transform)], calls)
3747
def test_post_commit_hooks(self):
3749
def record_post_transform(tree, tt):
3750
calls.append((tree, tt))
3751
MutableTree.hooks.install_named_hook('post_transform',
3752
record_post_transform, "Post transform")
3753
transform, root = self.get_transform()
3754
old_root_id = transform.tree_file_id(root)
3756
self.assertEqual(old_root_id, self.wt.get_root_id())
3757
self.assertEquals([(self.wt, transform)], calls)