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='development-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.root_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.kind(wt.path2id("foo")), "directory")
1539
changes = wt.changes_from(wt.basis_tree())
1540
self.assertFalse(changes.has_changed(), changes)
1542
def test_file_to_symlink(self):
1543
self.requireFeature(SymlinkFeature)
1544
wt = self.make_branch_and_tree('.')
1545
self.build_tree(['foo'])
1548
tt = TreeTransform(wt)
1549
self.addCleanup(tt.finalize)
1550
foo_trans_id = tt.trans_id_tree_path("foo")
1551
tt.delete_contents(foo_trans_id)
1552
tt.create_symlink("bar", foo_trans_id)
1554
self.assertPathExists("foo")
1556
self.addCleanup(wt.unlock)
1557
self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
1559
def test_dir_to_file(self):
1560
wt = self.make_branch_and_tree('.')
1561
self.build_tree(['foo/', 'foo/bar'])
1562
wt.add(['foo', 'foo/bar'])
1564
tt = TreeTransform(wt)
1565
self.addCleanup(tt.finalize)
1566
foo_trans_id = tt.trans_id_tree_path("foo")
1567
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1568
tt.delete_contents(foo_trans_id)
1569
tt.delete_versioned(bar_trans_id)
1570
tt.create_file(["aa\n"], foo_trans_id)
1572
self.assertPathExists("foo")
1574
self.addCleanup(wt.unlock)
1575
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1577
def test_dir_to_hardlink(self):
1578
self.requireFeature(HardlinkFeature)
1579
wt = self.make_branch_and_tree('.')
1580
self.build_tree(['foo/', 'foo/bar'])
1581
wt.add(['foo', 'foo/bar'])
1583
tt = TreeTransform(wt)
1584
self.addCleanup(tt.finalize)
1585
foo_trans_id = tt.trans_id_tree_path("foo")
1586
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1587
tt.delete_contents(foo_trans_id)
1588
tt.delete_versioned(bar_trans_id)
1589
self.build_tree(['baz'])
1590
tt.create_hardlink("baz", foo_trans_id)
1592
self.assertPathExists("foo")
1593
self.assertPathExists("baz")
1595
self.addCleanup(wt.unlock)
1596
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1598
def test_no_final_path(self):
1599
transform, root = self.get_transform()
1600
trans_id = transform.trans_id_file_id('foo')
1601
transform.create_file('bar', trans_id)
1602
transform.cancel_creation(trans_id)
1605
def test_create_from_tree(self):
1606
tree1 = self.make_branch_and_tree('tree1')
1607
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1608
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1609
tree2 = self.make_branch_and_tree('tree2')
1610
tt = TreeTransform(tree2)
1611
foo_trans_id = tt.create_path('foo', tt.root)
1612
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1613
bar_trans_id = tt.create_path('bar', tt.root)
1614
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1616
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1617
self.assertFileEqual('baz', 'tree2/bar')
1619
def test_create_from_tree_bytes(self):
1620
"""Provided lines are used instead of tree content."""
1621
tree1 = self.make_branch_and_tree('tree1')
1622
self.build_tree_contents([('tree1/foo', 'bar'),])
1623
tree1.add('foo', 'foo-id')
1624
tree2 = self.make_branch_and_tree('tree2')
1625
tt = TreeTransform(tree2)
1626
foo_trans_id = tt.create_path('foo', tt.root)
1627
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1629
self.assertFileEqual('qux', 'tree2/foo')
1631
def test_create_from_tree_symlink(self):
1632
self.requireFeature(SymlinkFeature)
1633
tree1 = self.make_branch_and_tree('tree1')
1634
os.symlink('bar', 'tree1/foo')
1635
tree1.add('foo', 'foo-id')
1636
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1637
foo_trans_id = tt.create_path('foo', tt.root)
1638
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1640
self.assertEqual('bar', os.readlink('tree2/foo'))
1643
class TransformGroup(object):
1645
def __init__(self, dirname, root_id):
1648
self.wt = BzrDir.create_standalone_workingtree(dirname)
1649
self.wt.set_root_id(root_id)
1650
self.b = self.wt.branch
1651
self.tt = TreeTransform(self.wt)
1652
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1655
def conflict_text(tree, merge):
1656
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1657
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1660
class TestInventoryAltered(tests.TestCaseWithTransport):
1662
def test_inventory_altered_unchanged(self):
1663
tree = self.make_branch_and_tree('tree')
1664
self.build_tree(['tree/foo'])
1665
tree.add('foo', 'foo-id')
1666
with TransformPreview(tree) as tt:
1667
self.assertEqual([], tt._inventory_altered())
1669
def test_inventory_altered_changed_parent_id(self):
1670
tree = self.make_branch_and_tree('tree')
1671
self.build_tree(['tree/foo'])
1672
tree.add('foo', 'foo-id')
1673
with TransformPreview(tree) as tt:
1674
tt.unversion_file(tt.root)
1675
tt.version_file('new-id', tt.root)
1676
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1677
foo_tuple = ('foo', foo_trans_id)
1678
root_tuple = ('', tt.root)
1679
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1681
def test_inventory_altered_noop_changed_parent_id(self):
1682
tree = self.make_branch_and_tree('tree')
1683
self.build_tree(['tree/foo'])
1684
tree.add('foo', 'foo-id')
1685
with TransformPreview(tree) as tt:
1686
tt.unversion_file(tt.root)
1687
tt.version_file(tree.get_root_id(), tt.root)
1688
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1689
self.assertEqual([], tt._inventory_altered())
1692
class TestTransformMerge(TestCaseInTempDir):
1694
def test_text_merge(self):
1695
root_id = generate_ids.gen_root_id()
1696
base = TransformGroup("base", root_id)
1697
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1698
base.tt.new_file('b', base.root, 'b1', 'b')
1699
base.tt.new_file('c', base.root, 'c', 'c')
1700
base.tt.new_file('d', base.root, 'd', 'd')
1701
base.tt.new_file('e', base.root, 'e', 'e')
1702
base.tt.new_file('f', base.root, 'f', 'f')
1703
base.tt.new_directory('g', base.root, 'g')
1704
base.tt.new_directory('h', base.root, 'h')
1706
other = TransformGroup("other", root_id)
1707
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1708
other.tt.new_file('b', other.root, 'b2', 'b')
1709
other.tt.new_file('c', other.root, 'c2', 'c')
1710
other.tt.new_file('d', other.root, 'd', 'd')
1711
other.tt.new_file('e', other.root, 'e2', 'e')
1712
other.tt.new_file('f', other.root, 'f', 'f')
1713
other.tt.new_file('g', other.root, 'g', 'g')
1714
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1715
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1717
this = TransformGroup("this", root_id)
1718
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1719
this.tt.new_file('b', this.root, 'b', 'b')
1720
this.tt.new_file('c', this.root, 'c', 'c')
1721
this.tt.new_file('d', this.root, 'd2', 'd')
1722
this.tt.new_file('e', this.root, 'e2', 'e')
1723
this.tt.new_file('f', this.root, 'f', 'f')
1724
this.tt.new_file('g', this.root, 'g', 'g')
1725
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1726
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1728
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1731
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1732
# three-way text conflict
1733
self.assertEqual(this.wt.get_file('b').read(),
1734
conflict_text('b', 'b2'))
1736
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1738
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1739
# Ambigious clean merge
1740
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1742
self.assertEqual(this.wt.get_file('f').read(), 'f')
1743
# Correct correct results when THIS == OTHER
1744
self.assertEqual(this.wt.get_file('g').read(), 'g')
1745
# Text conflict when THIS & OTHER are text and BASE is dir
1746
self.assertEqual(this.wt.get_file('h').read(),
1747
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1748
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1750
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1752
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1753
self.assertEqual(this.wt.get_file('i').read(),
1754
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1755
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1757
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1759
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1760
modified = ['a', 'b', 'c', 'h', 'i']
1761
merge_modified = this.wt.merge_modified()
1762
self.assertSubset(merge_modified, modified)
1763
self.assertEqual(len(merge_modified), len(modified))
1764
with file(this.wt.id2abspath('a'), 'wb') as f: f.write('booga')
1766
merge_modified = this.wt.merge_modified()
1767
self.assertSubset(merge_modified, modified)
1768
self.assertEqual(len(merge_modified), len(modified))
1772
def test_file_merge(self):
1773
self.requireFeature(SymlinkFeature)
1774
root_id = generate_ids.gen_root_id()
1775
base = TransformGroup("BASE", root_id)
1776
this = TransformGroup("THIS", root_id)
1777
other = TransformGroup("OTHER", root_id)
1778
for tg in this, base, other:
1779
tg.tt.new_directory('a', tg.root, 'a')
1780
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1781
tg.tt.new_file('c', tg.root, 'c', 'c')
1782
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1783
targets = ((base, 'base-e', 'base-f', None, None),
1784
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1785
(other, 'other-e', None, 'other-g', 'other-h'))
1786
for tg, e_target, f_target, g_target, h_target in targets:
1787
for link, target in (('e', e_target), ('f', f_target),
1788
('g', g_target), ('h', h_target)):
1789
if target is not None:
1790
tg.tt.new_symlink(link, tg.root, target, link)
1792
for tg in this, base, other:
1794
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1795
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1796
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1797
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1798
for suffix in ('THIS', 'BASE', 'OTHER'):
1799
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1800
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1801
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1802
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1803
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1804
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1805
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1806
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1807
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1808
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1809
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1810
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1811
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1812
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1814
def test_filename_merge(self):
1815
root_id = generate_ids.gen_root_id()
1816
base = TransformGroup("BASE", root_id)
1817
this = TransformGroup("THIS", root_id)
1818
other = TransformGroup("OTHER", root_id)
1819
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1820
for t in [base, this, other]]
1821
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1822
for t in [base, this, other]]
1823
base.tt.new_directory('c', base_a, 'c')
1824
this.tt.new_directory('c1', this_a, 'c')
1825
other.tt.new_directory('c', other_b, 'c')
1827
base.tt.new_directory('d', base_a, 'd')
1828
this.tt.new_directory('d1', this_b, 'd')
1829
other.tt.new_directory('d', other_a, 'd')
1831
base.tt.new_directory('e', base_a, 'e')
1832
this.tt.new_directory('e', this_a, 'e')
1833
other.tt.new_directory('e1', other_b, 'e')
1835
base.tt.new_directory('f', base_a, 'f')
1836
this.tt.new_directory('f1', this_b, 'f')
1837
other.tt.new_directory('f1', other_b, 'f')
1839
for tg in [this, base, other]:
1841
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1842
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1843
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1844
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1845
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1847
def test_filename_merge_conflicts(self):
1848
root_id = generate_ids.gen_root_id()
1849
base = TransformGroup("BASE", root_id)
1850
this = TransformGroup("THIS", root_id)
1851
other = TransformGroup("OTHER", root_id)
1852
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1853
for t in [base, this, other]]
1854
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1855
for t in [base, this, other]]
1857
base.tt.new_file('g', base_a, 'g', 'g')
1858
other.tt.new_file('g1', other_b, 'g1', 'g')
1860
base.tt.new_file('h', base_a, 'h', 'h')
1861
this.tt.new_file('h1', this_b, 'h1', 'h')
1863
base.tt.new_file('i', base.root, 'i', 'i')
1864
other.tt.new_directory('i1', this_b, 'i')
1866
for tg in [this, base, other]:
1868
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1870
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1871
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1872
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1873
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1874
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1875
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1876
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1879
class TestBuildTree(tests.TestCaseWithTransport):
1881
def test_build_tree_with_symlinks(self):
1882
self.requireFeature(SymlinkFeature)
1884
a = BzrDir.create_standalone_workingtree('a')
1886
with file('a/foo/bar', 'wb') as f: f.write('contents')
1887
os.symlink('a/foo/bar', 'a/foo/baz')
1888
a.add(['foo', 'foo/bar', 'foo/baz'])
1889
a.commit('initial commit')
1890
b = BzrDir.create_standalone_workingtree('b')
1891
basis = a.basis_tree()
1893
self.addCleanup(basis.unlock)
1894
build_tree(basis, b)
1895
self.assertIs(os.path.isdir('b/foo'), True)
1896
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1897
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1899
def test_build_with_references(self):
1900
tree = self.make_branch_and_tree('source',
1901
format='development-subtree')
1902
subtree = self.make_branch_and_tree('source/subtree',
1903
format='development-subtree')
1904
tree.add_reference(subtree)
1905
tree.commit('a revision')
1906
tree.branch.create_checkout('target')
1907
self.assertPathExists('target')
1908
self.assertPathExists('target/subtree')
1910
def test_file_conflict_handling(self):
1911
"""Ensure that when building trees, conflict handling is done"""
1912
source = self.make_branch_and_tree('source')
1913
target = self.make_branch_and_tree('target')
1914
self.build_tree(['source/file', 'target/file'])
1915
source.add('file', 'new-file')
1916
source.commit('added file')
1917
build_tree(source.basis_tree(), target)
1918
self.assertEqual([DuplicateEntry('Moved existing file to',
1919
'file.moved', 'file', None, 'new-file')],
1921
target2 = self.make_branch_and_tree('target2')
1922
target_file = file('target2/file', 'wb')
1924
source_file = file('source/file', 'rb')
1926
target_file.write(source_file.read())
1931
build_tree(source.basis_tree(), target2)
1932
self.assertEqual([], target2.conflicts())
1934
def test_symlink_conflict_handling(self):
1935
"""Ensure that when building trees, conflict handling is done"""
1936
self.requireFeature(SymlinkFeature)
1937
source = self.make_branch_and_tree('source')
1938
os.symlink('foo', 'source/symlink')
1939
source.add('symlink', 'new-symlink')
1940
source.commit('added file')
1941
target = self.make_branch_and_tree('target')
1942
os.symlink('bar', 'target/symlink')
1943
build_tree(source.basis_tree(), target)
1944
self.assertEqual([DuplicateEntry('Moved existing file to',
1945
'symlink.moved', 'symlink', None, 'new-symlink')],
1947
target = self.make_branch_and_tree('target2')
1948
os.symlink('foo', 'target2/symlink')
1949
build_tree(source.basis_tree(), target)
1950
self.assertEqual([], target.conflicts())
1952
def test_directory_conflict_handling(self):
1953
"""Ensure that when building trees, conflict handling is done"""
1954
source = self.make_branch_and_tree('source')
1955
target = self.make_branch_and_tree('target')
1956
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1957
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1958
source.commit('added file')
1959
build_tree(source.basis_tree(), target)
1960
self.assertEqual([], target.conflicts())
1961
self.assertPathExists('target/dir1/file')
1963
# Ensure contents are merged
1964
target = self.make_branch_and_tree('target2')
1965
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1966
build_tree(source.basis_tree(), target)
1967
self.assertEqual([], target.conflicts())
1968
self.assertPathExists('target2/dir1/file2')
1969
self.assertPathExists('target2/dir1/file')
1971
# Ensure new contents are suppressed for existing branches
1972
target = self.make_branch_and_tree('target3')
1973
self.make_branch('target3/dir1')
1974
self.build_tree(['target3/dir1/file2'])
1975
build_tree(source.basis_tree(), target)
1976
self.assertPathDoesNotExist('target3/dir1/file')
1977
self.assertPathExists('target3/dir1/file2')
1978
self.assertPathExists('target3/dir1.diverted/file')
1979
self.assertEqual([DuplicateEntry('Diverted to',
1980
'dir1.diverted', 'dir1', 'new-dir1', None)],
1983
target = self.make_branch_and_tree('target4')
1984
self.build_tree(['target4/dir1/'])
1985
self.make_branch('target4/dir1/file')
1986
build_tree(source.basis_tree(), target)
1987
self.assertPathExists('target4/dir1/file')
1988
self.assertEqual('directory', file_kind('target4/dir1/file'))
1989
self.assertPathExists('target4/dir1/file.diverted')
1990
self.assertEqual([DuplicateEntry('Diverted to',
1991
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1994
def test_mixed_conflict_handling(self):
1995
"""Ensure that when building trees, conflict handling is done"""
1996
source = self.make_branch_and_tree('source')
1997
target = self.make_branch_and_tree('target')
1998
self.build_tree(['source/name', 'target/name/'])
1999
source.add('name', 'new-name')
2000
source.commit('added file')
2001
build_tree(source.basis_tree(), target)
2002
self.assertEqual([DuplicateEntry('Moved existing file to',
2003
'name.moved', 'name', None, 'new-name')], target.conflicts())
2005
def test_raises_in_populated(self):
2006
source = self.make_branch_and_tree('source')
2007
self.build_tree(['source/name'])
2009
source.commit('added name')
2010
target = self.make_branch_and_tree('target')
2011
self.build_tree(['target/name'])
2013
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2014
build_tree, source.basis_tree(), target)
2016
def test_build_tree_rename_count(self):
2017
source = self.make_branch_and_tree('source')
2018
self.build_tree(['source/file1', 'source/dir1/'])
2019
source.add(['file1', 'dir1'])
2020
source.commit('add1')
2021
target1 = self.make_branch_and_tree('target1')
2022
transform_result = build_tree(source.basis_tree(), target1)
2023
self.assertEqual(2, transform_result.rename_count)
2025
self.build_tree(['source/dir1/file2'])
2026
source.add(['dir1/file2'])
2027
source.commit('add3')
2028
target2 = self.make_branch_and_tree('target2')
2029
transform_result = build_tree(source.basis_tree(), target2)
2030
# children of non-root directories should not be renamed
2031
self.assertEqual(2, transform_result.rename_count)
2033
def create_ab_tree(self):
2034
"""Create a committed test tree with two files"""
2035
source = self.make_branch_and_tree('source')
2036
self.build_tree_contents([('source/file1', 'A')])
2037
self.build_tree_contents([('source/file2', 'B')])
2038
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2039
source.commit('commit files')
2041
self.addCleanup(source.unlock)
2044
def test_build_tree_accelerator_tree(self):
2045
source = self.create_ab_tree()
2046
self.build_tree_contents([('source/file2', 'C')])
2048
real_source_get_file = source.get_file
2049
def get_file(file_id, path=None):
2050
calls.append(file_id)
2051
return real_source_get_file(file_id, path)
2052
source.get_file = get_file
2053
target = self.make_branch_and_tree('target')
2054
revision_tree = source.basis_tree()
2055
revision_tree.lock_read()
2056
self.addCleanup(revision_tree.unlock)
2057
build_tree(revision_tree, target, source)
2058
self.assertEqual(['file1-id'], calls)
2060
self.addCleanup(target.unlock)
2061
self.assertEqual([], list(target.iter_changes(revision_tree)))
2063
def test_build_tree_accelerator_tree_observes_sha1(self):
2064
source = self.create_ab_tree()
2065
sha1 = osutils.sha_string('A')
2066
target = self.make_branch_and_tree('target')
2068
self.addCleanup(target.unlock)
2069
state = target.current_dirstate()
2070
state._cutoff_time = time.time() + 60
2071
build_tree(source.basis_tree(), target, source)
2072
entry = state._get_entry(0, path_utf8='file1')
2073
self.assertEqual(sha1, entry[1][0][1])
2075
def test_build_tree_accelerator_tree_missing_file(self):
2076
source = self.create_ab_tree()
2077
os.unlink('source/file1')
2078
source.remove(['file2'])
2079
target = self.make_branch_and_tree('target')
2080
revision_tree = source.basis_tree()
2081
revision_tree.lock_read()
2082
self.addCleanup(revision_tree.unlock)
2083
build_tree(revision_tree, target, source)
2085
self.addCleanup(target.unlock)
2086
self.assertEqual([], list(target.iter_changes(revision_tree)))
2088
def test_build_tree_accelerator_wrong_kind(self):
2089
self.requireFeature(SymlinkFeature)
2090
source = self.make_branch_and_tree('source')
2091
self.build_tree_contents([('source/file1', '')])
2092
self.build_tree_contents([('source/file2', '')])
2093
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2094
source.commit('commit files')
2095
os.unlink('source/file2')
2096
self.build_tree_contents([('source/file2/', 'C')])
2097
os.unlink('source/file1')
2098
os.symlink('file2', 'source/file1')
2100
real_source_get_file = source.get_file
2101
def get_file(file_id, path=None):
2102
calls.append(file_id)
2103
return real_source_get_file(file_id, path)
2104
source.get_file = get_file
2105
target = self.make_branch_and_tree('target')
2106
revision_tree = source.basis_tree()
2107
revision_tree.lock_read()
2108
self.addCleanup(revision_tree.unlock)
2109
build_tree(revision_tree, target, source)
2110
self.assertEqual([], calls)
2112
self.addCleanup(target.unlock)
2113
self.assertEqual([], list(target.iter_changes(revision_tree)))
2115
def test_build_tree_hardlink(self):
2116
self.requireFeature(HardlinkFeature)
2117
source = self.create_ab_tree()
2118
target = self.make_branch_and_tree('target')
2119
revision_tree = source.basis_tree()
2120
revision_tree.lock_read()
2121
self.addCleanup(revision_tree.unlock)
2122
build_tree(revision_tree, target, source, hardlink=True)
2124
self.addCleanup(target.unlock)
2125
self.assertEqual([], list(target.iter_changes(revision_tree)))
2126
source_stat = os.stat('source/file1')
2127
target_stat = os.stat('target/file1')
2128
self.assertEqual(source_stat, target_stat)
2130
# Explicitly disallowing hardlinks should prevent them.
2131
target2 = self.make_branch_and_tree('target2')
2132
build_tree(revision_tree, target2, source, hardlink=False)
2134
self.addCleanup(target2.unlock)
2135
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2136
source_stat = os.stat('source/file1')
2137
target2_stat = os.stat('target2/file1')
2138
self.assertNotEqual(source_stat, target2_stat)
2140
def test_build_tree_accelerator_tree_moved(self):
2141
source = self.make_branch_and_tree('source')
2142
self.build_tree_contents([('source/file1', 'A')])
2143
source.add(['file1'], ['file1-id'])
2144
source.commit('commit files')
2145
source.rename_one('file1', 'file2')
2147
self.addCleanup(source.unlock)
2148
target = self.make_branch_and_tree('target')
2149
revision_tree = source.basis_tree()
2150
revision_tree.lock_read()
2151
self.addCleanup(revision_tree.unlock)
2152
build_tree(revision_tree, target, source)
2154
self.addCleanup(target.unlock)
2155
self.assertEqual([], list(target.iter_changes(revision_tree)))
2157
def test_build_tree_hardlinks_preserve_execute(self):
2158
self.requireFeature(HardlinkFeature)
2159
source = self.create_ab_tree()
2160
tt = TreeTransform(source)
2161
trans_id = tt.trans_id_tree_file_id('file1-id')
2162
tt.set_executability(True, trans_id)
2164
self.assertTrue(source.is_executable('file1-id'))
2165
target = self.make_branch_and_tree('target')
2166
revision_tree = source.basis_tree()
2167
revision_tree.lock_read()
2168
self.addCleanup(revision_tree.unlock)
2169
build_tree(revision_tree, target, source, hardlink=True)
2171
self.addCleanup(target.unlock)
2172
self.assertEqual([], list(target.iter_changes(revision_tree)))
2173
self.assertTrue(source.is_executable('file1-id'))
2175
def install_rot13_content_filter(self, pattern):
2177
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2178
# below, but that looks a bit... hard to read even if it's exactly
2180
original_registry = filters._reset_registry()
2181
def restore_registry():
2182
filters._reset_registry(original_registry)
2183
self.addCleanup(restore_registry)
2184
def rot13(chunks, context=None):
2185
return [''.join(chunks).encode('rot13')]
2186
rot13filter = filters.ContentFilter(rot13, rot13)
2187
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2188
os.mkdir(self.test_home_dir + '/.bazaar')
2189
rules_filename = self.test_home_dir + '/.bazaar/rules'
2190
f = open(rules_filename, 'wb')
2191
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2193
def uninstall_rules():
2194
os.remove(rules_filename)
2196
self.addCleanup(uninstall_rules)
2199
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2200
"""build_tree will not hardlink files that have content filtering rules
2201
applied to them (but will still hardlink other files from the same tree
2204
self.requireFeature(HardlinkFeature)
2205
self.install_rot13_content_filter('file1')
2206
source = self.create_ab_tree()
2207
target = self.make_branch_and_tree('target')
2208
revision_tree = source.basis_tree()
2209
revision_tree.lock_read()
2210
self.addCleanup(revision_tree.unlock)
2211
build_tree(revision_tree, target, source, hardlink=True)
2213
self.addCleanup(target.unlock)
2214
self.assertEqual([], list(target.iter_changes(revision_tree)))
2215
source_stat = os.stat('source/file1')
2216
target_stat = os.stat('target/file1')
2217
self.assertNotEqual(source_stat, target_stat)
2218
source_stat = os.stat('source/file2')
2219
target_stat = os.stat('target/file2')
2220
self.assertEqualStat(source_stat, target_stat)
2222
def test_case_insensitive_build_tree_inventory(self):
2223
if (features.CaseInsensitiveFilesystemFeature.available()
2224
or features.CaseInsCasePresFilenameFeature.available()):
2225
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2226
source = self.make_branch_and_tree('source')
2227
self.build_tree(['source/file', 'source/FILE'])
2228
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2229
source.commit('added files')
2230
# Don't try this at home, kids!
2231
# Force the tree to report that it is case insensitive
2232
target = self.make_branch_and_tree('target')
2233
target.case_sensitive = False
2234
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2235
self.assertEqual('file.moved', target.id2path('lower-id'))
2236
self.assertEqual('FILE', target.id2path('upper-id'))
2238
def test_build_tree_observes_sha(self):
2239
source = self.make_branch_and_tree('source')
2240
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2241
source.add(['file1', 'dir', 'dir/file2'],
2242
['file1-id', 'dir-id', 'file2-id'])
2243
source.commit('new files')
2244
target = self.make_branch_and_tree('target')
2246
self.addCleanup(target.unlock)
2247
# We make use of the fact that DirState caches its cutoff time. So we
2248
# set the 'safe' time to one minute in the future.
2249
state = target.current_dirstate()
2250
state._cutoff_time = time.time() + 60
2251
build_tree(source.basis_tree(), target)
2252
entry1_sha = osutils.sha_file_by_name('source/file1')
2253
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2254
# entry[1] is the state information, entry[1][0] is the state of the
2255
# working tree, entry[1][0][1] is the sha value for the current working
2257
entry1 = state._get_entry(0, path_utf8='file1')
2258
self.assertEqual(entry1_sha, entry1[1][0][1])
2259
# The 'size' field must also be set.
2260
self.assertEqual(25, entry1[1][0][2])
2261
entry1_state = entry1[1][0]
2262
entry2 = state._get_entry(0, path_utf8='dir/file2')
2263
self.assertEqual(entry2_sha, entry2[1][0][1])
2264
self.assertEqual(29, entry2[1][0][2])
2265
entry2_state = entry2[1][0]
2266
# Now, make sure that we don't have to re-read the content. The
2267
# packed_stat should match exactly.
2268
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2269
self.assertEqual(entry2_sha,
2270
target.get_file_sha1('file2-id', 'dir/file2'))
2271
self.assertEqual(entry1_state, entry1[1][0])
2272
self.assertEqual(entry2_state, entry2[1][0])
2275
class TestCommitTransform(tests.TestCaseWithTransport):
2277
def get_branch(self):
2278
tree = self.make_branch_and_tree('tree')
2280
self.addCleanup(tree.unlock)
2281
tree.commit('empty commit')
2284
def get_branch_and_transform(self):
2285
branch = self.get_branch()
2286
tt = TransformPreview(branch.basis_tree())
2287
self.addCleanup(tt.finalize)
2290
def test_commit_wrong_basis(self):
2291
branch = self.get_branch()
2292
basis = branch.repository.revision_tree(
2293
_mod_revision.NULL_REVISION)
2294
tt = TransformPreview(basis)
2295
self.addCleanup(tt.finalize)
2296
e = self.assertRaises(ValueError, tt.commit, branch, '')
2297
self.assertEqual('TreeTransform not based on branch basis: null:',
2300
def test_empy_commit(self):
2301
branch, tt = self.get_branch_and_transform()
2302
rev = tt.commit(branch, 'my message')
2303
self.assertEqual(2, branch.revno())
2304
repo = branch.repository
2305
self.assertEqual('my message', repo.get_revision(rev).message)
2307
def test_merge_parents(self):
2308
branch, tt = self.get_branch_and_transform()
2309
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2310
self.assertEqual(['rev1b', 'rev1c'],
2311
branch.basis_tree().get_parent_ids()[1:])
2313
def test_first_commit(self):
2314
branch = self.make_branch('branch')
2316
self.addCleanup(branch.unlock)
2317
tt = TransformPreview(branch.basis_tree())
2318
self.addCleanup(tt.finalize)
2319
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2320
rev = tt.commit(branch, 'my message')
2321
self.assertEqual([], branch.basis_tree().get_parent_ids())
2322
self.assertNotEqual(_mod_revision.NULL_REVISION,
2323
branch.last_revision())
2325
def test_first_commit_with_merge_parents(self):
2326
branch = self.make_branch('branch')
2328
self.addCleanup(branch.unlock)
2329
tt = TransformPreview(branch.basis_tree())
2330
self.addCleanup(tt.finalize)
2331
e = self.assertRaises(ValueError, tt.commit, branch,
2332
'my message', ['rev1b-id'])
2333
self.assertEqual('Cannot supply merge parents for first commit.',
2335
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2337
def test_add_files(self):
2338
branch, tt = self.get_branch_and_transform()
2339
tt.new_file('file', tt.root, 'contents', 'file-id')
2340
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2341
if SymlinkFeature.available():
2342
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2343
rev = tt.commit(branch, 'message')
2344
tree = branch.basis_tree()
2345
self.assertEqual('file', tree.id2path('file-id'))
2346
self.assertEqual('contents', tree.get_file_text('file-id'))
2347
self.assertEqual('dir', tree.id2path('dir-id'))
2348
if SymlinkFeature.available():
2349
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2350
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2352
def test_add_unversioned(self):
2353
branch, tt = self.get_branch_and_transform()
2354
tt.new_file('file', tt.root, 'contents')
2355
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2356
'message', strict=True)
2358
def test_modify_strict(self):
2359
branch, tt = self.get_branch_and_transform()
2360
tt.new_file('file', tt.root, 'contents', 'file-id')
2361
tt.commit(branch, 'message', strict=True)
2362
tt = TransformPreview(branch.basis_tree())
2363
self.addCleanup(tt.finalize)
2364
trans_id = tt.trans_id_file_id('file-id')
2365
tt.delete_contents(trans_id)
2366
tt.create_file('contents', trans_id)
2367
tt.commit(branch, 'message', strict=True)
2369
def test_commit_malformed(self):
2370
"""Committing a malformed transform should raise an exception.
2372
In this case, we are adding a file without adding its parent.
2374
branch, tt = self.get_branch_and_transform()
2375
parent_id = tt.trans_id_file_id('parent-id')
2376
tt.new_file('file', parent_id, 'contents', 'file-id')
2377
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2380
def test_commit_rich_revision_data(self):
2381
branch, tt = self.get_branch_and_transform()
2382
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2383
committer='me <me@example.com>',
2384
revprops={'foo': 'bar'}, revision_id='revid-1',
2385
authors=['Author1 <author1@example.com>',
2386
'Author2 <author2@example.com>',
2388
self.assertEqual('revid-1', rev_id)
2389
revision = branch.repository.get_revision(rev_id)
2390
self.assertEqual(1, revision.timestamp)
2391
self.assertEqual(43201, revision.timezone)
2392
self.assertEqual('me <me@example.com>', revision.committer)
2393
self.assertEqual(['Author1 <author1@example.com>',
2394
'Author2 <author2@example.com>'],
2395
revision.get_apparent_authors())
2396
del revision.properties['authors']
2397
self.assertEqual({'foo': 'bar',
2398
'branch-nick': 'tree'},
2399
revision.properties)
2401
def test_no_explicit_revprops(self):
2402
branch, tt = self.get_branch_and_transform()
2403
rev_id = tt.commit(branch, 'message', authors=[
2404
'Author1 <author1@example.com>',
2405
'Author2 <author2@example.com>', ])
2406
revision = branch.repository.get_revision(rev_id)
2407
self.assertEqual(['Author1 <author1@example.com>',
2408
'Author2 <author2@example.com>'],
2409
revision.get_apparent_authors())
2410
self.assertEqual('tree', revision.properties['branch-nick'])
2413
class TestBackupName(tests.TestCase):
2415
def test_deprecations(self):
2416
class MockTransform(object):
2418
def has_named_child(self, by_parent, parent_id, name):
2419
return name in by_parent.get(parent_id, [])
2421
class MockEntry(object):
2424
object.__init__(self)
2427
tt = MockTransform()
2428
name1 = self.applyDeprecated(
2429
symbol_versioning.deprecated_in((2, 3, 0)),
2430
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2431
self.assertEqual('name.~1~', name1)
2432
name2 = self.applyDeprecated(
2433
symbol_versioning.deprecated_in((2, 3, 0)),
2434
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2435
self.assertEqual('name.~2~', name2)
2438
class TestFileMover(tests.TestCaseWithTransport):
2440
def test_file_mover(self):
2441
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2442
mover = _FileMover()
2443
mover.rename('a', 'q')
2444
self.assertPathExists('q')
2445
self.assertPathDoesNotExist('a')
2446
self.assertPathExists('q/b')
2447
self.assertPathExists('c')
2448
self.assertPathExists('c/d')
2450
def test_pre_delete_rollback(self):
2451
self.build_tree(['a/'])
2452
mover = _FileMover()
2453
mover.pre_delete('a', 'q')
2454
self.assertPathExists('q')
2455
self.assertPathDoesNotExist('a')
2457
self.assertPathDoesNotExist('q')
2458
self.assertPathExists('a')
2460
def test_apply_deletions(self):
2461
self.build_tree(['a/', 'b/'])
2462
mover = _FileMover()
2463
mover.pre_delete('a', 'q')
2464
mover.pre_delete('b', 'r')
2465
self.assertPathExists('q')
2466
self.assertPathExists('r')
2467
self.assertPathDoesNotExist('a')
2468
self.assertPathDoesNotExist('b')
2469
mover.apply_deletions()
2470
self.assertPathDoesNotExist('q')
2471
self.assertPathDoesNotExist('r')
2472
self.assertPathDoesNotExist('a')
2473
self.assertPathDoesNotExist('b')
2475
def test_file_mover_rollback(self):
2476
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2477
mover = _FileMover()
2478
mover.rename('c/d', 'c/f')
2479
mover.rename('c/e', 'c/d')
2481
mover.rename('a', 'c')
2482
except errors.FileExists, e:
2484
self.assertPathExists('a')
2485
self.assertPathExists('c/d')
2488
class Bogus(Exception):
2492
class TestTransformRollback(tests.TestCaseWithTransport):
2494
class ExceptionFileMover(_FileMover):
2496
def __init__(self, bad_source=None, bad_target=None):
2497
_FileMover.__init__(self)
2498
self.bad_source = bad_source
2499
self.bad_target = bad_target
2501
def rename(self, source, target):
2502
if (self.bad_source is not None and
2503
source.endswith(self.bad_source)):
2505
elif (self.bad_target is not None and
2506
target.endswith(self.bad_target)):
2509
_FileMover.rename(self, source, target)
2511
def test_rollback_rename(self):
2512
tree = self.make_branch_and_tree('.')
2513
self.build_tree(['a/', 'a/b'])
2514
tt = TreeTransform(tree)
2515
self.addCleanup(tt.finalize)
2516
a_id = tt.trans_id_tree_path('a')
2517
tt.adjust_path('c', tt.root, a_id)
2518
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2519
self.assertRaises(Bogus, tt.apply,
2520
_mover=self.ExceptionFileMover(bad_source='a'))
2521
self.assertPathExists('a')
2522
self.assertPathExists('a/b')
2524
self.assertPathExists('c')
2525
self.assertPathExists('c/d')
2527
def test_rollback_rename_into_place(self):
2528
tree = self.make_branch_and_tree('.')
2529
self.build_tree(['a/', 'a/b'])
2530
tt = TreeTransform(tree)
2531
self.addCleanup(tt.finalize)
2532
a_id = tt.trans_id_tree_path('a')
2533
tt.adjust_path('c', tt.root, a_id)
2534
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2535
self.assertRaises(Bogus, tt.apply,
2536
_mover=self.ExceptionFileMover(bad_target='c/d'))
2537
self.assertPathExists('a')
2538
self.assertPathExists('a/b')
2540
self.assertPathExists('c')
2541
self.assertPathExists('c/d')
2543
def test_rollback_deletion(self):
2544
tree = self.make_branch_and_tree('.')
2545
self.build_tree(['a/', 'a/b'])
2546
tt = TreeTransform(tree)
2547
self.addCleanup(tt.finalize)
2548
a_id = tt.trans_id_tree_path('a')
2549
tt.delete_contents(a_id)
2550
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2551
self.assertRaises(Bogus, tt.apply,
2552
_mover=self.ExceptionFileMover(bad_target='d'))
2553
self.assertPathExists('a')
2554
self.assertPathExists('a/b')
2557
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2558
"""Ensure treetransform creation errors can be safely cleaned up after"""
2560
def _override_globals_in_method(self, instance, method_name, globals):
2561
"""Replace method on instance with one with updated globals"""
2563
func = getattr(instance, method_name).im_func
2564
new_globals = dict(func.func_globals)
2565
new_globals.update(globals)
2566
new_func = types.FunctionType(func.func_code, new_globals,
2567
func.func_name, func.func_defaults)
2568
setattr(instance, method_name,
2569
types.MethodType(new_func, instance, instance.__class__))
2570
self.addCleanup(delattr, instance, method_name)
2573
def _fake_open_raises_before(name, mode):
2574
"""Like open() but raises before doing anything"""
2578
def _fake_open_raises_after(name, mode):
2579
"""Like open() but raises after creating file without returning"""
2580
open(name, mode).close()
2583
def create_transform_and_root_trans_id(self):
2584
"""Setup a transform creating a file in limbo"""
2585
tree = self.make_branch_and_tree('.')
2586
tt = TreeTransform(tree)
2587
return tt, tt.create_path("a", tt.root)
2589
def create_transform_and_subdir_trans_id(self):
2590
"""Setup a transform creating a directory containing a file in limbo"""
2591
tree = self.make_branch_and_tree('.')
2592
tt = TreeTransform(tree)
2593
d_trans_id = tt.create_path("d", tt.root)
2594
tt.create_directory(d_trans_id)
2595
f_trans_id = tt.create_path("a", d_trans_id)
2596
tt.adjust_path("a", d_trans_id, f_trans_id)
2597
return tt, f_trans_id
2599
def test_root_create_file_open_raises_before_creation(self):
2600
tt, trans_id = self.create_transform_and_root_trans_id()
2601
self._override_globals_in_method(tt, "create_file",
2602
{"open": self._fake_open_raises_before})
2603
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2604
path = tt._limbo_name(trans_id)
2605
self.assertPathDoesNotExist(path)
2607
self.assertPathDoesNotExist(tt._limbodir)
2609
def test_root_create_file_open_raises_after_creation(self):
2610
tt, trans_id = self.create_transform_and_root_trans_id()
2611
self._override_globals_in_method(tt, "create_file",
2612
{"open": self._fake_open_raises_after})
2613
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2614
path = tt._limbo_name(trans_id)
2615
self.assertPathExists(path)
2617
self.assertPathDoesNotExist(path)
2618
self.assertPathDoesNotExist(tt._limbodir)
2620
def test_subdir_create_file_open_raises_before_creation(self):
2621
tt, trans_id = self.create_transform_and_subdir_trans_id()
2622
self._override_globals_in_method(tt, "create_file",
2623
{"open": self._fake_open_raises_before})
2624
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2625
path = tt._limbo_name(trans_id)
2626
self.assertPathDoesNotExist(path)
2628
self.assertPathDoesNotExist(tt._limbodir)
2630
def test_subdir_create_file_open_raises_after_creation(self):
2631
tt, trans_id = self.create_transform_and_subdir_trans_id()
2632
self._override_globals_in_method(tt, "create_file",
2633
{"open": self._fake_open_raises_after})
2634
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2635
path = tt._limbo_name(trans_id)
2636
self.assertPathExists(path)
2638
self.assertPathDoesNotExist(path)
2639
self.assertPathDoesNotExist(tt._limbodir)
2641
def test_rename_in_limbo_rename_raises_after_rename(self):
2642
tt, trans_id = self.create_transform_and_root_trans_id()
2643
parent1 = tt.new_directory('parent1', tt.root)
2644
child1 = tt.new_file('child1', parent1, 'contents')
2645
parent2 = tt.new_directory('parent2', tt.root)
2647
class FakeOSModule(object):
2648
def rename(self, old, new):
2651
self._override_globals_in_method(tt, "_rename_in_limbo",
2652
{"os": FakeOSModule()})
2654
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2655
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2656
self.assertPathExists(path)
2658
self.assertPathDoesNotExist(path)
2659
self.assertPathDoesNotExist(tt._limbodir)
2661
def test_rename_in_limbo_rename_raises_before_rename(self):
2662
tt, trans_id = self.create_transform_and_root_trans_id()
2663
parent1 = tt.new_directory('parent1', tt.root)
2664
child1 = tt.new_file('child1', parent1, 'contents')
2665
parent2 = tt.new_directory('parent2', tt.root)
2667
class FakeOSModule(object):
2668
def rename(self, old, new):
2670
self._override_globals_in_method(tt, "_rename_in_limbo",
2671
{"os": FakeOSModule()})
2673
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2674
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2675
self.assertPathExists(path)
2677
self.assertPathDoesNotExist(path)
2678
self.assertPathDoesNotExist(tt._limbodir)
2681
class TestTransformMissingParent(tests.TestCaseWithTransport):
2683
def make_tt_with_versioned_dir(self):
2684
wt = self.make_branch_and_tree('.')
2685
self.build_tree(['dir/',])
2686
wt.add(['dir'], ['dir-id'])
2687
wt.commit('Create dir')
2688
tt = TreeTransform(wt)
2689
self.addCleanup(tt.finalize)
2692
def test_resolve_create_parent_for_versioned_file(self):
2693
wt, tt = self.make_tt_with_versioned_dir()
2694
dir_tid = tt.trans_id_tree_file_id('dir-id')
2695
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2696
tt.delete_contents(dir_tid)
2697
tt.unversion_file(dir_tid)
2698
conflicts = resolve_conflicts(tt)
2699
# one conflict for the missing directory, one for the unversioned
2701
self.assertLength(2, conflicts)
2703
def test_non_versioned_file_create_conflict(self):
2704
wt, tt = self.make_tt_with_versioned_dir()
2705
dir_tid = tt.trans_id_tree_file_id('dir-id')
2706
tt.new_file('file', dir_tid, 'Contents')
2707
tt.delete_contents(dir_tid)
2708
tt.unversion_file(dir_tid)
2709
conflicts = resolve_conflicts(tt)
2710
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2711
self.assertLength(1, conflicts)
2712
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2716
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2717
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2719
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2720
('', ''), ('directory', 'directory'), (False, False))
2723
class TestTransformPreview(tests.TestCaseWithTransport):
2725
def create_tree(self):
2726
tree = self.make_branch_and_tree('.')
2727
self.build_tree_contents([('a', 'content 1')])
2728
tree.set_root_id('TREE_ROOT')
2729
tree.add('a', 'a-id')
2730
tree.commit('rev1', rev_id='rev1')
2731
return tree.branch.repository.revision_tree('rev1')
2733
def get_empty_preview(self):
2734
repository = self.make_repository('repo')
2735
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2736
preview = TransformPreview(tree)
2737
self.addCleanup(preview.finalize)
2740
def test_transform_preview(self):
2741
revision_tree = self.create_tree()
2742
preview = TransformPreview(revision_tree)
2743
self.addCleanup(preview.finalize)
2745
def test_transform_preview_tree(self):
2746
revision_tree = self.create_tree()
2747
preview = TransformPreview(revision_tree)
2748
self.addCleanup(preview.finalize)
2749
preview.get_preview_tree()
2751
def test_transform_new_file(self):
2752
revision_tree = self.create_tree()
2753
preview = TransformPreview(revision_tree)
2754
self.addCleanup(preview.finalize)
2755
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2756
preview_tree = preview.get_preview_tree()
2757
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2759
preview_tree.get_file('file2-id').read(), 'content B\n')
2761
def test_diff_preview_tree(self):
2762
revision_tree = self.create_tree()
2763
preview = TransformPreview(revision_tree)
2764
self.addCleanup(preview.finalize)
2765
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2766
preview_tree = preview.get_preview_tree()
2768
show_diff_trees(revision_tree, preview_tree, out)
2769
lines = out.getvalue().splitlines()
2770
self.assertEqual(lines[0], "=== added file 'file2'")
2771
# 3 lines of diff administrivia
2772
self.assertEqual(lines[4], "+content B")
2774
def test_transform_conflicts(self):
2775
revision_tree = self.create_tree()
2776
preview = TransformPreview(revision_tree)
2777
self.addCleanup(preview.finalize)
2778
preview.new_file('a', preview.root, 'content 2')
2779
resolve_conflicts(preview)
2780
trans_id = preview.trans_id_file_id('a-id')
2781
self.assertEqual('a.moved', preview.final_name(trans_id))
2783
def get_tree_and_preview_tree(self):
2784
revision_tree = self.create_tree()
2785
preview = TransformPreview(revision_tree)
2786
self.addCleanup(preview.finalize)
2787
a_trans_id = preview.trans_id_file_id('a-id')
2788
preview.delete_contents(a_trans_id)
2789
preview.create_file('b content', a_trans_id)
2790
preview_tree = preview.get_preview_tree()
2791
return revision_tree, preview_tree
2793
def test_iter_changes(self):
2794
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2795
root = revision_tree.get_root_id()
2796
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2797
(root, root), ('a', 'a'), ('file', 'file'),
2799
list(preview_tree.iter_changes(revision_tree)))
2801
def test_include_unchanged_succeeds(self):
2802
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2803
changes = preview_tree.iter_changes(revision_tree,
2804
include_unchanged=True)
2805
root = revision_tree.get_root_id()
2807
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2809
def test_specific_files(self):
2810
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2811
changes = preview_tree.iter_changes(revision_tree,
2812
specific_files=[''])
2813
self.assertEqual([A_ENTRY], list(changes))
2815
def test_want_unversioned(self):
2816
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2817
changes = preview_tree.iter_changes(revision_tree,
2818
want_unversioned=True)
2819
self.assertEqual([A_ENTRY], list(changes))
2821
def test_ignore_extra_trees_no_specific_files(self):
2822
# extra_trees is harmless without specific_files, so we'll silently
2823
# accept it, even though we won't use it.
2824
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2825
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2827
def test_ignore_require_versioned_no_specific_files(self):
2828
# require_versioned is meaningless without specific_files.
2829
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2830
preview_tree.iter_changes(revision_tree, require_versioned=False)
2832
def test_ignore_pb(self):
2833
# pb could be supported, but TT.iter_changes doesn't support it.
2834
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2835
preview_tree.iter_changes(revision_tree)
2837
def test_kind(self):
2838
revision_tree = self.create_tree()
2839
preview = TransformPreview(revision_tree)
2840
self.addCleanup(preview.finalize)
2841
preview.new_file('file', preview.root, 'contents', 'file-id')
2842
preview.new_directory('directory', preview.root, 'dir-id')
2843
preview_tree = preview.get_preview_tree()
2844
self.assertEqual('file', preview_tree.kind('file-id'))
2845
self.assertEqual('directory', preview_tree.kind('dir-id'))
2847
def test_get_file_mtime(self):
2848
preview = self.get_empty_preview()
2849
file_trans_id = preview.new_file('file', preview.root, 'contents',
2851
limbo_path = preview._limbo_name(file_trans_id)
2852
preview_tree = preview.get_preview_tree()
2853
self.assertEqual(os.stat(limbo_path).st_mtime,
2854
preview_tree.get_file_mtime('file-id'))
2856
def test_get_file_mtime_renamed(self):
2857
work_tree = self.make_branch_and_tree('tree')
2858
self.build_tree(['tree/file'])
2859
work_tree.add('file', 'file-id')
2860
preview = TransformPreview(work_tree)
2861
self.addCleanup(preview.finalize)
2862
file_trans_id = preview.trans_id_tree_file_id('file-id')
2863
preview.adjust_path('renamed', preview.root, file_trans_id)
2864
preview_tree = preview.get_preview_tree()
2865
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2866
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2868
def test_get_file_size(self):
2869
work_tree = self.make_branch_and_tree('tree')
2870
self.build_tree_contents([('tree/old', 'old')])
2871
work_tree.add('old', 'old-id')
2872
preview = TransformPreview(work_tree)
2873
self.addCleanup(preview.finalize)
2874
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2876
tree = preview.get_preview_tree()
2877
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2878
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2880
def test_get_file(self):
2881
preview = self.get_empty_preview()
2882
preview.new_file('file', preview.root, 'contents', 'file-id')
2883
preview_tree = preview.get_preview_tree()
2884
tree_file = preview_tree.get_file('file-id')
2886
self.assertEqual('contents', tree_file.read())
2890
def test_get_symlink_target(self):
2891
self.requireFeature(SymlinkFeature)
2892
preview = self.get_empty_preview()
2893
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2894
preview_tree = preview.get_preview_tree()
2895
self.assertEqual('target',
2896
preview_tree.get_symlink_target('symlink-id'))
2898
def test_all_file_ids(self):
2899
tree = self.make_branch_and_tree('tree')
2900
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2901
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2902
preview = TransformPreview(tree)
2903
self.addCleanup(preview.finalize)
2904
preview.unversion_file(preview.trans_id_file_id('b-id'))
2905
c_trans_id = preview.trans_id_file_id('c-id')
2906
preview.unversion_file(c_trans_id)
2907
preview.version_file('c-id', c_trans_id)
2908
preview_tree = preview.get_preview_tree()
2909
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2910
preview_tree.all_file_ids())
2912
def test_path2id_deleted_unchanged(self):
2913
tree = self.make_branch_and_tree('tree')
2914
self.build_tree(['tree/unchanged', 'tree/deleted'])
2915
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2916
preview = TransformPreview(tree)
2917
self.addCleanup(preview.finalize)
2918
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2919
preview_tree = preview.get_preview_tree()
2920
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2921
self.assertIs(None, preview_tree.path2id('deleted'))
2923
def test_path2id_created(self):
2924
tree = self.make_branch_and_tree('tree')
2925
self.build_tree(['tree/unchanged'])
2926
tree.add(['unchanged'], ['unchanged-id'])
2927
preview = TransformPreview(tree)
2928
self.addCleanup(preview.finalize)
2929
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2930
'contents', 'new-id')
2931
preview_tree = preview.get_preview_tree()
2932
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2934
def test_path2id_moved(self):
2935
tree = self.make_branch_and_tree('tree')
2936
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2937
tree.add(['old_parent', 'old_parent/child'],
2938
['old_parent-id', 'child-id'])
2939
preview = TransformPreview(tree)
2940
self.addCleanup(preview.finalize)
2941
new_parent = preview.new_directory('new_parent', preview.root,
2943
preview.adjust_path('child', new_parent,
2944
preview.trans_id_file_id('child-id'))
2945
preview_tree = preview.get_preview_tree()
2946
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2947
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2949
def test_path2id_renamed_parent(self):
2950
tree = self.make_branch_and_tree('tree')
2951
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2952
tree.add(['old_name', 'old_name/child'],
2953
['parent-id', 'child-id'])
2954
preview = TransformPreview(tree)
2955
self.addCleanup(preview.finalize)
2956
preview.adjust_path('new_name', preview.root,
2957
preview.trans_id_file_id('parent-id'))
2958
preview_tree = preview.get_preview_tree()
2959
self.assertIs(None, preview_tree.path2id('old_name/child'))
2960
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2962
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2963
preview_tree = tt.get_preview_tree()
2964
preview_result = list(preview_tree.iter_entries_by_dir(
2968
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2969
self.assertEqual(actual_result, preview_result)
2971
def test_iter_entries_by_dir_new(self):
2972
tree = self.make_branch_and_tree('tree')
2973
tt = TreeTransform(tree)
2974
tt.new_file('new', tt.root, 'contents', 'new-id')
2975
self.assertMatchingIterEntries(tt)
2977
def test_iter_entries_by_dir_deleted(self):
2978
tree = self.make_branch_and_tree('tree')
2979
self.build_tree(['tree/deleted'])
2980
tree.add('deleted', 'deleted-id')
2981
tt = TreeTransform(tree)
2982
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2983
self.assertMatchingIterEntries(tt)
2985
def test_iter_entries_by_dir_unversioned(self):
2986
tree = self.make_branch_and_tree('tree')
2987
self.build_tree(['tree/removed'])
2988
tree.add('removed', 'removed-id')
2989
tt = TreeTransform(tree)
2990
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2991
self.assertMatchingIterEntries(tt)
2993
def test_iter_entries_by_dir_moved(self):
2994
tree = self.make_branch_and_tree('tree')
2995
self.build_tree(['tree/moved', 'tree/new_parent/'])
2996
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2997
tt = TreeTransform(tree)
2998
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2999
tt.trans_id_file_id('moved-id'))
3000
self.assertMatchingIterEntries(tt)
3002
def test_iter_entries_by_dir_specific_file_ids(self):
3003
tree = self.make_branch_and_tree('tree')
3004
tree.set_root_id('tree-root-id')
3005
self.build_tree(['tree/parent/', 'tree/parent/child'])
3006
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
3007
tt = TreeTransform(tree)
3008
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
3010
def test_symlink_content_summary(self):
3011
self.requireFeature(SymlinkFeature)
3012
preview = self.get_empty_preview()
3013
preview.new_symlink('path', preview.root, 'target', 'path-id')
3014
summary = preview.get_preview_tree().path_content_summary('path')
3015
self.assertEqual(('symlink', None, None, 'target'), summary)
3017
def test_missing_content_summary(self):
3018
preview = self.get_empty_preview()
3019
summary = preview.get_preview_tree().path_content_summary('path')
3020
self.assertEqual(('missing', None, None, None), summary)
3022
def test_deleted_content_summary(self):
3023
tree = self.make_branch_and_tree('tree')
3024
self.build_tree(['tree/path/'])
3026
preview = TransformPreview(tree)
3027
self.addCleanup(preview.finalize)
3028
preview.delete_contents(preview.trans_id_tree_path('path'))
3029
summary = preview.get_preview_tree().path_content_summary('path')
3030
self.assertEqual(('missing', None, None, None), summary)
3032
def test_file_content_summary_executable(self):
3033
preview = self.get_empty_preview()
3034
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3035
preview.set_executability(True, path_id)
3036
summary = preview.get_preview_tree().path_content_summary('path')
3037
self.assertEqual(4, len(summary))
3038
self.assertEqual('file', summary[0])
3039
# size must be known
3040
self.assertEqual(len('contents'), summary[1])
3042
self.assertEqual(True, summary[2])
3043
# will not have hash (not cheap to determine)
3044
self.assertIs(None, summary[3])
3046
def test_change_executability(self):
3047
tree = self.make_branch_and_tree('tree')
3048
self.build_tree(['tree/path'])
3050
preview = TransformPreview(tree)
3051
self.addCleanup(preview.finalize)
3052
path_id = preview.trans_id_tree_path('path')
3053
preview.set_executability(True, path_id)
3054
summary = preview.get_preview_tree().path_content_summary('path')
3055
self.assertEqual(True, summary[2])
3057
def test_file_content_summary_non_exec(self):
3058
preview = self.get_empty_preview()
3059
preview.new_file('path', preview.root, 'contents', 'path-id')
3060
summary = preview.get_preview_tree().path_content_summary('path')
3061
self.assertEqual(4, len(summary))
3062
self.assertEqual('file', summary[0])
3063
# size must be known
3064
self.assertEqual(len('contents'), summary[1])
3066
self.assertEqual(False, summary[2])
3067
# will not have hash (not cheap to determine)
3068
self.assertIs(None, summary[3])
3070
def test_dir_content_summary(self):
3071
preview = self.get_empty_preview()
3072
preview.new_directory('path', preview.root, 'path-id')
3073
summary = preview.get_preview_tree().path_content_summary('path')
3074
self.assertEqual(('directory', None, None, None), summary)
3076
def test_tree_content_summary(self):
3077
preview = self.get_empty_preview()
3078
path = preview.new_directory('path', preview.root, 'path-id')
3079
preview.set_tree_reference('rev-1', path)
3080
summary = preview.get_preview_tree().path_content_summary('path')
3081
self.assertEqual(4, len(summary))
3082
self.assertEqual('tree-reference', summary[0])
3084
def test_annotate(self):
3085
tree = self.make_branch_and_tree('tree')
3086
self.build_tree_contents([('tree/file', 'a\n')])
3087
tree.add('file', 'file-id')
3088
tree.commit('a', rev_id='one')
3089
self.build_tree_contents([('tree/file', 'a\nb\n')])
3090
preview = TransformPreview(tree)
3091
self.addCleanup(preview.finalize)
3092
file_trans_id = preview.trans_id_file_id('file-id')
3093
preview.delete_contents(file_trans_id)
3094
preview.create_file('a\nb\nc\n', file_trans_id)
3095
preview_tree = preview.get_preview_tree()
3101
annotation = preview_tree.annotate_iter('file-id', 'me:')
3102
self.assertEqual(expected, annotation)
3104
def test_annotate_missing(self):
3105
preview = self.get_empty_preview()
3106
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3107
preview_tree = preview.get_preview_tree()
3113
annotation = preview_tree.annotate_iter('file-id', 'me:')
3114
self.assertEqual(expected, annotation)
3116
def test_annotate_rename(self):
3117
tree = self.make_branch_and_tree('tree')
3118
self.build_tree_contents([('tree/file', 'a\n')])
3119
tree.add('file', 'file-id')
3120
tree.commit('a', rev_id='one')
3121
preview = TransformPreview(tree)
3122
self.addCleanup(preview.finalize)
3123
file_trans_id = preview.trans_id_file_id('file-id')
3124
preview.adjust_path('newname', preview.root, file_trans_id)
3125
preview_tree = preview.get_preview_tree()
3129
annotation = preview_tree.annotate_iter('file-id', 'me:')
3130
self.assertEqual(expected, annotation)
3132
def test_annotate_deleted(self):
3133
tree = self.make_branch_and_tree('tree')
3134
self.build_tree_contents([('tree/file', 'a\n')])
3135
tree.add('file', 'file-id')
3136
tree.commit('a', rev_id='one')
3137
self.build_tree_contents([('tree/file', 'a\nb\n')])
3138
preview = TransformPreview(tree)
3139
self.addCleanup(preview.finalize)
3140
file_trans_id = preview.trans_id_file_id('file-id')
3141
preview.delete_contents(file_trans_id)
3142
preview_tree = preview.get_preview_tree()
3143
annotation = preview_tree.annotate_iter('file-id', 'me:')
3144
self.assertIs(None, annotation)
3146
def test_stored_kind(self):
3147
preview = self.get_empty_preview()
3148
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3149
preview_tree = preview.get_preview_tree()
3150
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3152
def test_is_executable(self):
3153
preview = self.get_empty_preview()
3154
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3155
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3156
preview_tree = preview.get_preview_tree()
3157
self.assertEqual(True, preview_tree.is_executable('file-id'))
3159
def test_get_set_parent_ids(self):
3160
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3161
self.assertEqual([], preview_tree.get_parent_ids())
3162
preview_tree.set_parent_ids(['rev-1'])
3163
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3165
def test_plan_file_merge(self):
3166
work_a = self.make_branch_and_tree('wta')
3167
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3168
work_a.add('file', 'file-id')
3169
base_id = work_a.commit('base version')
3170
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3171
preview = TransformPreview(work_a)
3172
self.addCleanup(preview.finalize)
3173
trans_id = preview.trans_id_file_id('file-id')
3174
preview.delete_contents(trans_id)
3175
preview.create_file('b\nc\nd\ne\n', trans_id)
3176
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3177
tree_a = preview.get_preview_tree()
3178
tree_a.set_parent_ids([base_id])
3180
('killed-a', 'a\n'),
3181
('killed-b', 'b\n'),
3182
('unchanged', 'c\n'),
3183
('unchanged', 'd\n'),
3186
], list(tree_a.plan_file_merge('file-id', tree_b)))
3188
def test_plan_file_merge_revision_tree(self):
3189
work_a = self.make_branch_and_tree('wta')
3190
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3191
work_a.add('file', 'file-id')
3192
base_id = work_a.commit('base version')
3193
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3194
preview = TransformPreview(work_a.basis_tree())
3195
self.addCleanup(preview.finalize)
3196
trans_id = preview.trans_id_file_id('file-id')
3197
preview.delete_contents(trans_id)
3198
preview.create_file('b\nc\nd\ne\n', trans_id)
3199
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3200
tree_a = preview.get_preview_tree()
3201
tree_a.set_parent_ids([base_id])
3203
('killed-a', 'a\n'),
3204
('killed-b', 'b\n'),
3205
('unchanged', 'c\n'),
3206
('unchanged', 'd\n'),
3209
], list(tree_a.plan_file_merge('file-id', tree_b)))
3211
def test_walkdirs(self):
3212
preview = self.get_empty_preview()
3213
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3214
# FIXME: new_directory should mark root.
3215
preview.fixup_new_roots()
3216
preview_tree = preview.get_preview_tree()
3217
file_trans_id = preview.new_file('a', preview.root, 'contents',
3219
expected = [(('', 'tree-root'),
3220
[('a', 'a', 'file', None, 'a-id', 'file')])]
3221
self.assertEqual(expected, list(preview_tree.walkdirs()))
3223
def test_extras(self):
3224
work_tree = self.make_branch_and_tree('tree')
3225
self.build_tree(['tree/removed-file', 'tree/existing-file',
3226
'tree/not-removed-file'])
3227
work_tree.add(['removed-file', 'not-removed-file'])
3228
preview = TransformPreview(work_tree)
3229
self.addCleanup(preview.finalize)
3230
preview.new_file('new-file', preview.root, 'contents')
3231
preview.new_file('new-versioned-file', preview.root, 'contents',
3233
tree = preview.get_preview_tree()
3234
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3235
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3238
def test_merge_into_preview(self):
3239
work_tree = self.make_branch_and_tree('tree')
3240
self.build_tree_contents([('tree/file','b\n')])
3241
work_tree.add('file', 'file-id')
3242
work_tree.commit('first commit')
3243
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3244
self.build_tree_contents([('child/file','b\nc\n')])
3245
child_tree.commit('child commit')
3246
child_tree.lock_write()
3247
self.addCleanup(child_tree.unlock)
3248
work_tree.lock_write()
3249
self.addCleanup(work_tree.unlock)
3250
preview = TransformPreview(work_tree)
3251
self.addCleanup(preview.finalize)
3252
file_trans_id = preview.trans_id_file_id('file-id')
3253
preview.delete_contents(file_trans_id)
3254
preview.create_file('a\nb\n', file_trans_id)
3255
preview_tree = preview.get_preview_tree()
3256
merger = Merger.from_revision_ids(None, preview_tree,
3257
child_tree.branch.last_revision(),
3258
other_branch=child_tree.branch,
3259
tree_branch=work_tree.branch)
3260
merger.merge_type = Merge3Merger
3261
tt = merger.make_merger().make_preview_transform()
3262
self.addCleanup(tt.finalize)
3263
final_tree = tt.get_preview_tree()
3264
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3266
def test_merge_preview_into_workingtree(self):
3267
tree = self.make_branch_and_tree('tree')
3268
tree.set_root_id('TREE_ROOT')
3269
tt = TransformPreview(tree)
3270
self.addCleanup(tt.finalize)
3271
tt.new_file('name', tt.root, 'content', 'file-id')
3272
tree2 = self.make_branch_and_tree('tree2')
3273
tree2.set_root_id('TREE_ROOT')
3274
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3275
None, tree.basis_tree())
3276
merger.merge_type = Merge3Merger
3279
def test_merge_preview_into_workingtree_handles_conflicts(self):
3280
tree = self.make_branch_and_tree('tree')
3281
self.build_tree_contents([('tree/foo', 'bar')])
3282
tree.add('foo', 'foo-id')
3284
tt = TransformPreview(tree)
3285
self.addCleanup(tt.finalize)
3286
trans_id = tt.trans_id_file_id('foo-id')
3287
tt.delete_contents(trans_id)
3288
tt.create_file('baz', trans_id)
3289
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3290
self.build_tree_contents([('tree2/foo', 'qux')])
3292
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3293
pb, tree.basis_tree())
3294
merger.merge_type = Merge3Merger
3297
def test_has_filename(self):
3298
wt = self.make_branch_and_tree('tree')
3299
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3300
tt = TransformPreview(wt)
3301
removed_id = tt.trans_id_tree_path('removed')
3302
tt.delete_contents(removed_id)
3303
tt.new_file('new', tt.root, 'contents')
3304
modified_id = tt.trans_id_tree_path('modified')
3305
tt.delete_contents(modified_id)
3306
tt.create_file('modified-contents', modified_id)
3307
self.addCleanup(tt.finalize)
3308
tree = tt.get_preview_tree()
3309
self.assertTrue(tree.has_filename('unmodified'))
3310
self.assertFalse(tree.has_filename('not-present'))
3311
self.assertFalse(tree.has_filename('removed'))
3312
self.assertTrue(tree.has_filename('new'))
3313
self.assertTrue(tree.has_filename('modified'))
3315
def test_is_executable(self):
3316
tree = self.make_branch_and_tree('tree')
3317
preview = TransformPreview(tree)
3318
self.addCleanup(preview.finalize)
3319
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3320
preview_tree = preview.get_preview_tree()
3321
self.assertEqual(False, preview_tree.is_executable('baz-id',
3323
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3325
def test_commit_preview_tree(self):
3326
tree = self.make_branch_and_tree('tree')
3327
rev_id = tree.commit('rev1')
3328
tree.branch.lock_write()
3329
self.addCleanup(tree.branch.unlock)
3330
tt = TransformPreview(tree)
3331
tt.new_file('file', tt.root, 'contents', 'file_id')
3332
self.addCleanup(tt.finalize)
3333
preview = tt.get_preview_tree()
3334
preview.set_parent_ids([rev_id])
3335
builder = tree.branch.get_commit_builder([rev_id])
3336
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3337
builder.finish_inventory()
3338
rev2_id = builder.commit('rev2')
3339
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3340
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3342
def test_ascii_limbo_paths(self):
3343
self.requireFeature(features.UnicodeFilenameFeature)
3344
branch = self.make_branch('any')
3345
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3346
tt = TransformPreview(tree)
3347
self.addCleanup(tt.finalize)
3348
foo_id = tt.new_directory('', ROOT_PARENT)
3349
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3350
limbo_path = tt._limbo_name(bar_id)
3351
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3354
class FakeSerializer(object):
3355
"""Serializer implementation that simply returns the input.
3357
The input is returned in the order used by pack.ContainerPushParser.
3360
def bytes_record(bytes, names):
3364
class TestSerializeTransform(tests.TestCaseWithTransport):
3366
_test_needs_features = [features.UnicodeFilenameFeature]
3368
def get_preview(self, tree=None):
3370
tree = self.make_branch_and_tree('tree')
3371
tt = TransformPreview(tree)
3372
self.addCleanup(tt.finalize)
3375
def assertSerializesTo(self, expected, tt):
3376
records = list(tt.serialize(FakeSerializer()))
3377
self.assertEqual(expected, records)
3380
def default_attribs():
3385
'_new_executability': {},
3387
'_tree_path_ids': {'': 'new-0'},
3389
'_removed_contents': [],
3390
'_non_present_ids': {},
3393
def make_records(self, attribs, contents):
3395
(((('attribs'),),), bencode.bencode(attribs))]
3396
records.extend([(((n, k),), c) for n, k, c in contents])
3399
def creation_records(self):
3400
attribs = self.default_attribs()
3401
attribs['_id_number'] = 3
3402
attribs['_new_name'] = {
3403
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3404
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3405
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3406
attribs['_new_executability'] = {'new-1': 1}
3408
('new-1', 'file', 'i 1\nbar\n'),
3409
('new-2', 'directory', ''),
3411
return self.make_records(attribs, contents)
3413
def test_serialize_creation(self):
3414
tt = self.get_preview()
3415
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3416
tt.new_directory('qux', tt.root, 'quxx')
3417
self.assertSerializesTo(self.creation_records(), tt)
3419
def test_deserialize_creation(self):
3420
tt = self.get_preview()
3421
tt.deserialize(iter(self.creation_records()))
3422
self.assertEqual(3, tt._id_number)
3423
self.assertEqual({'new-1': u'foo\u1234',
3424
'new-2': 'qux'}, tt._new_name)
3425
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3426
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3427
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3428
self.assertEqual({'new-1': True}, tt._new_executability)
3429
self.assertEqual({'new-1': 'file',
3430
'new-2': 'directory'}, tt._new_contents)
3431
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3433
foo_content = foo_limbo.read()
3436
self.assertEqual('bar', foo_content)
3438
def symlink_creation_records(self):
3439
attribs = self.default_attribs()
3440
attribs['_id_number'] = 2
3441
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3442
attribs['_new_parent'] = {'new-1': 'new-0'}
3443
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3444
return self.make_records(attribs, contents)
3446
def test_serialize_symlink_creation(self):
3447
self.requireFeature(features.SymlinkFeature)
3448
tt = self.get_preview()
3449
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3450
self.assertSerializesTo(self.symlink_creation_records(), tt)
3452
def test_deserialize_symlink_creation(self):
3453
self.requireFeature(features.SymlinkFeature)
3454
tt = self.get_preview()
3455
tt.deserialize(iter(self.symlink_creation_records()))
3456
abspath = tt._limbo_name('new-1')
3457
foo_content = osutils.readlink(abspath)
3458
self.assertEqual(u'bar\u1234', foo_content)
3460
def make_destruction_preview(self):
3461
tree = self.make_branch_and_tree('.')
3462
self.build_tree([u'foo\u1234', 'bar'])
3463
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3464
return self.get_preview(tree)
3466
def destruction_records(self):
3467
attribs = self.default_attribs()
3468
attribs['_id_number'] = 3
3469
attribs['_removed_id'] = ['new-1']
3470
attribs['_removed_contents'] = ['new-2']
3471
attribs['_tree_path_ids'] = {
3473
u'foo\u1234'.encode('utf-8'): 'new-1',
3476
return self.make_records(attribs, [])
3478
def test_serialize_destruction(self):
3479
tt = self.make_destruction_preview()
3480
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3481
tt.unversion_file(foo_trans_id)
3482
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3483
tt.delete_contents(bar_trans_id)
3484
self.assertSerializesTo(self.destruction_records(), tt)
3486
def test_deserialize_destruction(self):
3487
tt = self.make_destruction_preview()
3488
tt.deserialize(iter(self.destruction_records()))
3489
self.assertEqual({u'foo\u1234': 'new-1',
3491
'': tt.root}, tt._tree_path_ids)
3492
self.assertEqual({'new-1': u'foo\u1234',
3494
tt.root: ''}, tt._tree_id_paths)
3495
self.assertEqual(set(['new-1']), tt._removed_id)
3496
self.assertEqual(set(['new-2']), tt._removed_contents)
3498
def missing_records(self):
3499
attribs = self.default_attribs()
3500
attribs['_id_number'] = 2
3501
attribs['_non_present_ids'] = {
3503
return self.make_records(attribs, [])
3505
def test_serialize_missing(self):
3506
tt = self.get_preview()
3507
boo_trans_id = tt.trans_id_file_id('boo')
3508
self.assertSerializesTo(self.missing_records(), tt)
3510
def test_deserialize_missing(self):
3511
tt = self.get_preview()
3512
tt.deserialize(iter(self.missing_records()))
3513
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3515
def make_modification_preview(self):
3516
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3517
LINES_TWO = 'z\nbb\nx\ndd\n'
3518
tree = self.make_branch_and_tree('tree')
3519
self.build_tree_contents([('tree/file', LINES_ONE)])
3520
tree.add('file', 'file-id')
3521
return self.get_preview(tree), LINES_TWO
3523
def modification_records(self):
3524
attribs = self.default_attribs()
3525
attribs['_id_number'] = 2
3526
attribs['_tree_path_ids'] = {
3529
attribs['_removed_contents'] = ['new-1']
3530
contents = [('new-1', 'file',
3531
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3532
return self.make_records(attribs, contents)
3534
def test_serialize_modification(self):
3535
tt, LINES = self.make_modification_preview()
3536
trans_id = tt.trans_id_file_id('file-id')
3537
tt.delete_contents(trans_id)
3538
tt.create_file(LINES, trans_id)
3539
self.assertSerializesTo(self.modification_records(), tt)
3541
def test_deserialize_modification(self):
3542
tt, LINES = self.make_modification_preview()
3543
tt.deserialize(iter(self.modification_records()))
3544
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3546
def make_kind_change_preview(self):
3547
LINES = 'a\nb\nc\nd\n'
3548
tree = self.make_branch_and_tree('tree')
3549
self.build_tree(['tree/foo/'])
3550
tree.add('foo', 'foo-id')
3551
return self.get_preview(tree), LINES
3553
def kind_change_records(self):
3554
attribs = self.default_attribs()
3555
attribs['_id_number'] = 2
3556
attribs['_tree_path_ids'] = {
3559
attribs['_removed_contents'] = ['new-1']
3560
contents = [('new-1', 'file',
3561
'i 4\na\nb\nc\nd\n\n')]
3562
return self.make_records(attribs, contents)
3564
def test_serialize_kind_change(self):
3565
tt, LINES = self.make_kind_change_preview()
3566
trans_id = tt.trans_id_file_id('foo-id')
3567
tt.delete_contents(trans_id)
3568
tt.create_file(LINES, trans_id)
3569
self.assertSerializesTo(self.kind_change_records(), tt)
3571
def test_deserialize_kind_change(self):
3572
tt, LINES = self.make_kind_change_preview()
3573
tt.deserialize(iter(self.kind_change_records()))
3574
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3576
def make_add_contents_preview(self):
3577
LINES = 'a\nb\nc\nd\n'
3578
tree = self.make_branch_and_tree('tree')
3579
self.build_tree(['tree/foo'])
3581
os.unlink('tree/foo')
3582
return self.get_preview(tree), LINES
3584
def add_contents_records(self):
3585
attribs = self.default_attribs()
3586
attribs['_id_number'] = 2
3587
attribs['_tree_path_ids'] = {
3590
contents = [('new-1', 'file',
3591
'i 4\na\nb\nc\nd\n\n')]
3592
return self.make_records(attribs, contents)
3594
def test_serialize_add_contents(self):
3595
tt, LINES = self.make_add_contents_preview()
3596
trans_id = tt.trans_id_tree_path('foo')
3597
tt.create_file(LINES, trans_id)
3598
self.assertSerializesTo(self.add_contents_records(), tt)
3600
def test_deserialize_add_contents(self):
3601
tt, LINES = self.make_add_contents_preview()
3602
tt.deserialize(iter(self.add_contents_records()))
3603
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3605
def test_get_parents_lines(self):
3606
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3607
LINES_TWO = 'z\nbb\nx\ndd\n'
3608
tree = self.make_branch_and_tree('tree')
3609
self.build_tree_contents([('tree/file', LINES_ONE)])
3610
tree.add('file', 'file-id')
3611
tt = self.get_preview(tree)
3612
trans_id = tt.trans_id_tree_path('file')
3613
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3614
tt._get_parents_lines(trans_id))
3616
def test_get_parents_texts(self):
3617
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3618
LINES_TWO = 'z\nbb\nx\ndd\n'
3619
tree = self.make_branch_and_tree('tree')
3620
self.build_tree_contents([('tree/file', LINES_ONE)])
3621
tree.add('file', 'file-id')
3622
tt = self.get_preview(tree)
3623
trans_id = tt.trans_id_tree_path('file')
3624
self.assertEqual((LINES_ONE,),
3625
tt._get_parents_texts(trans_id))
3628
class TestOrphan(tests.TestCaseWithTransport):
3630
def test_no_orphan_for_transform_preview(self):
3631
tree = self.make_branch_and_tree('tree')
3632
tt = transform.TransformPreview(tree)
3633
self.addCleanup(tt.finalize)
3634
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3636
def _set_orphan_policy(self, wt, policy):
3637
wt.branch.get_config_stack().set('bzr.transform.orphan_policy',
3640
def _prepare_orphan(self, wt):
3641
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3642
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3643
wt.commit('add dir and file ignoring foo')
3644
tt = transform.TreeTransform(wt)
3645
self.addCleanup(tt.finalize)
3646
# dir and bar are deleted
3647
dir_tid = tt.trans_id_tree_path('dir')
3648
file_tid = tt.trans_id_tree_path('dir/file')
3649
orphan_tid = tt.trans_id_tree_path('dir/foo')
3650
tt.delete_contents(file_tid)
3651
tt.unversion_file(file_tid)
3652
tt.delete_contents(dir_tid)
3653
tt.unversion_file(dir_tid)
3654
# There should be a conflict because dir still contain foo
3655
raw_conflicts = tt.find_conflicts()
3656
self.assertLength(1, raw_conflicts)
3657
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3658
return tt, orphan_tid
3660
def test_new_orphan_created(self):
3661
wt = self.make_branch_and_tree('.')
3662
self._set_orphan_policy(wt, 'move')
3663
tt, orphan_tid = self._prepare_orphan(wt)
3666
warnings.append(args[0] % args[1:])
3667
self.overrideAttr(trace, 'warning', warning)
3668
remaining_conflicts = resolve_conflicts(tt)
3669
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3671
# Yeah for resolved conflicts !
3672
self.assertLength(0, remaining_conflicts)
3673
# We have a new orphan
3674
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3675
self.assertEquals('bzr-orphans',
3676
tt.final_name(tt.final_parent(orphan_tid)))
3678
def test_never_orphan(self):
3679
wt = self.make_branch_and_tree('.')
3680
self._set_orphan_policy(wt, 'conflict')
3681
tt, orphan_tid = self._prepare_orphan(wt)
3682
remaining_conflicts = resolve_conflicts(tt)
3683
self.assertLength(1, remaining_conflicts)
3684
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3685
remaining_conflicts.pop())
3687
def test_orphan_error(self):
3688
def bogus_orphan(tt, orphan_id, parent_id):
3689
raise transform.OrphaningError(tt.final_name(orphan_id),
3690
tt.final_name(parent_id))
3691
transform.orphaning_registry.register('bogus', bogus_orphan,
3692
'Raise an error when orphaning')
3693
wt = self.make_branch_and_tree('.')
3694
self._set_orphan_policy(wt, 'bogus')
3695
tt, orphan_tid = self._prepare_orphan(wt)
3696
remaining_conflicts = resolve_conflicts(tt)
3697
self.assertLength(1, remaining_conflicts)
3698
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3699
remaining_conflicts.pop())
3701
def test_unknown_orphan_policy(self):
3702
wt = self.make_branch_and_tree('.')
3703
# Set a fictional policy nobody ever implemented
3704
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3705
tt, orphan_tid = self._prepare_orphan(wt)
3708
warnings.append(args[0] % args[1:])
3709
self.overrideAttr(trace, 'warning', warning)
3710
remaining_conflicts = resolve_conflicts(tt)
3711
# We fallback to the default policy which create a conflict
3712
self.assertLength(1, remaining_conflicts)
3713
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3714
remaining_conflicts.pop())
3715
self.assertLength(1, warnings)
3716
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3719
class TestTransformHooks(tests.TestCaseWithTransport):
3722
super(TestTransformHooks, self).setUp()
3723
self.wt = self.make_branch_and_tree('.')
3726
def get_transform(self):
3727
transform = TreeTransform(self.wt)
3728
self.addCleanup(transform.finalize)
3729
return transform, transform.root
3731
def test_pre_commit_hooks(self):
3733
def record_pre_transform(tree, tt):
3734
calls.append((tree, tt))
3735
MutableTree.hooks.install_named_hook('pre_transform',
3736
record_pre_transform, "Pre transform")
3737
transform, root = self.get_transform()
3738
old_root_id = transform.tree_file_id(root)
3740
self.assertEqual(old_root_id, self.wt.get_root_id())
3741
self.assertEquals([(self.wt, transform)], calls)
3743
def test_post_commit_hooks(self):
3745
def record_post_transform(tree, tt):
3746
calls.append((tree, tt))
3747
MutableTree.hooks.install_named_hook('post_transform',
3748
record_post_transform, "Post transform")
3749
transform, root = self.get_transform()
3750
old_root_id = transform.tree_file_id(root)
3752
self.assertEqual(old_root_id, self.wt.get_root_id())
3753
self.assertEquals([(self.wt, transform)], calls)