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.tests import (
68
from bzrlib.tests.features import (
72
from bzrlib.transform import (
86
class TestTreeTransform(tests.TestCaseWithTransport):
89
super(TestTreeTransform, self).setUp()
90
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
93
def get_transform(self):
94
transform = TreeTransform(self.wt)
95
self.addCleanup(transform.finalize)
96
return transform, transform.root
98
def get_transform_for_sha1_test(self):
99
trans, root = self.get_transform()
100
self.wt.lock_tree_write()
101
self.addCleanup(self.wt.unlock)
102
contents = ['just some content\n']
103
sha1 = osutils.sha_strings(contents)
104
# Roll back the clock
105
trans._creation_mtime = time.time() - 20.0
106
return trans, root, contents, sha1
108
def test_existing_limbo(self):
109
transform, root = self.get_transform()
110
limbo_name = transform._limbodir
111
deletion_path = transform._deletiondir
112
os.mkdir(pathjoin(limbo_name, 'hehe'))
113
self.assertRaises(ImmortalLimbo, transform.apply)
114
self.assertRaises(LockError, self.wt.unlock)
115
self.assertRaises(ExistingLimbo, self.get_transform)
116
self.assertRaises(LockError, self.wt.unlock)
117
os.rmdir(pathjoin(limbo_name, 'hehe'))
119
os.rmdir(deletion_path)
120
transform, root = self.get_transform()
123
def test_existing_pending_deletion(self):
124
transform, root = self.get_transform()
125
deletion_path = self._limbodir = urlutils.local_path_from_url(
126
transform._tree._transport.abspath('pending-deletion'))
127
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
128
self.assertRaises(ImmortalPendingDeletion, transform.apply)
129
self.assertRaises(LockError, self.wt.unlock)
130
self.assertRaises(ExistingPendingDeletion, self.get_transform)
132
def test_build(self):
133
transform, root = self.get_transform()
134
self.wt.lock_tree_write()
135
self.addCleanup(self.wt.unlock)
136
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
137
imaginary_id = transform.trans_id_tree_path('imaginary')
138
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
139
self.assertEqual(imaginary_id, imaginary_id2)
140
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
141
self.assertEqual('directory', transform.final_kind(root))
142
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
143
trans_id = transform.create_path('name', root)
144
self.assertIs(transform.final_file_id(trans_id), None)
145
self.assertIs(None, transform.final_kind(trans_id))
146
transform.create_file('contents', trans_id)
147
transform.set_executability(True, trans_id)
148
transform.version_file('my_pretties', trans_id)
149
self.assertRaises(DuplicateKey, transform.version_file,
150
'my_pretties', trans_id)
151
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
152
self.assertEqual(transform.final_parent(trans_id), root)
153
self.assertIs(transform.final_parent(root), ROOT_PARENT)
154
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
155
oz_id = transform.create_path('oz', root)
156
transform.create_directory(oz_id)
157
transform.version_file('ozzie', oz_id)
158
trans_id2 = transform.create_path('name2', root)
159
transform.create_file('contents', trans_id2)
160
transform.set_executability(False, trans_id2)
161
transform.version_file('my_pretties2', trans_id2)
162
modified_paths = transform.apply().modified_paths
163
self.assertEqual('contents', self.wt.get_file_byname('name').read())
164
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
165
self.assertIs(self.wt.is_executable('my_pretties'), True)
166
self.assertIs(self.wt.is_executable('my_pretties2'), False)
167
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
168
self.assertEqual(len(modified_paths), 3)
169
tree_mod_paths = [self.wt.id2abspath(f) for f in
170
('ozzie', 'my_pretties', 'my_pretties2')]
171
self.assertSubset(tree_mod_paths, modified_paths)
172
# is it safe to finalize repeatedly?
176
def test_apply_informs_tree_of_observed_sha1(self):
177
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
178
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
181
orig = self.wt._observed_sha1
182
def _observed_sha1(*args):
185
self.wt._observed_sha1 = _observed_sha1
187
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
190
def test_create_file_caches_sha1(self):
191
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
192
trans_id = trans.create_path('file1', root)
193
trans.create_file(contents, trans_id, sha1=sha1)
194
st_val = osutils.lstat(trans._limbo_name(trans_id))
195
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
196
self.assertEqual(o_sha1, sha1)
197
self.assertEqualStat(o_st_val, st_val)
199
def test__apply_insertions_updates_sha1(self):
200
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
201
trans_id = trans.create_path('file1', root)
202
trans.create_file(contents, trans_id, sha1=sha1)
203
st_val = osutils.lstat(trans._limbo_name(trans_id))
204
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
205
self.assertEqual(o_sha1, sha1)
206
self.assertEqualStat(o_st_val, st_val)
207
creation_mtime = trans._creation_mtime + 10.0
208
# We fake a time difference from when the file was created until now it
209
# is being renamed by using os.utime. Note that the change we actually
210
# want to see is the real ctime change from 'os.rename()', but as long
211
# as we observe a new stat value, we should be fine.
212
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
214
new_st_val = osutils.lstat(self.wt.abspath('file1'))
215
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
216
self.assertEqual(o_sha1, sha1)
217
self.assertEqualStat(o_st_val, new_st_val)
218
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
220
def test_new_file_caches_sha1(self):
221
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
222
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
224
st_val = osutils.lstat(trans._limbo_name(trans_id))
225
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
226
self.assertEqual(o_sha1, sha1)
227
self.assertEqualStat(o_st_val, st_val)
229
def test_cancel_creation_removes_observed_sha1(self):
230
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
231
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
233
self.assertTrue(trans_id in trans._observed_sha1s)
234
trans.cancel_creation(trans_id)
235
self.assertFalse(trans_id in trans._observed_sha1s)
237
def test_create_files_same_timestamp(self):
238
transform, root = self.get_transform()
239
self.wt.lock_tree_write()
240
self.addCleanup(self.wt.unlock)
241
# Roll back the clock, so that we know everything is being set to the
243
transform._creation_mtime = creation_mtime = time.time() - 20.0
244
transform.create_file('content-one',
245
transform.create_path('one', root))
246
time.sleep(1) # *ugly*
247
transform.create_file('content-two',
248
transform.create_path('two', root))
250
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
252
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
254
# We only guarantee 2s resolution
255
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
256
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
257
# But if we have more than that, all files should get the same result
258
self.assertEqual(st1.st_mtime, st2.st_mtime)
260
def test_change_root_id(self):
261
transform, root = self.get_transform()
262
self.assertNotEqual('new-root-id', self.wt.get_root_id())
263
transform.new_directory('', ROOT_PARENT, 'new-root-id')
264
transform.delete_contents(root)
265
transform.unversion_file(root)
266
transform.fixup_new_roots()
268
self.assertEqual('new-root-id', self.wt.get_root_id())
270
def test_change_root_id_add_files(self):
271
transform, root = self.get_transform()
272
self.assertNotEqual('new-root-id', self.wt.get_root_id())
273
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
274
transform.new_file('file', new_trans_id, ['new-contents\n'],
276
transform.delete_contents(root)
277
transform.unversion_file(root)
278
transform.fixup_new_roots()
280
self.assertEqual('new-root-id', self.wt.get_root_id())
281
self.assertEqual('new-file-id', self.wt.path2id('file'))
282
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
284
def test_add_two_roots(self):
285
transform, root = self.get_transform()
286
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
287
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
288
self.assertRaises(ValueError, transform.fixup_new_roots)
290
def test_retain_existing_root(self):
291
tt, root = self.get_transform()
293
tt.new_directory('', ROOT_PARENT, 'new-root-id')
295
self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
297
def test_retain_existing_root_added_file(self):
298
tt, root = self.get_transform()
299
new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
300
child = tt.new_directory('child', new_trans_id, 'child-id')
302
self.assertEqual(tt.root, tt.final_parent(child))
304
def test_add_unversioned_root(self):
305
transform, root = self.get_transform()
306
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
307
transform.delete_contents(transform.root)
308
transform.fixup_new_roots()
309
self.assertNotIn(transform.root, transform._new_id)
311
def test_remove_root_fixup(self):
312
transform, root = self.get_transform()
313
old_root_id = self.wt.get_root_id()
314
self.assertNotEqual('new-root-id', old_root_id)
315
transform.delete_contents(root)
316
transform.unversion_file(root)
317
transform.fixup_new_roots()
319
self.assertEqual(old_root_id, self.wt.get_root_id())
321
transform, root = self.get_transform()
322
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
323
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
324
self.assertRaises(ValueError, transform.fixup_new_roots)
326
def test_fixup_new_roots_permits_empty_tree(self):
327
transform, root = self.get_transform()
328
transform.delete_contents(root)
329
transform.unversion_file(root)
330
transform.fixup_new_roots()
331
self.assertIs(None, transform.final_kind(root))
332
self.assertIs(None, transform.final_file_id(root))
334
def test_apply_retains_root_directory(self):
335
# Do not attempt to delete the physical root directory, because that
337
transform, root = self.get_transform()
339
transform.delete_contents(root)
340
e = self.assertRaises(AssertionError, self.assertRaises,
341
errors.TransformRenameFailed,
343
self.assertContainsRe('TransformRenameFailed not raised', str(e))
345
def test_apply_retains_file_id(self):
346
transform, root = self.get_transform()
347
old_root_id = transform.tree_file_id(root)
348
transform.unversion_file(root)
350
self.assertEqual(old_root_id, self.wt.get_root_id())
352
def test_hardlink(self):
353
self.requireFeature(HardlinkFeature)
354
transform, root = self.get_transform()
355
transform.new_file('file1', root, 'contents')
357
target = self.make_branch_and_tree('target')
358
target_transform = TreeTransform(target)
359
trans_id = target_transform.create_path('file1', target_transform.root)
360
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
361
target_transform.apply()
362
self.assertPathExists('target/file1')
363
source_stat = os.stat(self.wt.abspath('file1'))
364
target_stat = os.stat('target/file1')
365
self.assertEqual(source_stat, target_stat)
367
def test_convenience(self):
368
transform, root = self.get_transform()
369
self.wt.lock_tree_write()
370
self.addCleanup(self.wt.unlock)
371
trans_id = transform.new_file('name', root, 'contents',
373
oz = transform.new_directory('oz', root, 'oz-id')
374
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
375
toto = transform.new_file('toto', dorothy, 'toto-contents',
378
self.assertEqual(len(transform.find_conflicts()), 0)
380
self.assertRaises(ReusingTransform, transform.find_conflicts)
381
self.assertEqual('contents', file(self.wt.abspath('name')).read())
382
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
383
self.assertIs(self.wt.is_executable('my_pretties'), True)
384
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
385
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
386
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
388
self.assertEqual('toto-contents',
389
self.wt.get_file_byname('oz/dorothy/toto').read())
390
self.assertIs(self.wt.is_executable('toto-id'), False)
392
def test_tree_reference(self):
393
transform, root = self.get_transform()
394
tree = transform._tree
395
trans_id = transform.new_directory('reference', root, 'subtree-id')
396
transform.set_tree_reference('subtree-revision', trans_id)
399
self.addCleanup(tree.unlock)
400
self.assertEqual('subtree-revision',
401
tree.inventory['subtree-id'].reference_revision)
403
def test_conflicts(self):
404
transform, root = self.get_transform()
405
trans_id = transform.new_file('name', root, 'contents',
407
self.assertEqual(len(transform.find_conflicts()), 0)
408
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
409
self.assertEqual(transform.find_conflicts(),
410
[('duplicate', trans_id, trans_id2, 'name')])
411
self.assertRaises(MalformedTransform, transform.apply)
412
transform.adjust_path('name', trans_id, trans_id2)
413
self.assertEqual(transform.find_conflicts(),
414
[('non-directory parent', trans_id)])
415
tinman_id = transform.trans_id_tree_path('tinman')
416
transform.adjust_path('name', tinman_id, trans_id2)
417
self.assertEqual(transform.find_conflicts(),
418
[('unversioned parent', tinman_id),
419
('missing parent', tinman_id)])
420
lion_id = transform.create_path('lion', root)
421
self.assertEqual(transform.find_conflicts(),
422
[('unversioned parent', tinman_id),
423
('missing parent', tinman_id)])
424
transform.adjust_path('name', lion_id, trans_id2)
425
self.assertEqual(transform.find_conflicts(),
426
[('unversioned parent', lion_id),
427
('missing parent', lion_id)])
428
transform.version_file("Courage", lion_id)
429
self.assertEqual(transform.find_conflicts(),
430
[('missing parent', lion_id),
431
('versioning no contents', lion_id)])
432
transform.adjust_path('name2', root, trans_id2)
433
self.assertEqual(transform.find_conflicts(),
434
[('versioning no contents', lion_id)])
435
transform.create_file('Contents, okay?', lion_id)
436
transform.adjust_path('name2', trans_id2, trans_id2)
437
self.assertEqual(transform.find_conflicts(),
438
[('parent loop', trans_id2),
439
('non-directory parent', trans_id2)])
440
transform.adjust_path('name2', root, trans_id2)
441
oz_id = transform.new_directory('oz', root)
442
transform.set_executability(True, oz_id)
443
self.assertEqual(transform.find_conflicts(),
444
[('unversioned executability', oz_id)])
445
transform.version_file('oz-id', oz_id)
446
self.assertEqual(transform.find_conflicts(),
447
[('non-file executability', oz_id)])
448
transform.set_executability(None, oz_id)
449
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
451
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
452
self.assertEqual('contents', file(self.wt.abspath('name')).read())
453
transform2, root = self.get_transform()
454
oz_id = transform2.trans_id_tree_file_id('oz-id')
455
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
456
result = transform2.find_conflicts()
457
fp = FinalPaths(transform2)
458
self.assert_('oz/tip' in transform2._tree_path_ids)
459
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
460
self.assertEqual(len(result), 2)
461
self.assertEqual((result[0][0], result[0][1]),
462
('duplicate', newtip))
463
self.assertEqual((result[1][0], result[1][2]),
464
('duplicate id', newtip))
465
transform2.finalize()
466
transform3 = TreeTransform(self.wt)
467
self.addCleanup(transform3.finalize)
468
oz_id = transform3.trans_id_tree_file_id('oz-id')
469
transform3.delete_contents(oz_id)
470
self.assertEqual(transform3.find_conflicts(),
471
[('missing parent', oz_id)])
472
root_id = transform3.root
473
tip_id = transform3.trans_id_tree_file_id('tip-id')
474
transform3.adjust_path('tip', root_id, tip_id)
477
def test_conflict_on_case_insensitive(self):
478
tree = self.make_branch_and_tree('tree')
479
# Don't try this at home, kids!
480
# Force the tree to report that it is case sensitive, for conflict
482
tree.case_sensitive = True
483
transform = TreeTransform(tree)
484
self.addCleanup(transform.finalize)
485
transform.new_file('file', transform.root, 'content')
486
transform.new_file('FiLe', transform.root, 'content')
487
result = transform.find_conflicts()
488
self.assertEqual([], result)
490
# Force the tree to report that it is case insensitive, for conflict
492
tree.case_sensitive = False
493
transform = TreeTransform(tree)
494
self.addCleanup(transform.finalize)
495
transform.new_file('file', transform.root, 'content')
496
transform.new_file('FiLe', transform.root, 'content')
497
result = transform.find_conflicts()
498
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
500
def test_conflict_on_case_insensitive_existing(self):
501
tree = self.make_branch_and_tree('tree')
502
self.build_tree(['tree/FiLe'])
503
# Don't try this at home, kids!
504
# Force the tree to report that it is case sensitive, for conflict
506
tree.case_sensitive = True
507
transform = TreeTransform(tree)
508
self.addCleanup(transform.finalize)
509
transform.new_file('file', transform.root, 'content')
510
result = transform.find_conflicts()
511
self.assertEqual([], result)
513
# Force the tree to report that it is case insensitive, for conflict
515
tree.case_sensitive = False
516
transform = TreeTransform(tree)
517
self.addCleanup(transform.finalize)
518
transform.new_file('file', transform.root, 'content')
519
result = transform.find_conflicts()
520
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
522
def test_resolve_case_insensitive_conflict(self):
523
tree = self.make_branch_and_tree('tree')
524
# Don't try this at home, kids!
525
# Force the tree to report that it is case insensitive, for conflict
527
tree.case_sensitive = False
528
transform = TreeTransform(tree)
529
self.addCleanup(transform.finalize)
530
transform.new_file('file', transform.root, 'content')
531
transform.new_file('FiLe', transform.root, 'content')
532
resolve_conflicts(transform)
534
self.assertPathExists('tree/file')
535
self.assertPathExists('tree/FiLe.moved')
537
def test_resolve_checkout_case_conflict(self):
538
tree = self.make_branch_and_tree('tree')
539
# Don't try this at home, kids!
540
# Force the tree to report that it is case insensitive, for conflict
542
tree.case_sensitive = False
543
transform = TreeTransform(tree)
544
self.addCleanup(transform.finalize)
545
transform.new_file('file', transform.root, 'content')
546
transform.new_file('FiLe', transform.root, 'content')
547
resolve_conflicts(transform,
548
pass_func=lambda t, c: resolve_checkout(t, c, []))
550
self.assertPathExists('tree/file')
551
self.assertPathExists('tree/FiLe.moved')
553
def test_apply_case_conflict(self):
554
"""Ensure that a transform with case conflicts can always be applied"""
555
tree = self.make_branch_and_tree('tree')
556
transform = TreeTransform(tree)
557
self.addCleanup(transform.finalize)
558
transform.new_file('file', transform.root, 'content')
559
transform.new_file('FiLe', transform.root, 'content')
560
dir = transform.new_directory('dir', transform.root)
561
transform.new_file('dirfile', dir, 'content')
562
transform.new_file('dirFiLe', dir, 'content')
563
resolve_conflicts(transform)
565
self.assertPathExists('tree/file')
566
if not os.path.exists('tree/FiLe.moved'):
567
self.assertPathExists('tree/FiLe')
568
self.assertPathExists('tree/dir/dirfile')
569
if not os.path.exists('tree/dir/dirFiLe.moved'):
570
self.assertPathExists('tree/dir/dirFiLe')
572
def test_case_insensitive_limbo(self):
573
tree = self.make_branch_and_tree('tree')
574
# Don't try this at home, kids!
575
# Force the tree to report that it is case insensitive
576
tree.case_sensitive = False
577
transform = TreeTransform(tree)
578
self.addCleanup(transform.finalize)
579
dir = transform.new_directory('dir', transform.root)
580
first = transform.new_file('file', dir, 'content')
581
second = transform.new_file('FiLe', dir, 'content')
582
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
583
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
585
def test_adjust_path_updates_child_limbo_names(self):
586
tree = self.make_branch_and_tree('tree')
587
transform = TreeTransform(tree)
588
self.addCleanup(transform.finalize)
589
foo_id = transform.new_directory('foo', transform.root)
590
bar_id = transform.new_directory('bar', foo_id)
591
baz_id = transform.new_directory('baz', bar_id)
592
qux_id = transform.new_directory('qux', baz_id)
593
transform.adjust_path('quxx', foo_id, bar_id)
594
self.assertStartsWith(transform._limbo_name(qux_id),
595
transform._limbo_name(bar_id))
597
def test_add_del(self):
598
start, root = self.get_transform()
599
start.new_directory('a', root, 'a')
601
transform, root = self.get_transform()
602
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
603
transform.new_directory('a', root, 'a')
606
def test_unversioning(self):
607
create_tree, root = self.get_transform()
608
parent_id = create_tree.new_directory('parent', root, 'parent-id')
609
create_tree.new_file('child', parent_id, 'child', 'child-id')
611
unversion = TreeTransform(self.wt)
612
self.addCleanup(unversion.finalize)
613
parent = unversion.trans_id_tree_path('parent')
614
unversion.unversion_file(parent)
615
self.assertEqual(unversion.find_conflicts(),
616
[('unversioned parent', parent_id)])
617
file_id = unversion.trans_id_tree_file_id('child-id')
618
unversion.unversion_file(file_id)
621
def test_name_invariants(self):
622
create_tree, root = self.get_transform()
624
root = create_tree.root
625
create_tree.new_file('name1', root, 'hello1', 'name1')
626
create_tree.new_file('name2', root, 'hello2', 'name2')
627
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
628
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
629
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
630
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
633
mangle_tree,root = self.get_transform()
634
root = mangle_tree.root
636
name1 = mangle_tree.trans_id_tree_file_id('name1')
637
name2 = mangle_tree.trans_id_tree_file_id('name2')
638
mangle_tree.adjust_path('name2', root, name1)
639
mangle_tree.adjust_path('name1', root, name2)
641
#tests for deleting parent directories
642
ddir = mangle_tree.trans_id_tree_file_id('ddir')
643
mangle_tree.delete_contents(ddir)
644
dfile = mangle_tree.trans_id_tree_file_id('dfile')
645
mangle_tree.delete_versioned(dfile)
646
mangle_tree.unversion_file(dfile)
647
mfile = mangle_tree.trans_id_tree_file_id('mfile')
648
mangle_tree.adjust_path('mfile', root, mfile)
650
#tests for adding parent directories
651
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
652
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
653
mangle_tree.adjust_path('mfile2', newdir, mfile2)
654
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
655
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
656
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
657
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
659
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
660
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
661
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
662
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
663
self.assertEqual(file(mfile2_path).read(), 'later2')
664
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
665
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
666
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
667
self.assertEqual(file(newfile_path).read(), 'hello3')
668
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
669
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
670
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
672
def test_both_rename(self):
673
create_tree,root = self.get_transform()
674
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
675
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
677
mangle_tree,root = self.get_transform()
678
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
679
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
680
mangle_tree.adjust_path('test', root, selftest)
681
mangle_tree.adjust_path('test_too_much', root, selftest)
682
mangle_tree.set_executability(True, blackbox)
685
def test_both_rename2(self):
686
create_tree,root = self.get_transform()
687
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
688
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
689
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
690
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
693
mangle_tree,root = self.get_transform()
694
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
695
tests = mangle_tree.trans_id_tree_file_id('tests-id')
696
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
697
mangle_tree.adjust_path('selftest', bzrlib, tests)
698
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
699
mangle_tree.set_executability(True, test_too_much)
702
def test_both_rename3(self):
703
create_tree,root = self.get_transform()
704
tests = create_tree.new_directory('tests', root, 'tests-id')
705
create_tree.new_file('test_too_much.py', tests, 'hello1',
708
mangle_tree,root = self.get_transform()
709
tests = mangle_tree.trans_id_tree_file_id('tests-id')
710
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
711
mangle_tree.adjust_path('selftest', root, tests)
712
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
713
mangle_tree.set_executability(True, test_too_much)
716
def test_move_dangling_ie(self):
717
create_tree, root = self.get_transform()
719
root = create_tree.root
720
create_tree.new_file('name1', root, 'hello1', 'name1')
722
delete_contents, root = self.get_transform()
723
file = delete_contents.trans_id_tree_file_id('name1')
724
delete_contents.delete_contents(file)
725
delete_contents.apply()
726
move_id, root = self.get_transform()
727
name1 = move_id.trans_id_tree_file_id('name1')
728
newdir = move_id.new_directory('dir', root, 'newdir')
729
move_id.adjust_path('name2', newdir, name1)
732
def test_replace_dangling_ie(self):
733
create_tree, root = self.get_transform()
735
root = create_tree.root
736
create_tree.new_file('name1', root, 'hello1', 'name1')
738
delete_contents = TreeTransform(self.wt)
739
self.addCleanup(delete_contents.finalize)
740
file = delete_contents.trans_id_tree_file_id('name1')
741
delete_contents.delete_contents(file)
742
delete_contents.apply()
743
delete_contents.finalize()
744
replace = TreeTransform(self.wt)
745
self.addCleanup(replace.finalize)
746
name2 = replace.new_file('name2', root, 'hello2', 'name1')
747
conflicts = replace.find_conflicts()
748
name1 = replace.trans_id_tree_file_id('name1')
749
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
750
resolve_conflicts(replace)
753
def _test_symlinks(self, link_name1,link_target1,
754
link_name2, link_target2):
756
def ozpath(p): return 'oz/' + p
758
self.requireFeature(SymlinkFeature)
759
transform, root = self.get_transform()
760
oz_id = transform.new_directory('oz', root, 'oz-id')
761
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
763
wiz_id = transform.create_path(link_name2, oz_id)
764
transform.create_symlink(link_target2, wiz_id)
765
transform.version_file('wiz-id2', wiz_id)
766
transform.set_executability(True, wiz_id)
767
self.assertEqual(transform.find_conflicts(),
768
[('non-file executability', wiz_id)])
769
transform.set_executability(None, wiz_id)
771
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
772
self.assertEqual('symlink',
773
file_kind(self.wt.abspath(ozpath(link_name1))))
774
self.assertEqual(link_target2,
775
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
776
self.assertEqual(link_target1,
777
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
779
def test_symlinks(self):
780
self._test_symlinks('wizard', 'wizard-target',
781
'wizard2', 'behind_curtain')
783
def test_symlinks_unicode(self):
784
self.requireFeature(features.UnicodeFilenameFeature)
785
self._test_symlinks(u'\N{Euro Sign}wizard',
786
u'wizard-targ\N{Euro Sign}t',
787
u'\N{Euro Sign}wizard2',
788
u'b\N{Euro Sign}hind_curtain')
790
def test_unable_create_symlink(self):
792
wt = self.make_branch_and_tree('.')
793
tt = TreeTransform(wt) # TreeTransform obtains write lock
795
tt.new_symlink('foo', tt.root, 'bar')
799
os_symlink = getattr(os, 'symlink', None)
802
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
804
"Unable to create symlink 'foo' on this platform",
808
os.symlink = os_symlink
810
def get_conflicted(self):
811
create,root = self.get_transform()
812
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
813
oz = create.new_directory('oz', root, 'oz-id')
814
create.new_directory('emeraldcity', oz, 'emerald-id')
816
conflicts,root = self.get_transform()
817
# set up duplicate entry, duplicate id
818
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
820
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
821
oz = conflicts.trans_id_tree_file_id('oz-id')
822
# set up DeletedParent parent conflict
823
conflicts.delete_versioned(oz)
824
emerald = conflicts.trans_id_tree_file_id('emerald-id')
825
# set up MissingParent conflict
826
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
827
conflicts.adjust_path('munchkincity', root, munchkincity)
828
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
830
conflicts.adjust_path('emeraldcity', emerald, emerald)
831
return conflicts, emerald, oz, old_dorothy, new_dorothy
833
def test_conflict_resolution(self):
834
conflicts, emerald, oz, old_dorothy, new_dorothy =\
835
self.get_conflicted()
836
resolve_conflicts(conflicts)
837
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
838
self.assertIs(conflicts.final_file_id(old_dorothy), None)
839
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
840
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
841
self.assertEqual(conflicts.final_parent(emerald), oz)
844
def test_cook_conflicts(self):
845
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
846
raw_conflicts = resolve_conflicts(tt)
847
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
848
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
849
'dorothy', None, 'dorothy-id')
850
self.assertEqual(cooked_conflicts[0], duplicate)
851
duplicate_id = DuplicateID('Unversioned existing file',
852
'dorothy.moved', 'dorothy', None,
854
self.assertEqual(cooked_conflicts[1], duplicate_id)
855
missing_parent = MissingParent('Created directory', 'munchkincity',
857
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
858
self.assertEqual(cooked_conflicts[2], missing_parent)
859
unversioned_parent = UnversionedParent('Versioned directory',
862
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
864
self.assertEqual(cooked_conflicts[3], unversioned_parent)
865
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
866
'oz/emeraldcity', 'emerald-id', 'emerald-id')
867
self.assertEqual(cooked_conflicts[4], deleted_parent)
868
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
869
self.assertEqual(cooked_conflicts[6], parent_loop)
870
self.assertEqual(len(cooked_conflicts), 7)
873
def test_string_conflicts(self):
874
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
875
raw_conflicts = resolve_conflicts(tt)
876
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
878
conflicts_s = [unicode(c) for c in cooked_conflicts]
879
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
880
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
881
'Moved existing file to '
883
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
884
'Unversioned existing file '
886
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
887
' munchkincity. Created directory.')
888
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
889
' versioned, but has versioned'
890
' children. Versioned directory.')
891
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
892
" is not empty. Not deleting.")
893
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
894
' versioned, but has versioned'
895
' children. Versioned directory.')
896
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
897
' oz/emeraldcity. Cancelled move.')
899
def prepare_wrong_parent_kind(self):
900
tt, root = self.get_transform()
901
tt.new_file('parent', root, 'contents', 'parent-id')
903
tt, root = self.get_transform()
904
parent_id = tt.trans_id_file_id('parent-id')
905
tt.new_file('child,', parent_id, 'contents2', 'file-id')
908
def test_find_conflicts_wrong_parent_kind(self):
909
tt = self.prepare_wrong_parent_kind()
912
def test_resolve_conflicts_wrong_existing_parent_kind(self):
913
tt = self.prepare_wrong_parent_kind()
914
raw_conflicts = resolve_conflicts(tt)
915
self.assertEqual(set([('non-directory parent', 'Created directory',
916
'new-3')]), raw_conflicts)
917
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
918
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
919
'parent-id')], cooked_conflicts)
921
self.assertEqual(None, self.wt.path2id('parent'))
922
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
924
def test_resolve_conflicts_wrong_new_parent_kind(self):
925
tt, root = self.get_transform()
926
parent_id = tt.new_directory('parent', root, 'parent-id')
927
tt.new_file('child,', parent_id, 'contents2', 'file-id')
929
tt, root = self.get_transform()
930
parent_id = tt.trans_id_file_id('parent-id')
931
tt.delete_contents(parent_id)
932
tt.create_file('contents', parent_id)
933
raw_conflicts = resolve_conflicts(tt)
934
self.assertEqual(set([('non-directory parent', 'Created directory',
935
'new-3')]), raw_conflicts)
937
self.assertEqual(None, self.wt.path2id('parent'))
938
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
940
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
941
tt, root = self.get_transform()
942
parent_id = tt.new_directory('parent', root)
943
tt.new_file('child,', parent_id, 'contents2')
945
tt, root = self.get_transform()
946
parent_id = tt.trans_id_tree_path('parent')
947
tt.delete_contents(parent_id)
948
tt.create_file('contents', parent_id)
949
resolve_conflicts(tt)
951
self.assertIs(None, self.wt.path2id('parent'))
952
self.assertIs(None, self.wt.path2id('parent.new'))
954
def test_resolve_conflicts_missing_parent(self):
955
wt = self.make_branch_and_tree('.')
956
tt = TreeTransform(wt)
957
self.addCleanup(tt.finalize)
958
parent = tt.trans_id_file_id('parent-id')
959
tt.new_file('file', parent, 'Contents')
960
raw_conflicts = resolve_conflicts(tt)
961
# Since the directory doesn't exist it's seen as 'missing'. So
962
# 'resolve_conflicts' create a conflict asking for it to be created.
963
self.assertLength(1, raw_conflicts)
964
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
966
# apply fail since the missing directory doesn't exist
967
self.assertRaises(errors.NoFinalPath, tt.apply)
969
def test_moving_versioned_directories(self):
970
create, root = self.get_transform()
971
kansas = create.new_directory('kansas', root, 'kansas-id')
972
create.new_directory('house', kansas, 'house-id')
973
create.new_directory('oz', root, 'oz-id')
975
cyclone, root = self.get_transform()
976
oz = cyclone.trans_id_tree_file_id('oz-id')
977
house = cyclone.trans_id_tree_file_id('house-id')
978
cyclone.adjust_path('house', oz, house)
981
def test_moving_root(self):
982
create, root = self.get_transform()
983
fun = create.new_directory('fun', root, 'fun-id')
984
create.new_directory('sun', root, 'sun-id')
985
create.new_directory('moon', root, 'moon')
987
transform, root = self.get_transform()
988
transform.adjust_root_path('oldroot', fun)
989
new_root = transform.trans_id_tree_path('')
990
transform.version_file('new-root', new_root)
993
def test_renames(self):
994
create, root = self.get_transform()
995
old = create.new_directory('old-parent', root, 'old-id')
996
intermediate = create.new_directory('intermediate', old, 'im-id')
997
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1000
rename, root = self.get_transform()
1001
old = rename.trans_id_file_id('old-id')
1002
rename.adjust_path('new', root, old)
1003
myfile = rename.trans_id_file_id('myfile-id')
1004
rename.set_executability(True, myfile)
1007
def test_rename_fails(self):
1008
self.requireFeature(features.not_running_as_root)
1009
# see https://bugs.launchpad.net/bzr/+bug/491763
1010
create, root_id = self.get_transform()
1011
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1012
myfile = create.new_file('myfile', root_id, 'myfile-text',
1015
if os.name == "posix" and sys.platform != "cygwin":
1016
# posix filesystems fail on renaming if the readonly bit is set
1017
osutils.make_readonly(self.wt.abspath('first-dir'))
1018
elif os.name == "nt":
1019
# windows filesystems fail on renaming open files
1020
self.addCleanup(file(self.wt.abspath('myfile')).close)
1022
self.skip("Don't know how to force a permissions error on rename")
1023
# now transform to rename
1024
rename_transform, root_id = self.get_transform()
1025
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1026
dir_id = rename_transform.trans_id_file_id('first-id')
1027
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1028
e = self.assertRaises(errors.TransformRenameFailed,
1029
rename_transform.apply)
1030
# On nix looks like:
1031
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1032
# to .../first-dir/newname: [Errno 13] Permission denied"
1033
# On windows looks like:
1034
# "Failed to rename .../work/myfile to
1035
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1036
# This test isn't concerned with exactly what the error looks like,
1037
# and the strerror will vary across OS and locales, but the assert
1038
# that the exeception attributes are what we expect
1039
self.assertEqual(e.errno, errno.EACCES)
1040
if os.name == "posix":
1041
self.assertEndsWith(e.to_path, "/first-dir/newname")
1043
self.assertEqual(os.path.basename(e.from_path), "myfile")
1045
def test_set_executability_order(self):
1046
"""Ensure that executability behaves the same, no matter what order.
1048
- create file and set executability simultaneously
1049
- create file and set executability afterward
1050
- unsetting the executability of a file whose executability has not been
1051
declared should throw an exception (this may happen when a
1052
merge attempts to create a file with a duplicate ID)
1054
transform, root = self.get_transform()
1055
wt = transform._tree
1057
self.addCleanup(wt.unlock)
1058
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1060
sac = transform.new_file('set_after_creation', root,
1061
'Set after creation', 'sac')
1062
transform.set_executability(True, sac)
1063
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1065
self.assertRaises(KeyError, transform.set_executability, None, uws)
1067
self.assertTrue(wt.is_executable('soc'))
1068
self.assertTrue(wt.is_executable('sac'))
1070
def test_preserve_mode(self):
1071
"""File mode is preserved when replacing content"""
1072
if sys.platform == 'win32':
1073
raise TestSkipped('chmod has no effect on win32')
1074
transform, root = self.get_transform()
1075
transform.new_file('file1', root, 'contents', 'file1-id', True)
1077
self.wt.lock_write()
1078
self.addCleanup(self.wt.unlock)
1079
self.assertTrue(self.wt.is_executable('file1-id'))
1080
transform, root = self.get_transform()
1081
file1_id = transform.trans_id_tree_file_id('file1-id')
1082
transform.delete_contents(file1_id)
1083
transform.create_file('contents2', file1_id)
1085
self.assertTrue(self.wt.is_executable('file1-id'))
1087
def test__set_mode_stats_correctly(self):
1088
"""_set_mode stats to determine file mode."""
1089
if sys.platform == 'win32':
1090
raise TestSkipped('chmod has no effect on win32')
1094
def instrumented_stat(path):
1095
stat_paths.append(path)
1096
return real_stat(path)
1098
transform, root = self.get_transform()
1100
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1101
file_id='bar-id-1', executable=False)
1104
transform, root = self.get_transform()
1105
bar1_id = transform.trans_id_tree_path('bar')
1106
bar2_id = transform.trans_id_tree_path('bar2')
1108
os.stat = instrumented_stat
1109
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1112
transform.finalize()
1114
bar1_abspath = self.wt.abspath('bar')
1115
self.assertEqual([bar1_abspath], stat_paths)
1117
def test_iter_changes(self):
1118
self.wt.set_root_id('eert_toor')
1119
transform, root = self.get_transform()
1120
transform.new_file('old', root, 'blah', 'id-1', True)
1122
transform, root = self.get_transform()
1124
self.assertEqual([], list(transform.iter_changes()))
1125
old = transform.trans_id_tree_file_id('id-1')
1126
transform.unversion_file(old)
1127
self.assertEqual([('id-1', ('old', None), False, (True, False),
1128
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1129
(True, True))], list(transform.iter_changes()))
1130
transform.new_directory('new', root, 'id-1')
1131
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1132
('eert_toor', 'eert_toor'), ('old', 'new'),
1133
('file', 'directory'),
1134
(True, False))], list(transform.iter_changes()))
1136
transform.finalize()
1138
def test_iter_changes_new(self):
1139
self.wt.set_root_id('eert_toor')
1140
transform, root = self.get_transform()
1141
transform.new_file('old', root, 'blah')
1143
transform, root = self.get_transform()
1145
old = transform.trans_id_tree_path('old')
1146
transform.version_file('id-1', old)
1147
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1148
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1149
(False, False))], list(transform.iter_changes()))
1151
transform.finalize()
1153
def test_iter_changes_modifications(self):
1154
self.wt.set_root_id('eert_toor')
1155
transform, root = self.get_transform()
1156
transform.new_file('old', root, 'blah', 'id-1')
1157
transform.new_file('new', root, 'blah')
1158
transform.new_directory('subdir', root, 'subdir-id')
1160
transform, root = self.get_transform()
1162
old = transform.trans_id_tree_path('old')
1163
subdir = transform.trans_id_tree_file_id('subdir-id')
1164
new = transform.trans_id_tree_path('new')
1165
self.assertEqual([], list(transform.iter_changes()))
1168
transform.delete_contents(old)
1169
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1170
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1171
(False, False))], list(transform.iter_changes()))
1174
transform.create_file('blah', old)
1175
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1176
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1177
(False, False))], list(transform.iter_changes()))
1178
transform.cancel_deletion(old)
1179
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1180
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1181
(False, False))], list(transform.iter_changes()))
1182
transform.cancel_creation(old)
1184
# move file_id to a different file
1185
self.assertEqual([], list(transform.iter_changes()))
1186
transform.unversion_file(old)
1187
transform.version_file('id-1', new)
1188
transform.adjust_path('old', root, new)
1189
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1190
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1191
(False, False))], list(transform.iter_changes()))
1192
transform.cancel_versioning(new)
1193
transform._removed_id = set()
1196
self.assertEqual([], list(transform.iter_changes()))
1197
transform.set_executability(True, old)
1198
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1199
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1200
(False, True))], list(transform.iter_changes()))
1201
transform.set_executability(None, old)
1204
self.assertEqual([], list(transform.iter_changes()))
1205
transform.adjust_path('new', root, old)
1206
transform._new_parent = {}
1207
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1208
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1209
(False, False))], list(transform.iter_changes()))
1210
transform._new_name = {}
1213
self.assertEqual([], list(transform.iter_changes()))
1214
transform.adjust_path('new', subdir, old)
1215
transform._new_name = {}
1216
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1217
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1218
('file', 'file'), (False, False))],
1219
list(transform.iter_changes()))
1220
transform._new_path = {}
1223
transform.finalize()
1225
def test_iter_changes_modified_bleed(self):
1226
self.wt.set_root_id('eert_toor')
1227
"""Modified flag should not bleed from one change to another"""
1228
# unfortunately, we have no guarantee that file1 (which is modified)
1229
# will be applied before file2. And if it's applied after file2, it
1230
# obviously can't bleed into file2's change output. But for now, it
1232
transform, root = self.get_transform()
1233
transform.new_file('file1', root, 'blah', 'id-1')
1234
transform.new_file('file2', root, 'blah', 'id-2')
1236
transform, root = self.get_transform()
1238
transform.delete_contents(transform.trans_id_file_id('id-1'))
1239
transform.set_executability(True,
1240
transform.trans_id_file_id('id-2'))
1241
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1242
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1243
('file', None), (False, False)),
1244
('id-2', (u'file2', u'file2'), False, (True, True),
1245
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1246
('file', 'file'), (False, True))],
1247
list(transform.iter_changes()))
1249
transform.finalize()
1251
def test_iter_changes_move_missing(self):
1252
"""Test moving ids with no files around"""
1253
self.wt.set_root_id('toor_eert')
1254
# Need two steps because versioning a non-existant file is a conflict.
1255
transform, root = self.get_transform()
1256
transform.new_directory('floater', root, 'floater-id')
1258
transform, root = self.get_transform()
1259
transform.delete_contents(transform.trans_id_tree_path('floater'))
1261
transform, root = self.get_transform()
1262
floater = transform.trans_id_tree_path('floater')
1264
transform.adjust_path('flitter', root, floater)
1265
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1266
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1267
(None, None), (False, False))], list(transform.iter_changes()))
1269
transform.finalize()
1271
def test_iter_changes_pointless(self):
1272
"""Ensure that no-ops are not treated as modifications"""
1273
self.wt.set_root_id('eert_toor')
1274
transform, root = self.get_transform()
1275
transform.new_file('old', root, 'blah', 'id-1')
1276
transform.new_directory('subdir', root, 'subdir-id')
1278
transform, root = self.get_transform()
1280
old = transform.trans_id_tree_path('old')
1281
subdir = transform.trans_id_tree_file_id('subdir-id')
1282
self.assertEqual([], list(transform.iter_changes()))
1283
transform.delete_contents(subdir)
1284
transform.create_directory(subdir)
1285
transform.set_executability(False, old)
1286
transform.unversion_file(old)
1287
transform.version_file('id-1', old)
1288
transform.adjust_path('old', root, old)
1289
self.assertEqual([], list(transform.iter_changes()))
1291
transform.finalize()
1293
def test_rename_count(self):
1294
transform, root = self.get_transform()
1295
transform.new_file('name1', root, 'contents')
1296
self.assertEqual(transform.rename_count, 0)
1298
self.assertEqual(transform.rename_count, 1)
1299
transform2, root = self.get_transform()
1300
transform2.adjust_path('name2', root,
1301
transform2.trans_id_tree_path('name1'))
1302
self.assertEqual(transform2.rename_count, 0)
1304
self.assertEqual(transform2.rename_count, 2)
1306
def test_change_parent(self):
1307
"""Ensure that after we change a parent, the results are still right.
1309
Renames and parent changes on pending transforms can happen as part
1310
of conflict resolution, and are explicitly permitted by the
1313
This test ensures they work correctly with the rename-avoidance
1316
transform, root = self.get_transform()
1317
parent1 = transform.new_directory('parent1', root)
1318
child1 = transform.new_file('child1', parent1, 'contents')
1319
parent2 = transform.new_directory('parent2', root)
1320
transform.adjust_path('child1', parent2, child1)
1322
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1323
self.assertPathExists(self.wt.abspath('parent2/child1'))
1324
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1325
# no rename for child1 (counting only renames during apply)
1326
self.assertEqual(2, transform.rename_count)
1328
def test_cancel_parent(self):
1329
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1331
This is like the test_change_parent, except that we cancel the parent
1332
before adjusting the path. The transform must detect that the
1333
directory is non-empty, and move children to safe locations.
1335
transform, root = self.get_transform()
1336
parent1 = transform.new_directory('parent1', root)
1337
child1 = transform.new_file('child1', parent1, 'contents')
1338
child2 = transform.new_file('child2', parent1, 'contents')
1340
transform.cancel_creation(parent1)
1342
self.fail('Failed to move child1 before deleting parent1')
1343
transform.cancel_creation(child2)
1344
transform.create_directory(parent1)
1346
transform.cancel_creation(parent1)
1347
# If the transform incorrectly believes that child2 is still in
1348
# parent1's limbo directory, it will try to rename it and fail
1349
# because was already moved by the first cancel_creation.
1351
self.fail('Transform still thinks child2 is a child of parent1')
1352
parent2 = transform.new_directory('parent2', root)
1353
transform.adjust_path('child1', parent2, child1)
1355
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1356
self.assertPathExists(self.wt.abspath('parent2/child1'))
1357
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1358
self.assertEqual(2, transform.rename_count)
1360
def test_adjust_and_cancel(self):
1361
"""Make sure adjust_path keeps track of limbo children properly"""
1362
transform, root = self.get_transform()
1363
parent1 = transform.new_directory('parent1', root)
1364
child1 = transform.new_file('child1', parent1, 'contents')
1365
parent2 = transform.new_directory('parent2', root)
1366
transform.adjust_path('child1', parent2, child1)
1367
transform.cancel_creation(child1)
1369
transform.cancel_creation(parent1)
1370
# if the transform thinks child1 is still in parent1's limbo
1371
# directory, it will attempt to move it and fail.
1373
self.fail('Transform still thinks child1 is a child of parent1')
1374
transform.finalize()
1376
def test_noname_contents(self):
1377
"""TreeTransform should permit deferring naming files."""
1378
transform, root = self.get_transform()
1379
parent = transform.trans_id_file_id('parent-id')
1381
transform.create_directory(parent)
1383
self.fail("Can't handle contents with no name")
1384
transform.finalize()
1386
def test_noname_contents_nested(self):
1387
"""TreeTransform should permit deferring naming files."""
1388
transform, root = self.get_transform()
1389
parent = transform.trans_id_file_id('parent-id')
1391
transform.create_directory(parent)
1393
self.fail("Can't handle contents with no name")
1394
child = transform.new_directory('child', parent)
1395
transform.adjust_path('parent', root, parent)
1397
self.assertPathExists(self.wt.abspath('parent/child'))
1398
self.assertEqual(1, transform.rename_count)
1400
def test_reuse_name(self):
1401
"""Avoid reusing the same limbo name for different files"""
1402
transform, root = self.get_transform()
1403
parent = transform.new_directory('parent', root)
1404
child1 = transform.new_directory('child', parent)
1406
child2 = transform.new_directory('child', parent)
1408
self.fail('Tranform tried to use the same limbo name twice')
1409
transform.adjust_path('child2', parent, child2)
1411
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1412
# child2 is put into top-level limbo because child1 has already
1413
# claimed the direct limbo path when child2 is created. There is no
1414
# advantage in renaming files once they're in top-level limbo, except
1416
self.assertEqual(2, transform.rename_count)
1418
def test_reuse_when_first_moved(self):
1419
"""Don't avoid direct paths when it is safe to use them"""
1420
transform, root = self.get_transform()
1421
parent = transform.new_directory('parent', root)
1422
child1 = transform.new_directory('child', parent)
1423
transform.adjust_path('child1', parent, child1)
1424
child2 = transform.new_directory('child', parent)
1426
# limbo/new-1 => parent
1427
self.assertEqual(1, transform.rename_count)
1429
def test_reuse_after_cancel(self):
1430
"""Don't avoid direct paths when it is safe to use them"""
1431
transform, root = self.get_transform()
1432
parent2 = transform.new_directory('parent2', root)
1433
child1 = transform.new_directory('child1', parent2)
1434
transform.cancel_creation(parent2)
1435
transform.create_directory(parent2)
1436
child2 = transform.new_directory('child1', parent2)
1437
transform.adjust_path('child2', parent2, child1)
1439
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1440
self.assertEqual(2, transform.rename_count)
1442
def test_finalize_order(self):
1443
"""Finalize must be done in child-to-parent order"""
1444
transform, root = self.get_transform()
1445
parent = transform.new_directory('parent', root)
1446
child = transform.new_directory('child', parent)
1448
transform.finalize()
1450
self.fail('Tried to remove parent before child1')
1452
def test_cancel_with_cancelled_child_should_succeed(self):
1453
transform, root = self.get_transform()
1454
parent = transform.new_directory('parent', root)
1455
child = transform.new_directory('child', parent)
1456
transform.cancel_creation(child)
1457
transform.cancel_creation(parent)
1458
transform.finalize()
1460
def test_rollback_on_directory_clash(self):
1462
wt = self.make_branch_and_tree('.')
1463
tt = TreeTransform(wt) # TreeTransform obtains write lock
1465
foo = tt.new_directory('foo', tt.root)
1466
tt.new_file('bar', foo, 'foobar')
1467
baz = tt.new_directory('baz', tt.root)
1468
tt.new_file('qux', baz, 'quux')
1469
# Ask for a rename 'foo' -> 'baz'
1470
tt.adjust_path('baz', tt.root, foo)
1471
# Lie to tt that we've already resolved all conflicts.
1472
tt.apply(no_conflicts=True)
1476
# The rename will fail because the target directory is not empty (but
1477
# raises FileExists anyway).
1478
err = self.assertRaises(errors.FileExists, tt_helper)
1479
self.assertEndsWith(err.path, "/baz")
1481
def test_two_directories_clash(self):
1483
wt = self.make_branch_and_tree('.')
1484
tt = TreeTransform(wt) # TreeTransform obtains write lock
1486
foo_1 = tt.new_directory('foo', tt.root)
1487
tt.new_directory('bar', foo_1)
1488
# Adding the same directory with a different content
1489
foo_2 = tt.new_directory('foo', tt.root)
1490
tt.new_directory('baz', foo_2)
1491
# Lie to tt that we've already resolved all conflicts.
1492
tt.apply(no_conflicts=True)
1496
err = self.assertRaises(errors.FileExists, tt_helper)
1497
self.assertEndsWith(err.path, "/foo")
1499
def test_two_directories_clash_finalize(self):
1501
wt = self.make_branch_and_tree('.')
1502
tt = TreeTransform(wt) # TreeTransform obtains write lock
1504
foo_1 = tt.new_directory('foo', tt.root)
1505
tt.new_directory('bar', foo_1)
1506
# Adding the same directory with a different content
1507
foo_2 = tt.new_directory('foo', tt.root)
1508
tt.new_directory('baz', foo_2)
1509
# Lie to tt that we've already resolved all conflicts.
1510
tt.apply(no_conflicts=True)
1514
err = self.assertRaises(errors.FileExists, tt_helper)
1515
self.assertEndsWith(err.path, "/foo")
1517
def test_file_to_directory(self):
1518
wt = self.make_branch_and_tree('.')
1519
self.build_tree(['foo'])
1522
tt = TreeTransform(wt)
1523
self.addCleanup(tt.finalize)
1524
foo_trans_id = tt.trans_id_tree_path("foo")
1525
tt.delete_contents(foo_trans_id)
1526
tt.create_directory(foo_trans_id)
1527
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1528
tt.create_file(["aa\n"], bar_trans_id)
1529
tt.version_file("bar-1", bar_trans_id)
1531
self.assertPathExists("foo/bar")
1534
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
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.inventory.get_file_kind(wt.path2id("foo")),
1560
def test_dir_to_file(self):
1561
wt = self.make_branch_and_tree('.')
1562
self.build_tree(['foo/', 'foo/bar'])
1563
wt.add(['foo', 'foo/bar'])
1565
tt = TreeTransform(wt)
1566
self.addCleanup(tt.finalize)
1567
foo_trans_id = tt.trans_id_tree_path("foo")
1568
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1569
tt.delete_contents(foo_trans_id)
1570
tt.delete_versioned(bar_trans_id)
1571
tt.create_file(["aa\n"], foo_trans_id)
1573
self.assertPathExists("foo")
1575
self.addCleanup(wt.unlock)
1576
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1579
def test_dir_to_hardlink(self):
1580
self.requireFeature(HardlinkFeature)
1581
wt = self.make_branch_and_tree('.')
1582
self.build_tree(['foo/', 'foo/bar'])
1583
wt.add(['foo', 'foo/bar'])
1585
tt = TreeTransform(wt)
1586
self.addCleanup(tt.finalize)
1587
foo_trans_id = tt.trans_id_tree_path("foo")
1588
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1589
tt.delete_contents(foo_trans_id)
1590
tt.delete_versioned(bar_trans_id)
1591
self.build_tree(['baz'])
1592
tt.create_hardlink("baz", foo_trans_id)
1594
self.assertPathExists("foo")
1595
self.assertPathExists("baz")
1597
self.addCleanup(wt.unlock)
1598
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1601
def test_no_final_path(self):
1602
transform, root = self.get_transform()
1603
trans_id = transform.trans_id_file_id('foo')
1604
transform.create_file('bar', trans_id)
1605
transform.cancel_creation(trans_id)
1608
def test_create_from_tree(self):
1609
tree1 = self.make_branch_and_tree('tree1')
1610
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1611
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1612
tree2 = self.make_branch_and_tree('tree2')
1613
tt = TreeTransform(tree2)
1614
foo_trans_id = tt.create_path('foo', tt.root)
1615
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1616
bar_trans_id = tt.create_path('bar', tt.root)
1617
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1619
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1620
self.assertFileEqual('baz', 'tree2/bar')
1622
def test_create_from_tree_bytes(self):
1623
"""Provided lines are used instead of tree content."""
1624
tree1 = self.make_branch_and_tree('tree1')
1625
self.build_tree_contents([('tree1/foo', 'bar'),])
1626
tree1.add('foo', 'foo-id')
1627
tree2 = self.make_branch_and_tree('tree2')
1628
tt = TreeTransform(tree2)
1629
foo_trans_id = tt.create_path('foo', tt.root)
1630
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1632
self.assertFileEqual('qux', 'tree2/foo')
1634
def test_create_from_tree_symlink(self):
1635
self.requireFeature(SymlinkFeature)
1636
tree1 = self.make_branch_and_tree('tree1')
1637
os.symlink('bar', 'tree1/foo')
1638
tree1.add('foo', 'foo-id')
1639
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1640
foo_trans_id = tt.create_path('foo', tt.root)
1641
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1643
self.assertEqual('bar', os.readlink('tree2/foo'))
1646
class TransformGroup(object):
1648
def __init__(self, dirname, root_id):
1651
self.wt = BzrDir.create_standalone_workingtree(dirname)
1652
self.wt.set_root_id(root_id)
1653
self.b = self.wt.branch
1654
self.tt = TreeTransform(self.wt)
1655
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1658
def conflict_text(tree, merge):
1659
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1660
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1663
class TestInventoryAltered(tests.TestCaseWithTransport):
1665
def test_inventory_altered_unchanged(self):
1666
tree = self.make_branch_and_tree('tree')
1667
self.build_tree(['tree/foo'])
1668
tree.add('foo', 'foo-id')
1669
with TransformPreview(tree) as tt:
1670
self.assertEqual([], tt._inventory_altered())
1672
def test_inventory_altered_changed_parent_id(self):
1673
tree = self.make_branch_and_tree('tree')
1674
self.build_tree(['tree/foo'])
1675
tree.add('foo', 'foo-id')
1676
with TransformPreview(tree) as tt:
1677
tt.unversion_file(tt.root)
1678
tt.version_file('new-id', tt.root)
1679
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1680
foo_tuple = ('foo', foo_trans_id)
1681
root_tuple = ('', tt.root)
1682
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1684
def test_inventory_altered_noop_changed_parent_id(self):
1685
tree = self.make_branch_and_tree('tree')
1686
self.build_tree(['tree/foo'])
1687
tree.add('foo', 'foo-id')
1688
with TransformPreview(tree) as tt:
1689
tt.unversion_file(tt.root)
1690
tt.version_file(tree.get_root_id(), tt.root)
1691
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1692
self.assertEqual([], tt._inventory_altered())
1695
class TestTransformMerge(TestCaseInTempDir):
1697
def test_text_merge(self):
1698
root_id = generate_ids.gen_root_id()
1699
base = TransformGroup("base", root_id)
1700
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1701
base.tt.new_file('b', base.root, 'b1', 'b')
1702
base.tt.new_file('c', base.root, 'c', 'c')
1703
base.tt.new_file('d', base.root, 'd', 'd')
1704
base.tt.new_file('e', base.root, 'e', 'e')
1705
base.tt.new_file('f', base.root, 'f', 'f')
1706
base.tt.new_directory('g', base.root, 'g')
1707
base.tt.new_directory('h', base.root, 'h')
1709
other = TransformGroup("other", root_id)
1710
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1711
other.tt.new_file('b', other.root, 'b2', 'b')
1712
other.tt.new_file('c', other.root, 'c2', 'c')
1713
other.tt.new_file('d', other.root, 'd', 'd')
1714
other.tt.new_file('e', other.root, 'e2', 'e')
1715
other.tt.new_file('f', other.root, 'f', 'f')
1716
other.tt.new_file('g', other.root, 'g', 'g')
1717
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1718
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1720
this = TransformGroup("this", root_id)
1721
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1722
this.tt.new_file('b', this.root, 'b', 'b')
1723
this.tt.new_file('c', this.root, 'c', 'c')
1724
this.tt.new_file('d', this.root, 'd2', 'd')
1725
this.tt.new_file('e', this.root, 'e2', 'e')
1726
this.tt.new_file('f', this.root, 'f', 'f')
1727
this.tt.new_file('g', this.root, 'g', 'g')
1728
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1729
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1731
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1734
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1735
# three-way text conflict
1736
self.assertEqual(this.wt.get_file('b').read(),
1737
conflict_text('b', 'b2'))
1739
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1741
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1742
# Ambigious clean merge
1743
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1745
self.assertEqual(this.wt.get_file('f').read(), 'f')
1746
# Correct correct results when THIS == OTHER
1747
self.assertEqual(this.wt.get_file('g').read(), 'g')
1748
# Text conflict when THIS & OTHER are text and BASE is dir
1749
self.assertEqual(this.wt.get_file('h').read(),
1750
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1751
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1753
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1755
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1756
self.assertEqual(this.wt.get_file('i').read(),
1757
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1758
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1760
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1762
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1763
modified = ['a', 'b', 'c', 'h', 'i']
1764
merge_modified = this.wt.merge_modified()
1765
self.assertSubset(merge_modified, modified)
1766
self.assertEqual(len(merge_modified), len(modified))
1767
file(this.wt.id2abspath('a'), 'wb').write('booga')
1769
merge_modified = this.wt.merge_modified()
1770
self.assertSubset(merge_modified, modified)
1771
self.assertEqual(len(merge_modified), len(modified))
1775
def test_file_merge(self):
1776
self.requireFeature(SymlinkFeature)
1777
root_id = generate_ids.gen_root_id()
1778
base = TransformGroup("BASE", root_id)
1779
this = TransformGroup("THIS", root_id)
1780
other = TransformGroup("OTHER", root_id)
1781
for tg in this, base, other:
1782
tg.tt.new_directory('a', tg.root, 'a')
1783
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1784
tg.tt.new_file('c', tg.root, 'c', 'c')
1785
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1786
targets = ((base, 'base-e', 'base-f', None, None),
1787
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1788
(other, 'other-e', None, 'other-g', 'other-h'))
1789
for tg, e_target, f_target, g_target, h_target in targets:
1790
for link, target in (('e', e_target), ('f', f_target),
1791
('g', g_target), ('h', h_target)):
1792
if target is not None:
1793
tg.tt.new_symlink(link, tg.root, target, link)
1795
for tg in this, base, other:
1797
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1798
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1799
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1800
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1801
for suffix in ('THIS', 'BASE', 'OTHER'):
1802
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1803
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1804
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1805
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1806
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1807
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1808
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1809
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1810
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1811
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1812
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1813
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1814
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1815
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1817
def test_filename_merge(self):
1818
root_id = generate_ids.gen_root_id()
1819
base = TransformGroup("BASE", root_id)
1820
this = TransformGroup("THIS", root_id)
1821
other = TransformGroup("OTHER", root_id)
1822
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1823
for t in [base, this, other]]
1824
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1825
for t in [base, this, other]]
1826
base.tt.new_directory('c', base_a, 'c')
1827
this.tt.new_directory('c1', this_a, 'c')
1828
other.tt.new_directory('c', other_b, 'c')
1830
base.tt.new_directory('d', base_a, 'd')
1831
this.tt.new_directory('d1', this_b, 'd')
1832
other.tt.new_directory('d', other_a, 'd')
1834
base.tt.new_directory('e', base_a, 'e')
1835
this.tt.new_directory('e', this_a, 'e')
1836
other.tt.new_directory('e1', other_b, 'e')
1838
base.tt.new_directory('f', base_a, 'f')
1839
this.tt.new_directory('f1', this_b, 'f')
1840
other.tt.new_directory('f1', other_b, 'f')
1842
for tg in [this, base, other]:
1844
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1845
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1846
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1847
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1848
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1850
def test_filename_merge_conflicts(self):
1851
root_id = generate_ids.gen_root_id()
1852
base = TransformGroup("BASE", root_id)
1853
this = TransformGroup("THIS", root_id)
1854
other = TransformGroup("OTHER", root_id)
1855
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1856
for t in [base, this, other]]
1857
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1858
for t in [base, this, other]]
1860
base.tt.new_file('g', base_a, 'g', 'g')
1861
other.tt.new_file('g1', other_b, 'g1', 'g')
1863
base.tt.new_file('h', base_a, 'h', 'h')
1864
this.tt.new_file('h1', this_b, 'h1', 'h')
1866
base.tt.new_file('i', base.root, 'i', 'i')
1867
other.tt.new_directory('i1', this_b, 'i')
1869
for tg in [this, base, other]:
1871
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1873
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1874
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1875
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1876
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1877
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1878
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1879
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1882
class TestBuildTree(tests.TestCaseWithTransport):
1884
def test_build_tree_with_symlinks(self):
1885
self.requireFeature(SymlinkFeature)
1887
a = BzrDir.create_standalone_workingtree('a')
1889
file('a/foo/bar', 'wb').write('contents')
1890
os.symlink('a/foo/bar', 'a/foo/baz')
1891
a.add(['foo', 'foo/bar', 'foo/baz'])
1892
a.commit('initial commit')
1893
b = BzrDir.create_standalone_workingtree('b')
1894
basis = a.basis_tree()
1896
self.addCleanup(basis.unlock)
1897
build_tree(basis, b)
1898
self.assertIs(os.path.isdir('b/foo'), True)
1899
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1900
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1902
def test_build_with_references(self):
1903
tree = self.make_branch_and_tree('source',
1904
format='dirstate-with-subtree')
1905
subtree = self.make_branch_and_tree('source/subtree',
1906
format='dirstate-with-subtree')
1907
tree.add_reference(subtree)
1908
tree.commit('a revision')
1909
tree.branch.create_checkout('target')
1910
self.assertPathExists('target')
1911
self.assertPathExists('target/subtree')
1913
def test_file_conflict_handling(self):
1914
"""Ensure that when building trees, conflict handling is done"""
1915
source = self.make_branch_and_tree('source')
1916
target = self.make_branch_and_tree('target')
1917
self.build_tree(['source/file', 'target/file'])
1918
source.add('file', 'new-file')
1919
source.commit('added file')
1920
build_tree(source.basis_tree(), target)
1921
self.assertEqual([DuplicateEntry('Moved existing file to',
1922
'file.moved', 'file', None, 'new-file')],
1924
target2 = self.make_branch_and_tree('target2')
1925
target_file = file('target2/file', 'wb')
1927
source_file = file('source/file', 'rb')
1929
target_file.write(source_file.read())
1934
build_tree(source.basis_tree(), target2)
1935
self.assertEqual([], target2.conflicts())
1937
def test_symlink_conflict_handling(self):
1938
"""Ensure that when building trees, conflict handling is done"""
1939
self.requireFeature(SymlinkFeature)
1940
source = self.make_branch_and_tree('source')
1941
os.symlink('foo', 'source/symlink')
1942
source.add('symlink', 'new-symlink')
1943
source.commit('added file')
1944
target = self.make_branch_and_tree('target')
1945
os.symlink('bar', 'target/symlink')
1946
build_tree(source.basis_tree(), target)
1947
self.assertEqual([DuplicateEntry('Moved existing file to',
1948
'symlink.moved', 'symlink', None, 'new-symlink')],
1950
target = self.make_branch_and_tree('target2')
1951
os.symlink('foo', 'target2/symlink')
1952
build_tree(source.basis_tree(), target)
1953
self.assertEqual([], target.conflicts())
1955
def test_directory_conflict_handling(self):
1956
"""Ensure that when building trees, conflict handling is done"""
1957
source = self.make_branch_and_tree('source')
1958
target = self.make_branch_and_tree('target')
1959
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1960
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1961
source.commit('added file')
1962
build_tree(source.basis_tree(), target)
1963
self.assertEqual([], target.conflicts())
1964
self.assertPathExists('target/dir1/file')
1966
# Ensure contents are merged
1967
target = self.make_branch_and_tree('target2')
1968
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1969
build_tree(source.basis_tree(), target)
1970
self.assertEqual([], target.conflicts())
1971
self.assertPathExists('target2/dir1/file2')
1972
self.assertPathExists('target2/dir1/file')
1974
# Ensure new contents are suppressed for existing branches
1975
target = self.make_branch_and_tree('target3')
1976
self.make_branch('target3/dir1')
1977
self.build_tree(['target3/dir1/file2'])
1978
build_tree(source.basis_tree(), target)
1979
self.assertPathDoesNotExist('target3/dir1/file')
1980
self.assertPathExists('target3/dir1/file2')
1981
self.assertPathExists('target3/dir1.diverted/file')
1982
self.assertEqual([DuplicateEntry('Diverted to',
1983
'dir1.diverted', 'dir1', 'new-dir1', None)],
1986
target = self.make_branch_and_tree('target4')
1987
self.build_tree(['target4/dir1/'])
1988
self.make_branch('target4/dir1/file')
1989
build_tree(source.basis_tree(), target)
1990
self.assertPathExists('target4/dir1/file')
1991
self.assertEqual('directory', file_kind('target4/dir1/file'))
1992
self.assertPathExists('target4/dir1/file.diverted')
1993
self.assertEqual([DuplicateEntry('Diverted to',
1994
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1997
def test_mixed_conflict_handling(self):
1998
"""Ensure that when building trees, conflict handling is done"""
1999
source = self.make_branch_and_tree('source')
2000
target = self.make_branch_and_tree('target')
2001
self.build_tree(['source/name', 'target/name/'])
2002
source.add('name', 'new-name')
2003
source.commit('added file')
2004
build_tree(source.basis_tree(), target)
2005
self.assertEqual([DuplicateEntry('Moved existing file to',
2006
'name.moved', 'name', None, 'new-name')], target.conflicts())
2008
def test_raises_in_populated(self):
2009
source = self.make_branch_and_tree('source')
2010
self.build_tree(['source/name'])
2012
source.commit('added name')
2013
target = self.make_branch_and_tree('target')
2014
self.build_tree(['target/name'])
2016
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2017
build_tree, source.basis_tree(), target)
2019
def test_build_tree_rename_count(self):
2020
source = self.make_branch_and_tree('source')
2021
self.build_tree(['source/file1', 'source/dir1/'])
2022
source.add(['file1', 'dir1'])
2023
source.commit('add1')
2024
target1 = self.make_branch_and_tree('target1')
2025
transform_result = build_tree(source.basis_tree(), target1)
2026
self.assertEqual(2, transform_result.rename_count)
2028
self.build_tree(['source/dir1/file2'])
2029
source.add(['dir1/file2'])
2030
source.commit('add3')
2031
target2 = self.make_branch_and_tree('target2')
2032
transform_result = build_tree(source.basis_tree(), target2)
2033
# children of non-root directories should not be renamed
2034
self.assertEqual(2, transform_result.rename_count)
2036
def create_ab_tree(self):
2037
"""Create a committed test tree with two files"""
2038
source = self.make_branch_and_tree('source')
2039
self.build_tree_contents([('source/file1', 'A')])
2040
self.build_tree_contents([('source/file2', 'B')])
2041
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2042
source.commit('commit files')
2044
self.addCleanup(source.unlock)
2047
def test_build_tree_accelerator_tree(self):
2048
source = self.create_ab_tree()
2049
self.build_tree_contents([('source/file2', 'C')])
2051
real_source_get_file = source.get_file
2052
def get_file(file_id, path=None):
2053
calls.append(file_id)
2054
return real_source_get_file(file_id, path)
2055
source.get_file = get_file
2056
target = self.make_branch_and_tree('target')
2057
revision_tree = source.basis_tree()
2058
revision_tree.lock_read()
2059
self.addCleanup(revision_tree.unlock)
2060
build_tree(revision_tree, target, source)
2061
self.assertEqual(['file1-id'], calls)
2063
self.addCleanup(target.unlock)
2064
self.assertEqual([], list(target.iter_changes(revision_tree)))
2066
def test_build_tree_accelerator_tree_observes_sha1(self):
2067
source = self.create_ab_tree()
2068
sha1 = osutils.sha_string('A')
2069
target = self.make_branch_and_tree('target')
2071
self.addCleanup(target.unlock)
2072
state = target.current_dirstate()
2073
state._cutoff_time = time.time() + 60
2074
build_tree(source.basis_tree(), target, source)
2075
entry = state._get_entry(0, path_utf8='file1')
2076
self.assertEqual(sha1, entry[1][0][1])
2078
def test_build_tree_accelerator_tree_missing_file(self):
2079
source = self.create_ab_tree()
2080
os.unlink('source/file1')
2081
source.remove(['file2'])
2082
target = self.make_branch_and_tree('target')
2083
revision_tree = source.basis_tree()
2084
revision_tree.lock_read()
2085
self.addCleanup(revision_tree.unlock)
2086
build_tree(revision_tree, target, source)
2088
self.addCleanup(target.unlock)
2089
self.assertEqual([], list(target.iter_changes(revision_tree)))
2091
def test_build_tree_accelerator_wrong_kind(self):
2092
self.requireFeature(SymlinkFeature)
2093
source = self.make_branch_and_tree('source')
2094
self.build_tree_contents([('source/file1', '')])
2095
self.build_tree_contents([('source/file2', '')])
2096
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2097
source.commit('commit files')
2098
os.unlink('source/file2')
2099
self.build_tree_contents([('source/file2/', 'C')])
2100
os.unlink('source/file1')
2101
os.symlink('file2', 'source/file1')
2103
real_source_get_file = source.get_file
2104
def get_file(file_id, path=None):
2105
calls.append(file_id)
2106
return real_source_get_file(file_id, path)
2107
source.get_file = get_file
2108
target = self.make_branch_and_tree('target')
2109
revision_tree = source.basis_tree()
2110
revision_tree.lock_read()
2111
self.addCleanup(revision_tree.unlock)
2112
build_tree(revision_tree, target, source)
2113
self.assertEqual([], calls)
2115
self.addCleanup(target.unlock)
2116
self.assertEqual([], list(target.iter_changes(revision_tree)))
2118
def test_build_tree_hardlink(self):
2119
self.requireFeature(HardlinkFeature)
2120
source = self.create_ab_tree()
2121
target = self.make_branch_and_tree('target')
2122
revision_tree = source.basis_tree()
2123
revision_tree.lock_read()
2124
self.addCleanup(revision_tree.unlock)
2125
build_tree(revision_tree, target, source, hardlink=True)
2127
self.addCleanup(target.unlock)
2128
self.assertEqual([], list(target.iter_changes(revision_tree)))
2129
source_stat = os.stat('source/file1')
2130
target_stat = os.stat('target/file1')
2131
self.assertEqual(source_stat, target_stat)
2133
# Explicitly disallowing hardlinks should prevent them.
2134
target2 = self.make_branch_and_tree('target2')
2135
build_tree(revision_tree, target2, source, hardlink=False)
2137
self.addCleanup(target2.unlock)
2138
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2139
source_stat = os.stat('source/file1')
2140
target2_stat = os.stat('target2/file1')
2141
self.assertNotEqual(source_stat, target2_stat)
2143
def test_build_tree_accelerator_tree_moved(self):
2144
source = self.make_branch_and_tree('source')
2145
self.build_tree_contents([('source/file1', 'A')])
2146
source.add(['file1'], ['file1-id'])
2147
source.commit('commit files')
2148
source.rename_one('file1', 'file2')
2150
self.addCleanup(source.unlock)
2151
target = self.make_branch_and_tree('target')
2152
revision_tree = source.basis_tree()
2153
revision_tree.lock_read()
2154
self.addCleanup(revision_tree.unlock)
2155
build_tree(revision_tree, target, source)
2157
self.addCleanup(target.unlock)
2158
self.assertEqual([], list(target.iter_changes(revision_tree)))
2160
def test_build_tree_hardlinks_preserve_execute(self):
2161
self.requireFeature(HardlinkFeature)
2162
source = self.create_ab_tree()
2163
tt = TreeTransform(source)
2164
trans_id = tt.trans_id_tree_file_id('file1-id')
2165
tt.set_executability(True, trans_id)
2167
self.assertTrue(source.is_executable('file1-id'))
2168
target = self.make_branch_and_tree('target')
2169
revision_tree = source.basis_tree()
2170
revision_tree.lock_read()
2171
self.addCleanup(revision_tree.unlock)
2172
build_tree(revision_tree, target, source, hardlink=True)
2174
self.addCleanup(target.unlock)
2175
self.assertEqual([], list(target.iter_changes(revision_tree)))
2176
self.assertTrue(source.is_executable('file1-id'))
2178
def install_rot13_content_filter(self, pattern):
2180
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2181
# below, but that looks a bit... hard to read even if it's exactly
2183
original_registry = filters._reset_registry()
2184
def restore_registry():
2185
filters._reset_registry(original_registry)
2186
self.addCleanup(restore_registry)
2187
def rot13(chunks, context=None):
2188
return [''.join(chunks).encode('rot13')]
2189
rot13filter = filters.ContentFilter(rot13, rot13)
2190
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2191
os.mkdir(self.test_home_dir + '/.bazaar')
2192
rules_filename = self.test_home_dir + '/.bazaar/rules'
2193
f = open(rules_filename, 'wb')
2194
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2196
def uninstall_rules():
2197
os.remove(rules_filename)
2199
self.addCleanup(uninstall_rules)
2202
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2203
"""build_tree will not hardlink files that have content filtering rules
2204
applied to them (but will still hardlink other files from the same tree
2207
self.requireFeature(HardlinkFeature)
2208
self.install_rot13_content_filter('file1')
2209
source = self.create_ab_tree()
2210
target = self.make_branch_and_tree('target')
2211
revision_tree = source.basis_tree()
2212
revision_tree.lock_read()
2213
self.addCleanup(revision_tree.unlock)
2214
build_tree(revision_tree, target, source, hardlink=True)
2216
self.addCleanup(target.unlock)
2217
self.assertEqual([], list(target.iter_changes(revision_tree)))
2218
source_stat = os.stat('source/file1')
2219
target_stat = os.stat('target/file1')
2220
self.assertNotEqual(source_stat, target_stat)
2221
source_stat = os.stat('source/file2')
2222
target_stat = os.stat('target/file2')
2223
self.assertEqualStat(source_stat, target_stat)
2225
def test_case_insensitive_build_tree_inventory(self):
2226
if (features.CaseInsensitiveFilesystemFeature.available()
2227
or features.CaseInsCasePresFilenameFeature.available()):
2228
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2229
source = self.make_branch_and_tree('source')
2230
self.build_tree(['source/file', 'source/FILE'])
2231
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2232
source.commit('added files')
2233
# Don't try this at home, kids!
2234
# Force the tree to report that it is case insensitive
2235
target = self.make_branch_and_tree('target')
2236
target.case_sensitive = False
2237
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2238
self.assertEqual('file.moved', target.id2path('lower-id'))
2239
self.assertEqual('FILE', target.id2path('upper-id'))
2241
def test_build_tree_observes_sha(self):
2242
source = self.make_branch_and_tree('source')
2243
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2244
source.add(['file1', 'dir', 'dir/file2'],
2245
['file1-id', 'dir-id', 'file2-id'])
2246
source.commit('new files')
2247
target = self.make_branch_and_tree('target')
2249
self.addCleanup(target.unlock)
2250
# We make use of the fact that DirState caches its cutoff time. So we
2251
# set the 'safe' time to one minute in the future.
2252
state = target.current_dirstate()
2253
state._cutoff_time = time.time() + 60
2254
build_tree(source.basis_tree(), target)
2255
entry1_sha = osutils.sha_file_by_name('source/file1')
2256
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2257
# entry[1] is the state information, entry[1][0] is the state of the
2258
# working tree, entry[1][0][1] is the sha value for the current working
2260
entry1 = state._get_entry(0, path_utf8='file1')
2261
self.assertEqual(entry1_sha, entry1[1][0][1])
2262
# The 'size' field must also be set.
2263
self.assertEqual(25, entry1[1][0][2])
2264
entry1_state = entry1[1][0]
2265
entry2 = state._get_entry(0, path_utf8='dir/file2')
2266
self.assertEqual(entry2_sha, entry2[1][0][1])
2267
self.assertEqual(29, entry2[1][0][2])
2268
entry2_state = entry2[1][0]
2269
# Now, make sure that we don't have to re-read the content. The
2270
# packed_stat should match exactly.
2271
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2272
self.assertEqual(entry2_sha,
2273
target.get_file_sha1('file2-id', 'dir/file2'))
2274
self.assertEqual(entry1_state, entry1[1][0])
2275
self.assertEqual(entry2_state, entry2[1][0])
2278
class TestCommitTransform(tests.TestCaseWithTransport):
2280
def get_branch(self):
2281
tree = self.make_branch_and_tree('tree')
2283
self.addCleanup(tree.unlock)
2284
tree.commit('empty commit')
2287
def get_branch_and_transform(self):
2288
branch = self.get_branch()
2289
tt = TransformPreview(branch.basis_tree())
2290
self.addCleanup(tt.finalize)
2293
def test_commit_wrong_basis(self):
2294
branch = self.get_branch()
2295
basis = branch.repository.revision_tree(
2296
_mod_revision.NULL_REVISION)
2297
tt = TransformPreview(basis)
2298
self.addCleanup(tt.finalize)
2299
e = self.assertRaises(ValueError, tt.commit, branch, '')
2300
self.assertEqual('TreeTransform not based on branch basis: null:',
2303
def test_empy_commit(self):
2304
branch, tt = self.get_branch_and_transform()
2305
rev = tt.commit(branch, 'my message')
2306
self.assertEqual(2, branch.revno())
2307
repo = branch.repository
2308
self.assertEqual('my message', repo.get_revision(rev).message)
2310
def test_merge_parents(self):
2311
branch, tt = self.get_branch_and_transform()
2312
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2313
self.assertEqual(['rev1b', 'rev1c'],
2314
branch.basis_tree().get_parent_ids()[1:])
2316
def test_first_commit(self):
2317
branch = self.make_branch('branch')
2319
self.addCleanup(branch.unlock)
2320
tt = TransformPreview(branch.basis_tree())
2321
self.addCleanup(tt.finalize)
2322
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2323
rev = tt.commit(branch, 'my message')
2324
self.assertEqual([], branch.basis_tree().get_parent_ids())
2325
self.assertNotEqual(_mod_revision.NULL_REVISION,
2326
branch.last_revision())
2328
def test_first_commit_with_merge_parents(self):
2329
branch = self.make_branch('branch')
2331
self.addCleanup(branch.unlock)
2332
tt = TransformPreview(branch.basis_tree())
2333
self.addCleanup(tt.finalize)
2334
e = self.assertRaises(ValueError, tt.commit, branch,
2335
'my message', ['rev1b-id'])
2336
self.assertEqual('Cannot supply merge parents for first commit.',
2338
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2340
def test_add_files(self):
2341
branch, tt = self.get_branch_and_transform()
2342
tt.new_file('file', tt.root, 'contents', 'file-id')
2343
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2344
if SymlinkFeature.available():
2345
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2346
rev = tt.commit(branch, 'message')
2347
tree = branch.basis_tree()
2348
self.assertEqual('file', tree.id2path('file-id'))
2349
self.assertEqual('contents', tree.get_file_text('file-id'))
2350
self.assertEqual('dir', tree.id2path('dir-id'))
2351
if SymlinkFeature.available():
2352
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2353
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2355
def test_add_unversioned(self):
2356
branch, tt = self.get_branch_and_transform()
2357
tt.new_file('file', tt.root, 'contents')
2358
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2359
'message', strict=True)
2361
def test_modify_strict(self):
2362
branch, tt = self.get_branch_and_transform()
2363
tt.new_file('file', tt.root, 'contents', 'file-id')
2364
tt.commit(branch, 'message', strict=True)
2365
tt = TransformPreview(branch.basis_tree())
2366
self.addCleanup(tt.finalize)
2367
trans_id = tt.trans_id_file_id('file-id')
2368
tt.delete_contents(trans_id)
2369
tt.create_file('contents', trans_id)
2370
tt.commit(branch, 'message', strict=True)
2372
def test_commit_malformed(self):
2373
"""Committing a malformed transform should raise an exception.
2375
In this case, we are adding a file without adding its parent.
2377
branch, tt = self.get_branch_and_transform()
2378
parent_id = tt.trans_id_file_id('parent-id')
2379
tt.new_file('file', parent_id, 'contents', 'file-id')
2380
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2383
def test_commit_rich_revision_data(self):
2384
branch, tt = self.get_branch_and_transform()
2385
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2386
committer='me <me@example.com>',
2387
revprops={'foo': 'bar'}, revision_id='revid-1',
2388
authors=['Author1 <author1@example.com>',
2389
'Author2 <author2@example.com>',
2391
self.assertEqual('revid-1', rev_id)
2392
revision = branch.repository.get_revision(rev_id)
2393
self.assertEqual(1, revision.timestamp)
2394
self.assertEqual(43201, revision.timezone)
2395
self.assertEqual('me <me@example.com>', revision.committer)
2396
self.assertEqual(['Author1 <author1@example.com>',
2397
'Author2 <author2@example.com>'],
2398
revision.get_apparent_authors())
2399
del revision.properties['authors']
2400
self.assertEqual({'foo': 'bar',
2401
'branch-nick': 'tree'},
2402
revision.properties)
2404
def test_no_explicit_revprops(self):
2405
branch, tt = self.get_branch_and_transform()
2406
rev_id = tt.commit(branch, 'message', authors=[
2407
'Author1 <author1@example.com>',
2408
'Author2 <author2@example.com>', ])
2409
revision = branch.repository.get_revision(rev_id)
2410
self.assertEqual(['Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>'],
2412
revision.get_apparent_authors())
2413
self.assertEqual('tree', revision.properties['branch-nick'])
2416
class TestBackupName(tests.TestCase):
2418
def test_deprecations(self):
2419
class MockTransform(object):
2421
def has_named_child(self, by_parent, parent_id, name):
2422
return name in by_parent.get(parent_id, [])
2424
class MockEntry(object):
2427
object.__init__(self)
2430
tt = MockTransform()
2431
name1 = self.applyDeprecated(
2432
symbol_versioning.deprecated_in((2, 3, 0)),
2433
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2434
self.assertEqual('name.~1~', name1)
2435
name2 = self.applyDeprecated(
2436
symbol_versioning.deprecated_in((2, 3, 0)),
2437
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2438
self.assertEqual('name.~2~', name2)
2441
class TestFileMover(tests.TestCaseWithTransport):
2443
def test_file_mover(self):
2444
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2445
mover = _FileMover()
2446
mover.rename('a', 'q')
2447
self.assertPathExists('q')
2448
self.assertPathDoesNotExist('a')
2449
self.assertPathExists('q/b')
2450
self.assertPathExists('c')
2451
self.assertPathExists('c/d')
2453
def test_pre_delete_rollback(self):
2454
self.build_tree(['a/'])
2455
mover = _FileMover()
2456
mover.pre_delete('a', 'q')
2457
self.assertPathExists('q')
2458
self.assertPathDoesNotExist('a')
2460
self.assertPathDoesNotExist('q')
2461
self.assertPathExists('a')
2463
def test_apply_deletions(self):
2464
self.build_tree(['a/', 'b/'])
2465
mover = _FileMover()
2466
mover.pre_delete('a', 'q')
2467
mover.pre_delete('b', 'r')
2468
self.assertPathExists('q')
2469
self.assertPathExists('r')
2470
self.assertPathDoesNotExist('a')
2471
self.assertPathDoesNotExist('b')
2472
mover.apply_deletions()
2473
self.assertPathDoesNotExist('q')
2474
self.assertPathDoesNotExist('r')
2475
self.assertPathDoesNotExist('a')
2476
self.assertPathDoesNotExist('b')
2478
def test_file_mover_rollback(self):
2479
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2480
mover = _FileMover()
2481
mover.rename('c/d', 'c/f')
2482
mover.rename('c/e', 'c/d')
2484
mover.rename('a', 'c')
2485
except errors.FileExists, e:
2487
self.assertPathExists('a')
2488
self.assertPathExists('c/d')
2491
class Bogus(Exception):
2495
class TestTransformRollback(tests.TestCaseWithTransport):
2497
class ExceptionFileMover(_FileMover):
2499
def __init__(self, bad_source=None, bad_target=None):
2500
_FileMover.__init__(self)
2501
self.bad_source = bad_source
2502
self.bad_target = bad_target
2504
def rename(self, source, target):
2505
if (self.bad_source is not None and
2506
source.endswith(self.bad_source)):
2508
elif (self.bad_target is not None and
2509
target.endswith(self.bad_target)):
2512
_FileMover.rename(self, source, target)
2514
def test_rollback_rename(self):
2515
tree = self.make_branch_and_tree('.')
2516
self.build_tree(['a/', 'a/b'])
2517
tt = TreeTransform(tree)
2518
self.addCleanup(tt.finalize)
2519
a_id = tt.trans_id_tree_path('a')
2520
tt.adjust_path('c', tt.root, a_id)
2521
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2522
self.assertRaises(Bogus, tt.apply,
2523
_mover=self.ExceptionFileMover(bad_source='a'))
2524
self.assertPathExists('a')
2525
self.assertPathExists('a/b')
2527
self.assertPathExists('c')
2528
self.assertPathExists('c/d')
2530
def test_rollback_rename_into_place(self):
2531
tree = self.make_branch_and_tree('.')
2532
self.build_tree(['a/', 'a/b'])
2533
tt = TreeTransform(tree)
2534
self.addCleanup(tt.finalize)
2535
a_id = tt.trans_id_tree_path('a')
2536
tt.adjust_path('c', tt.root, a_id)
2537
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2538
self.assertRaises(Bogus, tt.apply,
2539
_mover=self.ExceptionFileMover(bad_target='c/d'))
2540
self.assertPathExists('a')
2541
self.assertPathExists('a/b')
2543
self.assertPathExists('c')
2544
self.assertPathExists('c/d')
2546
def test_rollback_deletion(self):
2547
tree = self.make_branch_and_tree('.')
2548
self.build_tree(['a/', 'a/b'])
2549
tt = TreeTransform(tree)
2550
self.addCleanup(tt.finalize)
2551
a_id = tt.trans_id_tree_path('a')
2552
tt.delete_contents(a_id)
2553
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2554
self.assertRaises(Bogus, tt.apply,
2555
_mover=self.ExceptionFileMover(bad_target='d'))
2556
self.assertPathExists('a')
2557
self.assertPathExists('a/b')
2560
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2561
"""Ensure treetransform creation errors can be safely cleaned up after"""
2563
def _override_globals_in_method(self, instance, method_name, globals):
2564
"""Replace method on instance with one with updated globals"""
2566
func = getattr(instance, method_name).im_func
2567
new_globals = dict(func.func_globals)
2568
new_globals.update(globals)
2569
new_func = types.FunctionType(func.func_code, new_globals,
2570
func.func_name, func.func_defaults)
2571
setattr(instance, method_name,
2572
types.MethodType(new_func, instance, instance.__class__))
2573
self.addCleanup(delattr, instance, method_name)
2576
def _fake_open_raises_before(name, mode):
2577
"""Like open() but raises before doing anything"""
2581
def _fake_open_raises_after(name, mode):
2582
"""Like open() but raises after creating file without returning"""
2583
open(name, mode).close()
2586
def create_transform_and_root_trans_id(self):
2587
"""Setup a transform creating a file in limbo"""
2588
tree = self.make_branch_and_tree('.')
2589
tt = TreeTransform(tree)
2590
return tt, tt.create_path("a", tt.root)
2592
def create_transform_and_subdir_trans_id(self):
2593
"""Setup a transform creating a directory containing a file in limbo"""
2594
tree = self.make_branch_and_tree('.')
2595
tt = TreeTransform(tree)
2596
d_trans_id = tt.create_path("d", tt.root)
2597
tt.create_directory(d_trans_id)
2598
f_trans_id = tt.create_path("a", d_trans_id)
2599
tt.adjust_path("a", d_trans_id, f_trans_id)
2600
return tt, f_trans_id
2602
def test_root_create_file_open_raises_before_creation(self):
2603
tt, trans_id = self.create_transform_and_root_trans_id()
2604
self._override_globals_in_method(tt, "create_file",
2605
{"open": self._fake_open_raises_before})
2606
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2607
path = tt._limbo_name(trans_id)
2608
self.assertPathDoesNotExist(path)
2610
self.assertPathDoesNotExist(tt._limbodir)
2612
def test_root_create_file_open_raises_after_creation(self):
2613
tt, trans_id = self.create_transform_and_root_trans_id()
2614
self._override_globals_in_method(tt, "create_file",
2615
{"open": self._fake_open_raises_after})
2616
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2617
path = tt._limbo_name(trans_id)
2618
self.assertPathExists(path)
2620
self.assertPathDoesNotExist(path)
2621
self.assertPathDoesNotExist(tt._limbodir)
2623
def test_subdir_create_file_open_raises_before_creation(self):
2624
tt, trans_id = self.create_transform_and_subdir_trans_id()
2625
self._override_globals_in_method(tt, "create_file",
2626
{"open": self._fake_open_raises_before})
2627
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2628
path = tt._limbo_name(trans_id)
2629
self.assertPathDoesNotExist(path)
2631
self.assertPathDoesNotExist(tt._limbodir)
2633
def test_subdir_create_file_open_raises_after_creation(self):
2634
tt, trans_id = self.create_transform_and_subdir_trans_id()
2635
self._override_globals_in_method(tt, "create_file",
2636
{"open": self._fake_open_raises_after})
2637
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2638
path = tt._limbo_name(trans_id)
2639
self.assertPathExists(path)
2641
self.assertPathDoesNotExist(path)
2642
self.assertPathDoesNotExist(tt._limbodir)
2644
def test_rename_in_limbo_rename_raises_after_rename(self):
2645
tt, trans_id = self.create_transform_and_root_trans_id()
2646
parent1 = tt.new_directory('parent1', tt.root)
2647
child1 = tt.new_file('child1', parent1, 'contents')
2648
parent2 = tt.new_directory('parent2', tt.root)
2650
class FakeOSModule(object):
2651
def rename(self, old, new):
2654
self._override_globals_in_method(tt, "_rename_in_limbo",
2655
{"os": FakeOSModule()})
2657
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2658
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2659
self.assertPathExists(path)
2661
self.assertPathDoesNotExist(path)
2662
self.assertPathDoesNotExist(tt._limbodir)
2664
def test_rename_in_limbo_rename_raises_before_rename(self):
2665
tt, trans_id = self.create_transform_and_root_trans_id()
2666
parent1 = tt.new_directory('parent1', tt.root)
2667
child1 = tt.new_file('child1', parent1, 'contents')
2668
parent2 = tt.new_directory('parent2', tt.root)
2670
class FakeOSModule(object):
2671
def rename(self, old, new):
2673
self._override_globals_in_method(tt, "_rename_in_limbo",
2674
{"os": FakeOSModule()})
2676
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2677
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2678
self.assertPathExists(path)
2680
self.assertPathDoesNotExist(path)
2681
self.assertPathDoesNotExist(tt._limbodir)
2684
class TestTransformMissingParent(tests.TestCaseWithTransport):
2686
def make_tt_with_versioned_dir(self):
2687
wt = self.make_branch_and_tree('.')
2688
self.build_tree(['dir/',])
2689
wt.add(['dir'], ['dir-id'])
2690
wt.commit('Create dir')
2691
tt = TreeTransform(wt)
2692
self.addCleanup(tt.finalize)
2695
def test_resolve_create_parent_for_versioned_file(self):
2696
wt, tt = self.make_tt_with_versioned_dir()
2697
dir_tid = tt.trans_id_tree_file_id('dir-id')
2698
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2699
tt.delete_contents(dir_tid)
2700
tt.unversion_file(dir_tid)
2701
conflicts = resolve_conflicts(tt)
2702
# one conflict for the missing directory, one for the unversioned
2704
self.assertLength(2, conflicts)
2706
def test_non_versioned_file_create_conflict(self):
2707
wt, tt = self.make_tt_with_versioned_dir()
2708
dir_tid = tt.trans_id_tree_file_id('dir-id')
2709
tt.new_file('file', dir_tid, 'Contents')
2710
tt.delete_contents(dir_tid)
2711
tt.unversion_file(dir_tid)
2712
conflicts = resolve_conflicts(tt)
2713
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2714
self.assertLength(1, conflicts)
2715
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2719
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2720
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2722
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2723
('', ''), ('directory', 'directory'), (False, False))
2726
class TestTransformPreview(tests.TestCaseWithTransport):
2728
def create_tree(self):
2729
tree = self.make_branch_and_tree('.')
2730
self.build_tree_contents([('a', 'content 1')])
2731
tree.set_root_id('TREE_ROOT')
2732
tree.add('a', 'a-id')
2733
tree.commit('rev1', rev_id='rev1')
2734
return tree.branch.repository.revision_tree('rev1')
2736
def get_empty_preview(self):
2737
repository = self.make_repository('repo')
2738
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2739
preview = TransformPreview(tree)
2740
self.addCleanup(preview.finalize)
2743
def test_transform_preview(self):
2744
revision_tree = self.create_tree()
2745
preview = TransformPreview(revision_tree)
2746
self.addCleanup(preview.finalize)
2748
def test_transform_preview_tree(self):
2749
revision_tree = self.create_tree()
2750
preview = TransformPreview(revision_tree)
2751
self.addCleanup(preview.finalize)
2752
preview.get_preview_tree()
2754
def test_transform_new_file(self):
2755
revision_tree = self.create_tree()
2756
preview = TransformPreview(revision_tree)
2757
self.addCleanup(preview.finalize)
2758
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2759
preview_tree = preview.get_preview_tree()
2760
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2762
preview_tree.get_file('file2-id').read(), 'content B\n')
2764
def test_diff_preview_tree(self):
2765
revision_tree = self.create_tree()
2766
preview = TransformPreview(revision_tree)
2767
self.addCleanup(preview.finalize)
2768
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2769
preview_tree = preview.get_preview_tree()
2771
show_diff_trees(revision_tree, preview_tree, out)
2772
lines = out.getvalue().splitlines()
2773
self.assertEqual(lines[0], "=== added file 'file2'")
2774
# 3 lines of diff administrivia
2775
self.assertEqual(lines[4], "+content B")
2777
def test_transform_conflicts(self):
2778
revision_tree = self.create_tree()
2779
preview = TransformPreview(revision_tree)
2780
self.addCleanup(preview.finalize)
2781
preview.new_file('a', preview.root, 'content 2')
2782
resolve_conflicts(preview)
2783
trans_id = preview.trans_id_file_id('a-id')
2784
self.assertEqual('a.moved', preview.final_name(trans_id))
2786
def get_tree_and_preview_tree(self):
2787
revision_tree = self.create_tree()
2788
preview = TransformPreview(revision_tree)
2789
self.addCleanup(preview.finalize)
2790
a_trans_id = preview.trans_id_file_id('a-id')
2791
preview.delete_contents(a_trans_id)
2792
preview.create_file('b content', a_trans_id)
2793
preview_tree = preview.get_preview_tree()
2794
return revision_tree, preview_tree
2796
def test_iter_changes(self):
2797
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2798
root = revision_tree.inventory.root.file_id
2799
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2800
(root, root), ('a', 'a'), ('file', 'file'),
2802
list(preview_tree.iter_changes(revision_tree)))
2804
def test_include_unchanged_succeeds(self):
2805
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2806
changes = preview_tree.iter_changes(revision_tree,
2807
include_unchanged=True)
2808
root = revision_tree.inventory.root.file_id
2810
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2812
def test_specific_files(self):
2813
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2814
changes = preview_tree.iter_changes(revision_tree,
2815
specific_files=[''])
2816
self.assertEqual([A_ENTRY], list(changes))
2818
def test_want_unversioned(self):
2819
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2820
changes = preview_tree.iter_changes(revision_tree,
2821
want_unversioned=True)
2822
self.assertEqual([A_ENTRY], list(changes))
2824
def test_ignore_extra_trees_no_specific_files(self):
2825
# extra_trees is harmless without specific_files, so we'll silently
2826
# accept it, even though we won't use it.
2827
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2828
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2830
def test_ignore_require_versioned_no_specific_files(self):
2831
# require_versioned is meaningless without specific_files.
2832
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2833
preview_tree.iter_changes(revision_tree, require_versioned=False)
2835
def test_ignore_pb(self):
2836
# pb could be supported, but TT.iter_changes doesn't support it.
2837
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2838
preview_tree.iter_changes(revision_tree)
2840
def test_kind(self):
2841
revision_tree = self.create_tree()
2842
preview = TransformPreview(revision_tree)
2843
self.addCleanup(preview.finalize)
2844
preview.new_file('file', preview.root, 'contents', 'file-id')
2845
preview.new_directory('directory', preview.root, 'dir-id')
2846
preview_tree = preview.get_preview_tree()
2847
self.assertEqual('file', preview_tree.kind('file-id'))
2848
self.assertEqual('directory', preview_tree.kind('dir-id'))
2850
def test_get_file_mtime(self):
2851
preview = self.get_empty_preview()
2852
file_trans_id = preview.new_file('file', preview.root, 'contents',
2854
limbo_path = preview._limbo_name(file_trans_id)
2855
preview_tree = preview.get_preview_tree()
2856
self.assertEqual(os.stat(limbo_path).st_mtime,
2857
preview_tree.get_file_mtime('file-id'))
2859
def test_get_file_mtime_renamed(self):
2860
work_tree = self.make_branch_and_tree('tree')
2861
self.build_tree(['tree/file'])
2862
work_tree.add('file', 'file-id')
2863
preview = TransformPreview(work_tree)
2864
self.addCleanup(preview.finalize)
2865
file_trans_id = preview.trans_id_tree_file_id('file-id')
2866
preview.adjust_path('renamed', preview.root, file_trans_id)
2867
preview_tree = preview.get_preview_tree()
2868
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2869
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2871
def test_get_file_size(self):
2872
work_tree = self.make_branch_and_tree('tree')
2873
self.build_tree_contents([('tree/old', 'old')])
2874
work_tree.add('old', 'old-id')
2875
preview = TransformPreview(work_tree)
2876
self.addCleanup(preview.finalize)
2877
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2879
tree = preview.get_preview_tree()
2880
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2881
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2883
def test_get_file(self):
2884
preview = self.get_empty_preview()
2885
preview.new_file('file', preview.root, 'contents', 'file-id')
2886
preview_tree = preview.get_preview_tree()
2887
tree_file = preview_tree.get_file('file-id')
2889
self.assertEqual('contents', tree_file.read())
2893
def test_get_symlink_target(self):
2894
self.requireFeature(SymlinkFeature)
2895
preview = self.get_empty_preview()
2896
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2897
preview_tree = preview.get_preview_tree()
2898
self.assertEqual('target',
2899
preview_tree.get_symlink_target('symlink-id'))
2901
def test_all_file_ids(self):
2902
tree = self.make_branch_and_tree('tree')
2903
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2904
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2905
preview = TransformPreview(tree)
2906
self.addCleanup(preview.finalize)
2907
preview.unversion_file(preview.trans_id_file_id('b-id'))
2908
c_trans_id = preview.trans_id_file_id('c-id')
2909
preview.unversion_file(c_trans_id)
2910
preview.version_file('c-id', c_trans_id)
2911
preview_tree = preview.get_preview_tree()
2912
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2913
preview_tree.all_file_ids())
2915
def test_path2id_deleted_unchanged(self):
2916
tree = self.make_branch_and_tree('tree')
2917
self.build_tree(['tree/unchanged', 'tree/deleted'])
2918
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2919
preview = TransformPreview(tree)
2920
self.addCleanup(preview.finalize)
2921
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2922
preview_tree = preview.get_preview_tree()
2923
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2924
self.assertIs(None, preview_tree.path2id('deleted'))
2926
def test_path2id_created(self):
2927
tree = self.make_branch_and_tree('tree')
2928
self.build_tree(['tree/unchanged'])
2929
tree.add(['unchanged'], ['unchanged-id'])
2930
preview = TransformPreview(tree)
2931
self.addCleanup(preview.finalize)
2932
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2933
'contents', 'new-id')
2934
preview_tree = preview.get_preview_tree()
2935
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2937
def test_path2id_moved(self):
2938
tree = self.make_branch_and_tree('tree')
2939
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2940
tree.add(['old_parent', 'old_parent/child'],
2941
['old_parent-id', 'child-id'])
2942
preview = TransformPreview(tree)
2943
self.addCleanup(preview.finalize)
2944
new_parent = preview.new_directory('new_parent', preview.root,
2946
preview.adjust_path('child', new_parent,
2947
preview.trans_id_file_id('child-id'))
2948
preview_tree = preview.get_preview_tree()
2949
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2950
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2952
def test_path2id_renamed_parent(self):
2953
tree = self.make_branch_and_tree('tree')
2954
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2955
tree.add(['old_name', 'old_name/child'],
2956
['parent-id', 'child-id'])
2957
preview = TransformPreview(tree)
2958
self.addCleanup(preview.finalize)
2959
preview.adjust_path('new_name', preview.root,
2960
preview.trans_id_file_id('parent-id'))
2961
preview_tree = preview.get_preview_tree()
2962
self.assertIs(None, preview_tree.path2id('old_name/child'))
2963
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2965
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2966
preview_tree = tt.get_preview_tree()
2967
preview_result = list(preview_tree.iter_entries_by_dir(
2971
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2972
self.assertEqual(actual_result, preview_result)
2974
def test_iter_entries_by_dir_new(self):
2975
tree = self.make_branch_and_tree('tree')
2976
tt = TreeTransform(tree)
2977
tt.new_file('new', tt.root, 'contents', 'new-id')
2978
self.assertMatchingIterEntries(tt)
2980
def test_iter_entries_by_dir_deleted(self):
2981
tree = self.make_branch_and_tree('tree')
2982
self.build_tree(['tree/deleted'])
2983
tree.add('deleted', 'deleted-id')
2984
tt = TreeTransform(tree)
2985
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2986
self.assertMatchingIterEntries(tt)
2988
def test_iter_entries_by_dir_unversioned(self):
2989
tree = self.make_branch_and_tree('tree')
2990
self.build_tree(['tree/removed'])
2991
tree.add('removed', 'removed-id')
2992
tt = TreeTransform(tree)
2993
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2994
self.assertMatchingIterEntries(tt)
2996
def test_iter_entries_by_dir_moved(self):
2997
tree = self.make_branch_and_tree('tree')
2998
self.build_tree(['tree/moved', 'tree/new_parent/'])
2999
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
3000
tt = TreeTransform(tree)
3001
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
3002
tt.trans_id_file_id('moved-id'))
3003
self.assertMatchingIterEntries(tt)
3005
def test_iter_entries_by_dir_specific_file_ids(self):
3006
tree = self.make_branch_and_tree('tree')
3007
tree.set_root_id('tree-root-id')
3008
self.build_tree(['tree/parent/', 'tree/parent/child'])
3009
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
3010
tt = TreeTransform(tree)
3011
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
3013
def test_symlink_content_summary(self):
3014
self.requireFeature(SymlinkFeature)
3015
preview = self.get_empty_preview()
3016
preview.new_symlink('path', preview.root, 'target', 'path-id')
3017
summary = preview.get_preview_tree().path_content_summary('path')
3018
self.assertEqual(('symlink', None, None, 'target'), summary)
3020
def test_missing_content_summary(self):
3021
preview = self.get_empty_preview()
3022
summary = preview.get_preview_tree().path_content_summary('path')
3023
self.assertEqual(('missing', None, None, None), summary)
3025
def test_deleted_content_summary(self):
3026
tree = self.make_branch_and_tree('tree')
3027
self.build_tree(['tree/path/'])
3029
preview = TransformPreview(tree)
3030
self.addCleanup(preview.finalize)
3031
preview.delete_contents(preview.trans_id_tree_path('path'))
3032
summary = preview.get_preview_tree().path_content_summary('path')
3033
self.assertEqual(('missing', None, None, None), summary)
3035
def test_file_content_summary_executable(self):
3036
preview = self.get_empty_preview()
3037
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3038
preview.set_executability(True, path_id)
3039
summary = preview.get_preview_tree().path_content_summary('path')
3040
self.assertEqual(4, len(summary))
3041
self.assertEqual('file', summary[0])
3042
# size must be known
3043
self.assertEqual(len('contents'), summary[1])
3045
self.assertEqual(True, summary[2])
3046
# will not have hash (not cheap to determine)
3047
self.assertIs(None, summary[3])
3049
def test_change_executability(self):
3050
tree = self.make_branch_and_tree('tree')
3051
self.build_tree(['tree/path'])
3053
preview = TransformPreview(tree)
3054
self.addCleanup(preview.finalize)
3055
path_id = preview.trans_id_tree_path('path')
3056
preview.set_executability(True, path_id)
3057
summary = preview.get_preview_tree().path_content_summary('path')
3058
self.assertEqual(True, summary[2])
3060
def test_file_content_summary_non_exec(self):
3061
preview = self.get_empty_preview()
3062
preview.new_file('path', preview.root, 'contents', 'path-id')
3063
summary = preview.get_preview_tree().path_content_summary('path')
3064
self.assertEqual(4, len(summary))
3065
self.assertEqual('file', summary[0])
3066
# size must be known
3067
self.assertEqual(len('contents'), summary[1])
3069
self.assertEqual(False, summary[2])
3070
# will not have hash (not cheap to determine)
3071
self.assertIs(None, summary[3])
3073
def test_dir_content_summary(self):
3074
preview = self.get_empty_preview()
3075
preview.new_directory('path', preview.root, 'path-id')
3076
summary = preview.get_preview_tree().path_content_summary('path')
3077
self.assertEqual(('directory', None, None, None), summary)
3079
def test_tree_content_summary(self):
3080
preview = self.get_empty_preview()
3081
path = preview.new_directory('path', preview.root, 'path-id')
3082
preview.set_tree_reference('rev-1', path)
3083
summary = preview.get_preview_tree().path_content_summary('path')
3084
self.assertEqual(4, len(summary))
3085
self.assertEqual('tree-reference', summary[0])
3087
def test_annotate(self):
3088
tree = self.make_branch_and_tree('tree')
3089
self.build_tree_contents([('tree/file', 'a\n')])
3090
tree.add('file', 'file-id')
3091
tree.commit('a', rev_id='one')
3092
self.build_tree_contents([('tree/file', 'a\nb\n')])
3093
preview = TransformPreview(tree)
3094
self.addCleanup(preview.finalize)
3095
file_trans_id = preview.trans_id_file_id('file-id')
3096
preview.delete_contents(file_trans_id)
3097
preview.create_file('a\nb\nc\n', file_trans_id)
3098
preview_tree = preview.get_preview_tree()
3104
annotation = preview_tree.annotate_iter('file-id', 'me:')
3105
self.assertEqual(expected, annotation)
3107
def test_annotate_missing(self):
3108
preview = self.get_empty_preview()
3109
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3110
preview_tree = preview.get_preview_tree()
3116
annotation = preview_tree.annotate_iter('file-id', 'me:')
3117
self.assertEqual(expected, annotation)
3119
def test_annotate_rename(self):
3120
tree = self.make_branch_and_tree('tree')
3121
self.build_tree_contents([('tree/file', 'a\n')])
3122
tree.add('file', 'file-id')
3123
tree.commit('a', rev_id='one')
3124
preview = TransformPreview(tree)
3125
self.addCleanup(preview.finalize)
3126
file_trans_id = preview.trans_id_file_id('file-id')
3127
preview.adjust_path('newname', preview.root, file_trans_id)
3128
preview_tree = preview.get_preview_tree()
3132
annotation = preview_tree.annotate_iter('file-id', 'me:')
3133
self.assertEqual(expected, annotation)
3135
def test_annotate_deleted(self):
3136
tree = self.make_branch_and_tree('tree')
3137
self.build_tree_contents([('tree/file', 'a\n')])
3138
tree.add('file', 'file-id')
3139
tree.commit('a', rev_id='one')
3140
self.build_tree_contents([('tree/file', 'a\nb\n')])
3141
preview = TransformPreview(tree)
3142
self.addCleanup(preview.finalize)
3143
file_trans_id = preview.trans_id_file_id('file-id')
3144
preview.delete_contents(file_trans_id)
3145
preview_tree = preview.get_preview_tree()
3146
annotation = preview_tree.annotate_iter('file-id', 'me:')
3147
self.assertIs(None, annotation)
3149
def test_stored_kind(self):
3150
preview = self.get_empty_preview()
3151
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3152
preview_tree = preview.get_preview_tree()
3153
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3155
def test_is_executable(self):
3156
preview = self.get_empty_preview()
3157
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3158
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3159
preview_tree = preview.get_preview_tree()
3160
self.assertEqual(True, preview_tree.is_executable('file-id'))
3162
def test_get_set_parent_ids(self):
3163
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3164
self.assertEqual([], preview_tree.get_parent_ids())
3165
preview_tree.set_parent_ids(['rev-1'])
3166
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3168
def test_plan_file_merge(self):
3169
work_a = self.make_branch_and_tree('wta')
3170
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3171
work_a.add('file', 'file-id')
3172
base_id = work_a.commit('base version')
3173
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3174
preview = TransformPreview(work_a)
3175
self.addCleanup(preview.finalize)
3176
trans_id = preview.trans_id_file_id('file-id')
3177
preview.delete_contents(trans_id)
3178
preview.create_file('b\nc\nd\ne\n', trans_id)
3179
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3180
tree_a = preview.get_preview_tree()
3181
tree_a.set_parent_ids([base_id])
3183
('killed-a', 'a\n'),
3184
('killed-b', 'b\n'),
3185
('unchanged', 'c\n'),
3186
('unchanged', 'd\n'),
3189
], list(tree_a.plan_file_merge('file-id', tree_b)))
3191
def test_plan_file_merge_revision_tree(self):
3192
work_a = self.make_branch_and_tree('wta')
3193
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3194
work_a.add('file', 'file-id')
3195
base_id = work_a.commit('base version')
3196
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3197
preview = TransformPreview(work_a.basis_tree())
3198
self.addCleanup(preview.finalize)
3199
trans_id = preview.trans_id_file_id('file-id')
3200
preview.delete_contents(trans_id)
3201
preview.create_file('b\nc\nd\ne\n', trans_id)
3202
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3203
tree_a = preview.get_preview_tree()
3204
tree_a.set_parent_ids([base_id])
3206
('killed-a', 'a\n'),
3207
('killed-b', 'b\n'),
3208
('unchanged', 'c\n'),
3209
('unchanged', 'd\n'),
3212
], list(tree_a.plan_file_merge('file-id', tree_b)))
3214
def test_walkdirs(self):
3215
preview = self.get_empty_preview()
3216
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3217
# FIXME: new_directory should mark root.
3218
preview.fixup_new_roots()
3219
preview_tree = preview.get_preview_tree()
3220
file_trans_id = preview.new_file('a', preview.root, 'contents',
3222
expected = [(('', 'tree-root'),
3223
[('a', 'a', 'file', None, 'a-id', 'file')])]
3224
self.assertEqual(expected, list(preview_tree.walkdirs()))
3226
def test_extras(self):
3227
work_tree = self.make_branch_and_tree('tree')
3228
self.build_tree(['tree/removed-file', 'tree/existing-file',
3229
'tree/not-removed-file'])
3230
work_tree.add(['removed-file', 'not-removed-file'])
3231
preview = TransformPreview(work_tree)
3232
self.addCleanup(preview.finalize)
3233
preview.new_file('new-file', preview.root, 'contents')
3234
preview.new_file('new-versioned-file', preview.root, 'contents',
3236
tree = preview.get_preview_tree()
3237
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3238
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3241
def test_merge_into_preview(self):
3242
work_tree = self.make_branch_and_tree('tree')
3243
self.build_tree_contents([('tree/file','b\n')])
3244
work_tree.add('file', 'file-id')
3245
work_tree.commit('first commit')
3246
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3247
self.build_tree_contents([('child/file','b\nc\n')])
3248
child_tree.commit('child commit')
3249
child_tree.lock_write()
3250
self.addCleanup(child_tree.unlock)
3251
work_tree.lock_write()
3252
self.addCleanup(work_tree.unlock)
3253
preview = TransformPreview(work_tree)
3254
self.addCleanup(preview.finalize)
3255
file_trans_id = preview.trans_id_file_id('file-id')
3256
preview.delete_contents(file_trans_id)
3257
preview.create_file('a\nb\n', file_trans_id)
3258
preview_tree = preview.get_preview_tree()
3259
merger = Merger.from_revision_ids(None, preview_tree,
3260
child_tree.branch.last_revision(),
3261
other_branch=child_tree.branch,
3262
tree_branch=work_tree.branch)
3263
merger.merge_type = Merge3Merger
3264
tt = merger.make_merger().make_preview_transform()
3265
self.addCleanup(tt.finalize)
3266
final_tree = tt.get_preview_tree()
3267
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3269
def test_merge_preview_into_workingtree(self):
3270
tree = self.make_branch_and_tree('tree')
3271
tree.set_root_id('TREE_ROOT')
3272
tt = TransformPreview(tree)
3273
self.addCleanup(tt.finalize)
3274
tt.new_file('name', tt.root, 'content', 'file-id')
3275
tree2 = self.make_branch_and_tree('tree2')
3276
tree2.set_root_id('TREE_ROOT')
3277
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3278
None, tree.basis_tree())
3279
merger.merge_type = Merge3Merger
3282
def test_merge_preview_into_workingtree_handles_conflicts(self):
3283
tree = self.make_branch_and_tree('tree')
3284
self.build_tree_contents([('tree/foo', 'bar')])
3285
tree.add('foo', 'foo-id')
3287
tt = TransformPreview(tree)
3288
self.addCleanup(tt.finalize)
3289
trans_id = tt.trans_id_file_id('foo-id')
3290
tt.delete_contents(trans_id)
3291
tt.create_file('baz', trans_id)
3292
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3293
self.build_tree_contents([('tree2/foo', 'qux')])
3295
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3296
pb, tree.basis_tree())
3297
merger.merge_type = Merge3Merger
3300
def test_has_filename(self):
3301
wt = self.make_branch_and_tree('tree')
3302
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3303
tt = TransformPreview(wt)
3304
removed_id = tt.trans_id_tree_path('removed')
3305
tt.delete_contents(removed_id)
3306
tt.new_file('new', tt.root, 'contents')
3307
modified_id = tt.trans_id_tree_path('modified')
3308
tt.delete_contents(modified_id)
3309
tt.create_file('modified-contents', modified_id)
3310
self.addCleanup(tt.finalize)
3311
tree = tt.get_preview_tree()
3312
self.assertTrue(tree.has_filename('unmodified'))
3313
self.assertFalse(tree.has_filename('not-present'))
3314
self.assertFalse(tree.has_filename('removed'))
3315
self.assertTrue(tree.has_filename('new'))
3316
self.assertTrue(tree.has_filename('modified'))
3318
def test_is_executable(self):
3319
tree = self.make_branch_and_tree('tree')
3320
preview = TransformPreview(tree)
3321
self.addCleanup(preview.finalize)
3322
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3323
preview_tree = preview.get_preview_tree()
3324
self.assertEqual(False, preview_tree.is_executable('baz-id',
3326
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3328
def test_commit_preview_tree(self):
3329
tree = self.make_branch_and_tree('tree')
3330
rev_id = tree.commit('rev1')
3331
tree.branch.lock_write()
3332
self.addCleanup(tree.branch.unlock)
3333
tt = TransformPreview(tree)
3334
tt.new_file('file', tt.root, 'contents', 'file_id')
3335
self.addCleanup(tt.finalize)
3336
preview = tt.get_preview_tree()
3337
preview.set_parent_ids([rev_id])
3338
builder = tree.branch.get_commit_builder([rev_id])
3339
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3340
builder.finish_inventory()
3341
rev2_id = builder.commit('rev2')
3342
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3343
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3345
def test_ascii_limbo_paths(self):
3346
self.requireFeature(features.UnicodeFilenameFeature)
3347
branch = self.make_branch('any')
3348
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3349
tt = TransformPreview(tree)
3350
self.addCleanup(tt.finalize)
3351
foo_id = tt.new_directory('', ROOT_PARENT)
3352
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3353
limbo_path = tt._limbo_name(bar_id)
3354
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3357
class FakeSerializer(object):
3358
"""Serializer implementation that simply returns the input.
3360
The input is returned in the order used by pack.ContainerPushParser.
3363
def bytes_record(bytes, names):
3367
class TestSerializeTransform(tests.TestCaseWithTransport):
3369
_test_needs_features = [features.UnicodeFilenameFeature]
3371
def get_preview(self, tree=None):
3373
tree = self.make_branch_and_tree('tree')
3374
tt = TransformPreview(tree)
3375
self.addCleanup(tt.finalize)
3378
def assertSerializesTo(self, expected, tt):
3379
records = list(tt.serialize(FakeSerializer()))
3380
self.assertEqual(expected, records)
3383
def default_attribs():
3388
'_new_executability': {},
3390
'_tree_path_ids': {'': 'new-0'},
3392
'_removed_contents': [],
3393
'_non_present_ids': {},
3396
def make_records(self, attribs, contents):
3398
(((('attribs'),),), bencode.bencode(attribs))]
3399
records.extend([(((n, k),), c) for n, k, c in contents])
3402
def creation_records(self):
3403
attribs = self.default_attribs()
3404
attribs['_id_number'] = 3
3405
attribs['_new_name'] = {
3406
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3407
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3408
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3409
attribs['_new_executability'] = {'new-1': 1}
3411
('new-1', 'file', 'i 1\nbar\n'),
3412
('new-2', 'directory', ''),
3414
return self.make_records(attribs, contents)
3416
def test_serialize_creation(self):
3417
tt = self.get_preview()
3418
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3419
tt.new_directory('qux', tt.root, 'quxx')
3420
self.assertSerializesTo(self.creation_records(), tt)
3422
def test_deserialize_creation(self):
3423
tt = self.get_preview()
3424
tt.deserialize(iter(self.creation_records()))
3425
self.assertEqual(3, tt._id_number)
3426
self.assertEqual({'new-1': u'foo\u1234',
3427
'new-2': 'qux'}, tt._new_name)
3428
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3429
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3430
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3431
self.assertEqual({'new-1': True}, tt._new_executability)
3432
self.assertEqual({'new-1': 'file',
3433
'new-2': 'directory'}, tt._new_contents)
3434
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3436
foo_content = foo_limbo.read()
3439
self.assertEqual('bar', foo_content)
3441
def symlink_creation_records(self):
3442
attribs = self.default_attribs()
3443
attribs['_id_number'] = 2
3444
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3445
attribs['_new_parent'] = {'new-1': 'new-0'}
3446
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3447
return self.make_records(attribs, contents)
3449
def test_serialize_symlink_creation(self):
3450
self.requireFeature(features.SymlinkFeature)
3451
tt = self.get_preview()
3452
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3453
self.assertSerializesTo(self.symlink_creation_records(), tt)
3455
def test_deserialize_symlink_creation(self):
3456
self.requireFeature(features.SymlinkFeature)
3457
tt = self.get_preview()
3458
tt.deserialize(iter(self.symlink_creation_records()))
3459
abspath = tt._limbo_name('new-1')
3460
foo_content = osutils.readlink(abspath)
3461
self.assertEqual(u'bar\u1234', foo_content)
3463
def make_destruction_preview(self):
3464
tree = self.make_branch_and_tree('.')
3465
self.build_tree([u'foo\u1234', 'bar'])
3466
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3467
return self.get_preview(tree)
3469
def destruction_records(self):
3470
attribs = self.default_attribs()
3471
attribs['_id_number'] = 3
3472
attribs['_removed_id'] = ['new-1']
3473
attribs['_removed_contents'] = ['new-2']
3474
attribs['_tree_path_ids'] = {
3476
u'foo\u1234'.encode('utf-8'): 'new-1',
3479
return self.make_records(attribs, [])
3481
def test_serialize_destruction(self):
3482
tt = self.make_destruction_preview()
3483
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3484
tt.unversion_file(foo_trans_id)
3485
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3486
tt.delete_contents(bar_trans_id)
3487
self.assertSerializesTo(self.destruction_records(), tt)
3489
def test_deserialize_destruction(self):
3490
tt = self.make_destruction_preview()
3491
tt.deserialize(iter(self.destruction_records()))
3492
self.assertEqual({u'foo\u1234': 'new-1',
3494
'': tt.root}, tt._tree_path_ids)
3495
self.assertEqual({'new-1': u'foo\u1234',
3497
tt.root: ''}, tt._tree_id_paths)
3498
self.assertEqual(set(['new-1']), tt._removed_id)
3499
self.assertEqual(set(['new-2']), tt._removed_contents)
3501
def missing_records(self):
3502
attribs = self.default_attribs()
3503
attribs['_id_number'] = 2
3504
attribs['_non_present_ids'] = {
3506
return self.make_records(attribs, [])
3508
def test_serialize_missing(self):
3509
tt = self.get_preview()
3510
boo_trans_id = tt.trans_id_file_id('boo')
3511
self.assertSerializesTo(self.missing_records(), tt)
3513
def test_deserialize_missing(self):
3514
tt = self.get_preview()
3515
tt.deserialize(iter(self.missing_records()))
3516
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3518
def make_modification_preview(self):
3519
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3520
LINES_TWO = 'z\nbb\nx\ndd\n'
3521
tree = self.make_branch_and_tree('tree')
3522
self.build_tree_contents([('tree/file', LINES_ONE)])
3523
tree.add('file', 'file-id')
3524
return self.get_preview(tree), LINES_TWO
3526
def modification_records(self):
3527
attribs = self.default_attribs()
3528
attribs['_id_number'] = 2
3529
attribs['_tree_path_ids'] = {
3532
attribs['_removed_contents'] = ['new-1']
3533
contents = [('new-1', 'file',
3534
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3535
return self.make_records(attribs, contents)
3537
def test_serialize_modification(self):
3538
tt, LINES = self.make_modification_preview()
3539
trans_id = tt.trans_id_file_id('file-id')
3540
tt.delete_contents(trans_id)
3541
tt.create_file(LINES, trans_id)
3542
self.assertSerializesTo(self.modification_records(), tt)
3544
def test_deserialize_modification(self):
3545
tt, LINES = self.make_modification_preview()
3546
tt.deserialize(iter(self.modification_records()))
3547
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3549
def make_kind_change_preview(self):
3550
LINES = 'a\nb\nc\nd\n'
3551
tree = self.make_branch_and_tree('tree')
3552
self.build_tree(['tree/foo/'])
3553
tree.add('foo', 'foo-id')
3554
return self.get_preview(tree), LINES
3556
def kind_change_records(self):
3557
attribs = self.default_attribs()
3558
attribs['_id_number'] = 2
3559
attribs['_tree_path_ids'] = {
3562
attribs['_removed_contents'] = ['new-1']
3563
contents = [('new-1', 'file',
3564
'i 4\na\nb\nc\nd\n\n')]
3565
return self.make_records(attribs, contents)
3567
def test_serialize_kind_change(self):
3568
tt, LINES = self.make_kind_change_preview()
3569
trans_id = tt.trans_id_file_id('foo-id')
3570
tt.delete_contents(trans_id)
3571
tt.create_file(LINES, trans_id)
3572
self.assertSerializesTo(self.kind_change_records(), tt)
3574
def test_deserialize_kind_change(self):
3575
tt, LINES = self.make_kind_change_preview()
3576
tt.deserialize(iter(self.kind_change_records()))
3577
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3579
def make_add_contents_preview(self):
3580
LINES = 'a\nb\nc\nd\n'
3581
tree = self.make_branch_and_tree('tree')
3582
self.build_tree(['tree/foo'])
3584
os.unlink('tree/foo')
3585
return self.get_preview(tree), LINES
3587
def add_contents_records(self):
3588
attribs = self.default_attribs()
3589
attribs['_id_number'] = 2
3590
attribs['_tree_path_ids'] = {
3593
contents = [('new-1', 'file',
3594
'i 4\na\nb\nc\nd\n\n')]
3595
return self.make_records(attribs, contents)
3597
def test_serialize_add_contents(self):
3598
tt, LINES = self.make_add_contents_preview()
3599
trans_id = tt.trans_id_tree_path('foo')
3600
tt.create_file(LINES, trans_id)
3601
self.assertSerializesTo(self.add_contents_records(), tt)
3603
def test_deserialize_add_contents(self):
3604
tt, LINES = self.make_add_contents_preview()
3605
tt.deserialize(iter(self.add_contents_records()))
3606
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3608
def test_get_parents_lines(self):
3609
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3610
LINES_TWO = 'z\nbb\nx\ndd\n'
3611
tree = self.make_branch_and_tree('tree')
3612
self.build_tree_contents([('tree/file', LINES_ONE)])
3613
tree.add('file', 'file-id')
3614
tt = self.get_preview(tree)
3615
trans_id = tt.trans_id_tree_path('file')
3616
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3617
tt._get_parents_lines(trans_id))
3619
def test_get_parents_texts(self):
3620
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3621
LINES_TWO = 'z\nbb\nx\ndd\n'
3622
tree = self.make_branch_and_tree('tree')
3623
self.build_tree_contents([('tree/file', LINES_ONE)])
3624
tree.add('file', 'file-id')
3625
tt = self.get_preview(tree)
3626
trans_id = tt.trans_id_tree_path('file')
3627
self.assertEqual((LINES_ONE,),
3628
tt._get_parents_texts(trans_id))
3631
class TestOrphan(tests.TestCaseWithTransport):
3633
def test_no_orphan_for_transform_preview(self):
3634
tree = self.make_branch_and_tree('tree')
3635
tt = transform.TransformPreview(tree)
3636
self.addCleanup(tt.finalize)
3637
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3639
def _set_orphan_policy(self, wt, policy):
3640
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3643
def _prepare_orphan(self, wt):
3644
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3645
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3646
wt.commit('add dir and file ignoring foo')
3647
tt = transform.TreeTransform(wt)
3648
self.addCleanup(tt.finalize)
3649
# dir and bar are deleted
3650
dir_tid = tt.trans_id_tree_path('dir')
3651
file_tid = tt.trans_id_tree_path('dir/file')
3652
orphan_tid = tt.trans_id_tree_path('dir/foo')
3653
tt.delete_contents(file_tid)
3654
tt.unversion_file(file_tid)
3655
tt.delete_contents(dir_tid)
3656
tt.unversion_file(dir_tid)
3657
# There should be a conflict because dir still contain foo
3658
raw_conflicts = tt.find_conflicts()
3659
self.assertLength(1, raw_conflicts)
3660
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3661
return tt, orphan_tid
3663
def test_new_orphan_created(self):
3664
wt = self.make_branch_and_tree('.')
3665
self._set_orphan_policy(wt, 'move')
3666
tt, orphan_tid = self._prepare_orphan(wt)
3669
warnings.append(args[0] % args[1:])
3670
self.overrideAttr(trace, 'warning', warning)
3671
remaining_conflicts = resolve_conflicts(tt)
3672
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3674
# Yeah for resolved conflicts !
3675
self.assertLength(0, remaining_conflicts)
3676
# We have a new orphan
3677
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3678
self.assertEquals('bzr-orphans',
3679
tt.final_name(tt.final_parent(orphan_tid)))
3681
def test_never_orphan(self):
3682
wt = self.make_branch_and_tree('.')
3683
self._set_orphan_policy(wt, 'conflict')
3684
tt, orphan_tid = self._prepare_orphan(wt)
3685
remaining_conflicts = resolve_conflicts(tt)
3686
self.assertLength(1, remaining_conflicts)
3687
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3688
remaining_conflicts.pop())
3690
def test_orphan_error(self):
3691
def bogus_orphan(tt, orphan_id, parent_id):
3692
raise transform.OrphaningError(tt.final_name(orphan_id),
3693
tt.final_name(parent_id))
3694
transform.orphaning_registry.register('bogus', bogus_orphan,
3695
'Raise an error when orphaning')
3696
wt = self.make_branch_and_tree('.')
3697
self._set_orphan_policy(wt, 'bogus')
3698
tt, orphan_tid = self._prepare_orphan(wt)
3699
remaining_conflicts = resolve_conflicts(tt)
3700
self.assertLength(1, remaining_conflicts)
3701
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3702
remaining_conflicts.pop())
3704
def test_unknown_orphan_policy(self):
3705
wt = self.make_branch_and_tree('.')
3706
# Set a fictional policy nobody ever implemented
3707
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3708
tt, orphan_tid = self._prepare_orphan(wt)
3711
warnings.append(args[0] % args[1:])
3712
self.overrideAttr(trace, 'warning', warning)
3713
remaining_conflicts = resolve_conflicts(tt)
3714
# We fallback to the default policy which create a conflict
3715
self.assertLength(1, remaining_conflicts)
3716
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3717
remaining_conflicts.pop())
3718
self.assertLength(1, warnings)
3719
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')