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.assertContainsRe(str(err),
1480
"^File exists: .+/baz")
1482
def test_two_directories_clash(self):
1484
wt = self.make_branch_and_tree('.')
1485
tt = TreeTransform(wt) # TreeTransform obtains write lock
1487
foo_1 = tt.new_directory('foo', tt.root)
1488
tt.new_directory('bar', foo_1)
1489
# Adding the same directory with a different content
1490
foo_2 = tt.new_directory('foo', tt.root)
1491
tt.new_directory('baz', foo_2)
1492
# Lie to tt that we've already resolved all conflicts.
1493
tt.apply(no_conflicts=True)
1497
err = self.assertRaises(errors.FileExists, tt_helper)
1498
self.assertContainsRe(str(err),
1499
"^File exists: .+/foo")
1501
def test_two_directories_clash_finalize(self):
1503
wt = self.make_branch_and_tree('.')
1504
tt = TreeTransform(wt) # TreeTransform obtains write lock
1506
foo_1 = tt.new_directory('foo', tt.root)
1507
tt.new_directory('bar', foo_1)
1508
# Adding the same directory with a different content
1509
foo_2 = tt.new_directory('foo', tt.root)
1510
tt.new_directory('baz', foo_2)
1511
# Lie to tt that we've already resolved all conflicts.
1512
tt.apply(no_conflicts=True)
1516
err = self.assertRaises(errors.FileExists, tt_helper)
1517
self.assertContainsRe(str(err),
1518
"^File exists: .+/foo")
1520
def test_file_to_directory(self):
1521
wt = self.make_branch_and_tree('.')
1522
self.build_tree(['foo'])
1525
tt = TreeTransform(wt)
1526
self.addCleanup(tt.finalize)
1527
foo_trans_id = tt.trans_id_tree_path("foo")
1528
tt.delete_contents(foo_trans_id)
1529
tt.create_directory(foo_trans_id)
1530
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1531
tt.create_file(["aa\n"], bar_trans_id)
1532
tt.version_file("bar-1", bar_trans_id)
1534
self.assertPathExists("foo/bar")
1537
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1542
changes = wt.changes_from(wt.basis_tree())
1543
self.assertFalse(changes.has_changed(), changes)
1545
def test_file_to_symlink(self):
1546
self.requireFeature(SymlinkFeature)
1547
wt = self.make_branch_and_tree('.')
1548
self.build_tree(['foo'])
1551
tt = TreeTransform(wt)
1552
self.addCleanup(tt.finalize)
1553
foo_trans_id = tt.trans_id_tree_path("foo")
1554
tt.delete_contents(foo_trans_id)
1555
tt.create_symlink("bar", foo_trans_id)
1557
self.assertPathExists("foo")
1559
self.addCleanup(wt.unlock)
1560
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1563
def test_dir_to_file(self):
1564
wt = self.make_branch_and_tree('.')
1565
self.build_tree(['foo/', 'foo/bar'])
1566
wt.add(['foo', 'foo/bar'])
1568
tt = TreeTransform(wt)
1569
self.addCleanup(tt.finalize)
1570
foo_trans_id = tt.trans_id_tree_path("foo")
1571
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1572
tt.delete_contents(foo_trans_id)
1573
tt.delete_versioned(bar_trans_id)
1574
tt.create_file(["aa\n"], foo_trans_id)
1576
self.assertPathExists("foo")
1578
self.addCleanup(wt.unlock)
1579
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1582
def test_dir_to_hardlink(self):
1583
self.requireFeature(HardlinkFeature)
1584
wt = self.make_branch_and_tree('.')
1585
self.build_tree(['foo/', 'foo/bar'])
1586
wt.add(['foo', 'foo/bar'])
1588
tt = TreeTransform(wt)
1589
self.addCleanup(tt.finalize)
1590
foo_trans_id = tt.trans_id_tree_path("foo")
1591
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1592
tt.delete_contents(foo_trans_id)
1593
tt.delete_versioned(bar_trans_id)
1594
self.build_tree(['baz'])
1595
tt.create_hardlink("baz", foo_trans_id)
1597
self.assertPathExists("foo")
1598
self.assertPathExists("baz")
1600
self.addCleanup(wt.unlock)
1601
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1604
def test_no_final_path(self):
1605
transform, root = self.get_transform()
1606
trans_id = transform.trans_id_file_id('foo')
1607
transform.create_file('bar', trans_id)
1608
transform.cancel_creation(trans_id)
1611
def test_create_from_tree(self):
1612
tree1 = self.make_branch_and_tree('tree1')
1613
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1614
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1615
tree2 = self.make_branch_and_tree('tree2')
1616
tt = TreeTransform(tree2)
1617
foo_trans_id = tt.create_path('foo', tt.root)
1618
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1619
bar_trans_id = tt.create_path('bar', tt.root)
1620
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1622
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1623
self.assertFileEqual('baz', 'tree2/bar')
1625
def test_create_from_tree_bytes(self):
1626
"""Provided lines are used instead of tree content."""
1627
tree1 = self.make_branch_and_tree('tree1')
1628
self.build_tree_contents([('tree1/foo', 'bar'),])
1629
tree1.add('foo', 'foo-id')
1630
tree2 = self.make_branch_and_tree('tree2')
1631
tt = TreeTransform(tree2)
1632
foo_trans_id = tt.create_path('foo', tt.root)
1633
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1635
self.assertFileEqual('qux', 'tree2/foo')
1637
def test_create_from_tree_symlink(self):
1638
self.requireFeature(SymlinkFeature)
1639
tree1 = self.make_branch_and_tree('tree1')
1640
os.symlink('bar', 'tree1/foo')
1641
tree1.add('foo', 'foo-id')
1642
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1643
foo_trans_id = tt.create_path('foo', tt.root)
1644
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1646
self.assertEqual('bar', os.readlink('tree2/foo'))
1649
class TransformGroup(object):
1651
def __init__(self, dirname, root_id):
1654
self.wt = BzrDir.create_standalone_workingtree(dirname)
1655
self.wt.set_root_id(root_id)
1656
self.b = self.wt.branch
1657
self.tt = TreeTransform(self.wt)
1658
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1661
def conflict_text(tree, merge):
1662
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1663
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1666
class TestInventoryAltered(tests.TestCaseWithTransport):
1668
def test_inventory_altered_unchanged(self):
1669
tree = self.make_branch_and_tree('tree')
1670
self.build_tree(['tree/foo'])
1671
tree.add('foo', 'foo-id')
1672
with TransformPreview(tree) as tt:
1673
self.assertEqual([], tt._inventory_altered())
1675
def test_inventory_altered_changed_parent_id(self):
1676
tree = self.make_branch_and_tree('tree')
1677
self.build_tree(['tree/foo'])
1678
tree.add('foo', 'foo-id')
1679
with TransformPreview(tree) as tt:
1680
tt.unversion_file(tt.root)
1681
tt.version_file('new-id', tt.root)
1682
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1683
foo_tuple = ('foo', foo_trans_id)
1684
root_tuple = ('', tt.root)
1685
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1687
def test_inventory_altered_noop_changed_parent_id(self):
1688
tree = self.make_branch_and_tree('tree')
1689
self.build_tree(['tree/foo'])
1690
tree.add('foo', 'foo-id')
1691
with TransformPreview(tree) as tt:
1692
tt.unversion_file(tt.root)
1693
tt.version_file(tree.get_root_id(), tt.root)
1694
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1695
self.assertEqual([], tt._inventory_altered())
1698
class TestTransformMerge(TestCaseInTempDir):
1700
def test_text_merge(self):
1701
root_id = generate_ids.gen_root_id()
1702
base = TransformGroup("base", root_id)
1703
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1704
base.tt.new_file('b', base.root, 'b1', 'b')
1705
base.tt.new_file('c', base.root, 'c', 'c')
1706
base.tt.new_file('d', base.root, 'd', 'd')
1707
base.tt.new_file('e', base.root, 'e', 'e')
1708
base.tt.new_file('f', base.root, 'f', 'f')
1709
base.tt.new_directory('g', base.root, 'g')
1710
base.tt.new_directory('h', base.root, 'h')
1712
other = TransformGroup("other", root_id)
1713
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1714
other.tt.new_file('b', other.root, 'b2', 'b')
1715
other.tt.new_file('c', other.root, 'c2', 'c')
1716
other.tt.new_file('d', other.root, 'd', 'd')
1717
other.tt.new_file('e', other.root, 'e2', 'e')
1718
other.tt.new_file('f', other.root, 'f', 'f')
1719
other.tt.new_file('g', other.root, 'g', 'g')
1720
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1721
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1723
this = TransformGroup("this", root_id)
1724
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1725
this.tt.new_file('b', this.root, 'b', 'b')
1726
this.tt.new_file('c', this.root, 'c', 'c')
1727
this.tt.new_file('d', this.root, 'd2', 'd')
1728
this.tt.new_file('e', this.root, 'e2', 'e')
1729
this.tt.new_file('f', this.root, 'f', 'f')
1730
this.tt.new_file('g', this.root, 'g', 'g')
1731
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1732
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1734
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1737
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1738
# three-way text conflict
1739
self.assertEqual(this.wt.get_file('b').read(),
1740
conflict_text('b', 'b2'))
1742
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1744
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1745
# Ambigious clean merge
1746
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1748
self.assertEqual(this.wt.get_file('f').read(), 'f')
1749
# Correct correct results when THIS == OTHER
1750
self.assertEqual(this.wt.get_file('g').read(), 'g')
1751
# Text conflict when THIS & OTHER are text and BASE is dir
1752
self.assertEqual(this.wt.get_file('h').read(),
1753
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1754
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1756
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1758
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1759
self.assertEqual(this.wt.get_file('i').read(),
1760
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1761
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1763
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1765
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1766
modified = ['a', 'b', 'c', 'h', 'i']
1767
merge_modified = this.wt.merge_modified()
1768
self.assertSubset(merge_modified, modified)
1769
self.assertEqual(len(merge_modified), len(modified))
1770
file(this.wt.id2abspath('a'), 'wb').write('booga')
1772
merge_modified = this.wt.merge_modified()
1773
self.assertSubset(merge_modified, modified)
1774
self.assertEqual(len(merge_modified), len(modified))
1778
def test_file_merge(self):
1779
self.requireFeature(SymlinkFeature)
1780
root_id = generate_ids.gen_root_id()
1781
base = TransformGroup("BASE", root_id)
1782
this = TransformGroup("THIS", root_id)
1783
other = TransformGroup("OTHER", root_id)
1784
for tg in this, base, other:
1785
tg.tt.new_directory('a', tg.root, 'a')
1786
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1787
tg.tt.new_file('c', tg.root, 'c', 'c')
1788
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1789
targets = ((base, 'base-e', 'base-f', None, None),
1790
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1791
(other, 'other-e', None, 'other-g', 'other-h'))
1792
for tg, e_target, f_target, g_target, h_target in targets:
1793
for link, target in (('e', e_target), ('f', f_target),
1794
('g', g_target), ('h', h_target)):
1795
if target is not None:
1796
tg.tt.new_symlink(link, tg.root, target, link)
1798
for tg in this, base, other:
1800
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1801
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1802
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1803
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1804
for suffix in ('THIS', 'BASE', 'OTHER'):
1805
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1806
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1807
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1808
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1809
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1810
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1811
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1812
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1813
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1814
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1815
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1816
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1817
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1818
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1820
def test_filename_merge(self):
1821
root_id = generate_ids.gen_root_id()
1822
base = TransformGroup("BASE", root_id)
1823
this = TransformGroup("THIS", root_id)
1824
other = TransformGroup("OTHER", root_id)
1825
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1826
for t in [base, this, other]]
1827
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1828
for t in [base, this, other]]
1829
base.tt.new_directory('c', base_a, 'c')
1830
this.tt.new_directory('c1', this_a, 'c')
1831
other.tt.new_directory('c', other_b, 'c')
1833
base.tt.new_directory('d', base_a, 'd')
1834
this.tt.new_directory('d1', this_b, 'd')
1835
other.tt.new_directory('d', other_a, 'd')
1837
base.tt.new_directory('e', base_a, 'e')
1838
this.tt.new_directory('e', this_a, 'e')
1839
other.tt.new_directory('e1', other_b, 'e')
1841
base.tt.new_directory('f', base_a, 'f')
1842
this.tt.new_directory('f1', this_b, 'f')
1843
other.tt.new_directory('f1', other_b, 'f')
1845
for tg in [this, base, other]:
1847
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1848
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1849
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1850
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1851
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1853
def test_filename_merge_conflicts(self):
1854
root_id = generate_ids.gen_root_id()
1855
base = TransformGroup("BASE", root_id)
1856
this = TransformGroup("THIS", root_id)
1857
other = TransformGroup("OTHER", root_id)
1858
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1859
for t in [base, this, other]]
1860
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1861
for t in [base, this, other]]
1863
base.tt.new_file('g', base_a, 'g', 'g')
1864
other.tt.new_file('g1', other_b, 'g1', 'g')
1866
base.tt.new_file('h', base_a, 'h', 'h')
1867
this.tt.new_file('h1', this_b, 'h1', 'h')
1869
base.tt.new_file('i', base.root, 'i', 'i')
1870
other.tt.new_directory('i1', this_b, 'i')
1872
for tg in [this, base, other]:
1874
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1876
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1877
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1878
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1879
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1880
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1881
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1882
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1885
class TestBuildTree(tests.TestCaseWithTransport):
1887
def test_build_tree_with_symlinks(self):
1888
self.requireFeature(SymlinkFeature)
1890
a = BzrDir.create_standalone_workingtree('a')
1892
file('a/foo/bar', 'wb').write('contents')
1893
os.symlink('a/foo/bar', 'a/foo/baz')
1894
a.add(['foo', 'foo/bar', 'foo/baz'])
1895
a.commit('initial commit')
1896
b = BzrDir.create_standalone_workingtree('b')
1897
basis = a.basis_tree()
1899
self.addCleanup(basis.unlock)
1900
build_tree(basis, b)
1901
self.assertIs(os.path.isdir('b/foo'), True)
1902
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1903
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1905
def test_build_with_references(self):
1906
tree = self.make_branch_and_tree('source',
1907
format='dirstate-with-subtree')
1908
subtree = self.make_branch_and_tree('source/subtree',
1909
format='dirstate-with-subtree')
1910
tree.add_reference(subtree)
1911
tree.commit('a revision')
1912
tree.branch.create_checkout('target')
1913
self.assertPathExists('target')
1914
self.assertPathExists('target/subtree')
1916
def test_file_conflict_handling(self):
1917
"""Ensure that when building trees, conflict handling is done"""
1918
source = self.make_branch_and_tree('source')
1919
target = self.make_branch_and_tree('target')
1920
self.build_tree(['source/file', 'target/file'])
1921
source.add('file', 'new-file')
1922
source.commit('added file')
1923
build_tree(source.basis_tree(), target)
1924
self.assertEqual([DuplicateEntry('Moved existing file to',
1925
'file.moved', 'file', None, 'new-file')],
1927
target2 = self.make_branch_and_tree('target2')
1928
target_file = file('target2/file', 'wb')
1930
source_file = file('source/file', 'rb')
1932
target_file.write(source_file.read())
1937
build_tree(source.basis_tree(), target2)
1938
self.assertEqual([], target2.conflicts())
1940
def test_symlink_conflict_handling(self):
1941
"""Ensure that when building trees, conflict handling is done"""
1942
self.requireFeature(SymlinkFeature)
1943
source = self.make_branch_and_tree('source')
1944
os.symlink('foo', 'source/symlink')
1945
source.add('symlink', 'new-symlink')
1946
source.commit('added file')
1947
target = self.make_branch_and_tree('target')
1948
os.symlink('bar', 'target/symlink')
1949
build_tree(source.basis_tree(), target)
1950
self.assertEqual([DuplicateEntry('Moved existing file to',
1951
'symlink.moved', 'symlink', None, 'new-symlink')],
1953
target = self.make_branch_and_tree('target2')
1954
os.symlink('foo', 'target2/symlink')
1955
build_tree(source.basis_tree(), target)
1956
self.assertEqual([], target.conflicts())
1958
def test_directory_conflict_handling(self):
1959
"""Ensure that when building trees, conflict handling is done"""
1960
source = self.make_branch_and_tree('source')
1961
target = self.make_branch_and_tree('target')
1962
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1963
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1964
source.commit('added file')
1965
build_tree(source.basis_tree(), target)
1966
self.assertEqual([], target.conflicts())
1967
self.assertPathExists('target/dir1/file')
1969
# Ensure contents are merged
1970
target = self.make_branch_and_tree('target2')
1971
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1972
build_tree(source.basis_tree(), target)
1973
self.assertEqual([], target.conflicts())
1974
self.assertPathExists('target2/dir1/file2')
1975
self.assertPathExists('target2/dir1/file')
1977
# Ensure new contents are suppressed for existing branches
1978
target = self.make_branch_and_tree('target3')
1979
self.make_branch('target3/dir1')
1980
self.build_tree(['target3/dir1/file2'])
1981
build_tree(source.basis_tree(), target)
1982
self.assertPathDoesNotExist('target3/dir1/file')
1983
self.assertPathExists('target3/dir1/file2')
1984
self.assertPathExists('target3/dir1.diverted/file')
1985
self.assertEqual([DuplicateEntry('Diverted to',
1986
'dir1.diverted', 'dir1', 'new-dir1', None)],
1989
target = self.make_branch_and_tree('target4')
1990
self.build_tree(['target4/dir1/'])
1991
self.make_branch('target4/dir1/file')
1992
build_tree(source.basis_tree(), target)
1993
self.assertPathExists('target4/dir1/file')
1994
self.assertEqual('directory', file_kind('target4/dir1/file'))
1995
self.assertPathExists('target4/dir1/file.diverted')
1996
self.assertEqual([DuplicateEntry('Diverted to',
1997
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
2000
def test_mixed_conflict_handling(self):
2001
"""Ensure that when building trees, conflict handling is done"""
2002
source = self.make_branch_and_tree('source')
2003
target = self.make_branch_and_tree('target')
2004
self.build_tree(['source/name', 'target/name/'])
2005
source.add('name', 'new-name')
2006
source.commit('added file')
2007
build_tree(source.basis_tree(), target)
2008
self.assertEqual([DuplicateEntry('Moved existing file to',
2009
'name.moved', 'name', None, 'new-name')], target.conflicts())
2011
def test_raises_in_populated(self):
2012
source = self.make_branch_and_tree('source')
2013
self.build_tree(['source/name'])
2015
source.commit('added name')
2016
target = self.make_branch_and_tree('target')
2017
self.build_tree(['target/name'])
2019
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2020
build_tree, source.basis_tree(), target)
2022
def test_build_tree_rename_count(self):
2023
source = self.make_branch_and_tree('source')
2024
self.build_tree(['source/file1', 'source/dir1/'])
2025
source.add(['file1', 'dir1'])
2026
source.commit('add1')
2027
target1 = self.make_branch_and_tree('target1')
2028
transform_result = build_tree(source.basis_tree(), target1)
2029
self.assertEqual(2, transform_result.rename_count)
2031
self.build_tree(['source/dir1/file2'])
2032
source.add(['dir1/file2'])
2033
source.commit('add3')
2034
target2 = self.make_branch_and_tree('target2')
2035
transform_result = build_tree(source.basis_tree(), target2)
2036
# children of non-root directories should not be renamed
2037
self.assertEqual(2, transform_result.rename_count)
2039
def create_ab_tree(self):
2040
"""Create a committed test tree with two files"""
2041
source = self.make_branch_and_tree('source')
2042
self.build_tree_contents([('source/file1', 'A')])
2043
self.build_tree_contents([('source/file2', 'B')])
2044
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2045
source.commit('commit files')
2047
self.addCleanup(source.unlock)
2050
def test_build_tree_accelerator_tree(self):
2051
source = self.create_ab_tree()
2052
self.build_tree_contents([('source/file2', 'C')])
2054
real_source_get_file = source.get_file
2055
def get_file(file_id, path=None):
2056
calls.append(file_id)
2057
return real_source_get_file(file_id, path)
2058
source.get_file = get_file
2059
target = self.make_branch_and_tree('target')
2060
revision_tree = source.basis_tree()
2061
revision_tree.lock_read()
2062
self.addCleanup(revision_tree.unlock)
2063
build_tree(revision_tree, target, source)
2064
self.assertEqual(['file1-id'], calls)
2066
self.addCleanup(target.unlock)
2067
self.assertEqual([], list(target.iter_changes(revision_tree)))
2069
def test_build_tree_accelerator_tree_observes_sha1(self):
2070
source = self.create_ab_tree()
2071
sha1 = osutils.sha_string('A')
2072
target = self.make_branch_and_tree('target')
2074
self.addCleanup(target.unlock)
2075
state = target.current_dirstate()
2076
state._cutoff_time = time.time() + 60
2077
build_tree(source.basis_tree(), target, source)
2078
entry = state._get_entry(0, path_utf8='file1')
2079
self.assertEqual(sha1, entry[1][0][1])
2081
def test_build_tree_accelerator_tree_missing_file(self):
2082
source = self.create_ab_tree()
2083
os.unlink('source/file1')
2084
source.remove(['file2'])
2085
target = self.make_branch_and_tree('target')
2086
revision_tree = source.basis_tree()
2087
revision_tree.lock_read()
2088
self.addCleanup(revision_tree.unlock)
2089
build_tree(revision_tree, target, source)
2091
self.addCleanup(target.unlock)
2092
self.assertEqual([], list(target.iter_changes(revision_tree)))
2094
def test_build_tree_accelerator_wrong_kind(self):
2095
self.requireFeature(SymlinkFeature)
2096
source = self.make_branch_and_tree('source')
2097
self.build_tree_contents([('source/file1', '')])
2098
self.build_tree_contents([('source/file2', '')])
2099
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2100
source.commit('commit files')
2101
os.unlink('source/file2')
2102
self.build_tree_contents([('source/file2/', 'C')])
2103
os.unlink('source/file1')
2104
os.symlink('file2', 'source/file1')
2106
real_source_get_file = source.get_file
2107
def get_file(file_id, path=None):
2108
calls.append(file_id)
2109
return real_source_get_file(file_id, path)
2110
source.get_file = get_file
2111
target = self.make_branch_and_tree('target')
2112
revision_tree = source.basis_tree()
2113
revision_tree.lock_read()
2114
self.addCleanup(revision_tree.unlock)
2115
build_tree(revision_tree, target, source)
2116
self.assertEqual([], calls)
2118
self.addCleanup(target.unlock)
2119
self.assertEqual([], list(target.iter_changes(revision_tree)))
2121
def test_build_tree_hardlink(self):
2122
self.requireFeature(HardlinkFeature)
2123
source = self.create_ab_tree()
2124
target = self.make_branch_and_tree('target')
2125
revision_tree = source.basis_tree()
2126
revision_tree.lock_read()
2127
self.addCleanup(revision_tree.unlock)
2128
build_tree(revision_tree, target, source, hardlink=True)
2130
self.addCleanup(target.unlock)
2131
self.assertEqual([], list(target.iter_changes(revision_tree)))
2132
source_stat = os.stat('source/file1')
2133
target_stat = os.stat('target/file1')
2134
self.assertEqual(source_stat, target_stat)
2136
# Explicitly disallowing hardlinks should prevent them.
2137
target2 = self.make_branch_and_tree('target2')
2138
build_tree(revision_tree, target2, source, hardlink=False)
2140
self.addCleanup(target2.unlock)
2141
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2142
source_stat = os.stat('source/file1')
2143
target2_stat = os.stat('target2/file1')
2144
self.assertNotEqual(source_stat, target2_stat)
2146
def test_build_tree_accelerator_tree_moved(self):
2147
source = self.make_branch_and_tree('source')
2148
self.build_tree_contents([('source/file1', 'A')])
2149
source.add(['file1'], ['file1-id'])
2150
source.commit('commit files')
2151
source.rename_one('file1', 'file2')
2153
self.addCleanup(source.unlock)
2154
target = self.make_branch_and_tree('target')
2155
revision_tree = source.basis_tree()
2156
revision_tree.lock_read()
2157
self.addCleanup(revision_tree.unlock)
2158
build_tree(revision_tree, target, source)
2160
self.addCleanup(target.unlock)
2161
self.assertEqual([], list(target.iter_changes(revision_tree)))
2163
def test_build_tree_hardlinks_preserve_execute(self):
2164
self.requireFeature(HardlinkFeature)
2165
source = self.create_ab_tree()
2166
tt = TreeTransform(source)
2167
trans_id = tt.trans_id_tree_file_id('file1-id')
2168
tt.set_executability(True, trans_id)
2170
self.assertTrue(source.is_executable('file1-id'))
2171
target = self.make_branch_and_tree('target')
2172
revision_tree = source.basis_tree()
2173
revision_tree.lock_read()
2174
self.addCleanup(revision_tree.unlock)
2175
build_tree(revision_tree, target, source, hardlink=True)
2177
self.addCleanup(target.unlock)
2178
self.assertEqual([], list(target.iter_changes(revision_tree)))
2179
self.assertTrue(source.is_executable('file1-id'))
2181
def install_rot13_content_filter(self, pattern):
2183
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2184
# below, but that looks a bit... hard to read even if it's exactly
2186
original_registry = filters._reset_registry()
2187
def restore_registry():
2188
filters._reset_registry(original_registry)
2189
self.addCleanup(restore_registry)
2190
def rot13(chunks, context=None):
2191
return [''.join(chunks).encode('rot13')]
2192
rot13filter = filters.ContentFilter(rot13, rot13)
2193
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2194
os.mkdir(self.test_home_dir + '/.bazaar')
2195
rules_filename = self.test_home_dir + '/.bazaar/rules'
2196
f = open(rules_filename, 'wb')
2197
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2199
def uninstall_rules():
2200
os.remove(rules_filename)
2202
self.addCleanup(uninstall_rules)
2205
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2206
"""build_tree will not hardlink files that have content filtering rules
2207
applied to them (but will still hardlink other files from the same tree
2210
self.requireFeature(HardlinkFeature)
2211
self.install_rot13_content_filter('file1')
2212
source = self.create_ab_tree()
2213
target = self.make_branch_and_tree('target')
2214
revision_tree = source.basis_tree()
2215
revision_tree.lock_read()
2216
self.addCleanup(revision_tree.unlock)
2217
build_tree(revision_tree, target, source, hardlink=True)
2219
self.addCleanup(target.unlock)
2220
self.assertEqual([], list(target.iter_changes(revision_tree)))
2221
source_stat = os.stat('source/file1')
2222
target_stat = os.stat('target/file1')
2223
self.assertNotEqual(source_stat, target_stat)
2224
source_stat = os.stat('source/file2')
2225
target_stat = os.stat('target/file2')
2226
self.assertEqualStat(source_stat, target_stat)
2228
def test_case_insensitive_build_tree_inventory(self):
2229
if (features.CaseInsensitiveFilesystemFeature.available()
2230
or features.CaseInsCasePresFilenameFeature.available()):
2231
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2232
source = self.make_branch_and_tree('source')
2233
self.build_tree(['source/file', 'source/FILE'])
2234
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2235
source.commit('added files')
2236
# Don't try this at home, kids!
2237
# Force the tree to report that it is case insensitive
2238
target = self.make_branch_and_tree('target')
2239
target.case_sensitive = False
2240
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2241
self.assertEqual('file.moved', target.id2path('lower-id'))
2242
self.assertEqual('FILE', target.id2path('upper-id'))
2244
def test_build_tree_observes_sha(self):
2245
source = self.make_branch_and_tree('source')
2246
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2247
source.add(['file1', 'dir', 'dir/file2'],
2248
['file1-id', 'dir-id', 'file2-id'])
2249
source.commit('new files')
2250
target = self.make_branch_and_tree('target')
2252
self.addCleanup(target.unlock)
2253
# We make use of the fact that DirState caches its cutoff time. So we
2254
# set the 'safe' time to one minute in the future.
2255
state = target.current_dirstate()
2256
state._cutoff_time = time.time() + 60
2257
build_tree(source.basis_tree(), target)
2258
entry1_sha = osutils.sha_file_by_name('source/file1')
2259
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2260
# entry[1] is the state information, entry[1][0] is the state of the
2261
# working tree, entry[1][0][1] is the sha value for the current working
2263
entry1 = state._get_entry(0, path_utf8='file1')
2264
self.assertEqual(entry1_sha, entry1[1][0][1])
2265
# The 'size' field must also be set.
2266
self.assertEqual(25, entry1[1][0][2])
2267
entry1_state = entry1[1][0]
2268
entry2 = state._get_entry(0, path_utf8='dir/file2')
2269
self.assertEqual(entry2_sha, entry2[1][0][1])
2270
self.assertEqual(29, entry2[1][0][2])
2271
entry2_state = entry2[1][0]
2272
# Now, make sure that we don't have to re-read the content. The
2273
# packed_stat should match exactly.
2274
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2275
self.assertEqual(entry2_sha,
2276
target.get_file_sha1('file2-id', 'dir/file2'))
2277
self.assertEqual(entry1_state, entry1[1][0])
2278
self.assertEqual(entry2_state, entry2[1][0])
2281
class TestCommitTransform(tests.TestCaseWithTransport):
2283
def get_branch(self):
2284
tree = self.make_branch_and_tree('tree')
2286
self.addCleanup(tree.unlock)
2287
tree.commit('empty commit')
2290
def get_branch_and_transform(self):
2291
branch = self.get_branch()
2292
tt = TransformPreview(branch.basis_tree())
2293
self.addCleanup(tt.finalize)
2296
def test_commit_wrong_basis(self):
2297
branch = self.get_branch()
2298
basis = branch.repository.revision_tree(
2299
_mod_revision.NULL_REVISION)
2300
tt = TransformPreview(basis)
2301
self.addCleanup(tt.finalize)
2302
e = self.assertRaises(ValueError, tt.commit, branch, '')
2303
self.assertEqual('TreeTransform not based on branch basis: null:',
2306
def test_empy_commit(self):
2307
branch, tt = self.get_branch_and_transform()
2308
rev = tt.commit(branch, 'my message')
2309
self.assertEqual(2, branch.revno())
2310
repo = branch.repository
2311
self.assertEqual('my message', repo.get_revision(rev).message)
2313
def test_merge_parents(self):
2314
branch, tt = self.get_branch_and_transform()
2315
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2316
self.assertEqual(['rev1b', 'rev1c'],
2317
branch.basis_tree().get_parent_ids()[1:])
2319
def test_first_commit(self):
2320
branch = self.make_branch('branch')
2322
self.addCleanup(branch.unlock)
2323
tt = TransformPreview(branch.basis_tree())
2324
self.addCleanup(tt.finalize)
2325
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2326
rev = tt.commit(branch, 'my message')
2327
self.assertEqual([], branch.basis_tree().get_parent_ids())
2328
self.assertNotEqual(_mod_revision.NULL_REVISION,
2329
branch.last_revision())
2331
def test_first_commit_with_merge_parents(self):
2332
branch = self.make_branch('branch')
2334
self.addCleanup(branch.unlock)
2335
tt = TransformPreview(branch.basis_tree())
2336
self.addCleanup(tt.finalize)
2337
e = self.assertRaises(ValueError, tt.commit, branch,
2338
'my message', ['rev1b-id'])
2339
self.assertEqual('Cannot supply merge parents for first commit.',
2341
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2343
def test_add_files(self):
2344
branch, tt = self.get_branch_and_transform()
2345
tt.new_file('file', tt.root, 'contents', 'file-id')
2346
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2347
if SymlinkFeature.available():
2348
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2349
rev = tt.commit(branch, 'message')
2350
tree = branch.basis_tree()
2351
self.assertEqual('file', tree.id2path('file-id'))
2352
self.assertEqual('contents', tree.get_file_text('file-id'))
2353
self.assertEqual('dir', tree.id2path('dir-id'))
2354
if SymlinkFeature.available():
2355
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2356
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2358
def test_add_unversioned(self):
2359
branch, tt = self.get_branch_and_transform()
2360
tt.new_file('file', tt.root, 'contents')
2361
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2362
'message', strict=True)
2364
def test_modify_strict(self):
2365
branch, tt = self.get_branch_and_transform()
2366
tt.new_file('file', tt.root, 'contents', 'file-id')
2367
tt.commit(branch, 'message', strict=True)
2368
tt = TransformPreview(branch.basis_tree())
2369
self.addCleanup(tt.finalize)
2370
trans_id = tt.trans_id_file_id('file-id')
2371
tt.delete_contents(trans_id)
2372
tt.create_file('contents', trans_id)
2373
tt.commit(branch, 'message', strict=True)
2375
def test_commit_malformed(self):
2376
"""Committing a malformed transform should raise an exception.
2378
In this case, we are adding a file without adding its parent.
2380
branch, tt = self.get_branch_and_transform()
2381
parent_id = tt.trans_id_file_id('parent-id')
2382
tt.new_file('file', parent_id, 'contents', 'file-id')
2383
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2386
def test_commit_rich_revision_data(self):
2387
branch, tt = self.get_branch_and_transform()
2388
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2389
committer='me <me@example.com>',
2390
revprops={'foo': 'bar'}, revision_id='revid-1',
2391
authors=['Author1 <author1@example.com>',
2392
'Author2 <author2@example.com>',
2394
self.assertEqual('revid-1', rev_id)
2395
revision = branch.repository.get_revision(rev_id)
2396
self.assertEqual(1, revision.timestamp)
2397
self.assertEqual(43201, revision.timezone)
2398
self.assertEqual('me <me@example.com>', revision.committer)
2399
self.assertEqual(['Author1 <author1@example.com>',
2400
'Author2 <author2@example.com>'],
2401
revision.get_apparent_authors())
2402
del revision.properties['authors']
2403
self.assertEqual({'foo': 'bar',
2404
'branch-nick': 'tree'},
2405
revision.properties)
2407
def test_no_explicit_revprops(self):
2408
branch, tt = self.get_branch_and_transform()
2409
rev_id = tt.commit(branch, 'message', authors=[
2410
'Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>', ])
2412
revision = branch.repository.get_revision(rev_id)
2413
self.assertEqual(['Author1 <author1@example.com>',
2414
'Author2 <author2@example.com>'],
2415
revision.get_apparent_authors())
2416
self.assertEqual('tree', revision.properties['branch-nick'])
2419
class TestBackupName(tests.TestCase):
2421
def test_deprecations(self):
2422
class MockTransform(object):
2424
def has_named_child(self, by_parent, parent_id, name):
2425
return name in by_parent.get(parent_id, [])
2427
class MockEntry(object):
2430
object.__init__(self)
2433
tt = MockTransform()
2434
name1 = self.applyDeprecated(
2435
symbol_versioning.deprecated_in((2, 3, 0)),
2436
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2437
self.assertEqual('name.~1~', name1)
2438
name2 = self.applyDeprecated(
2439
symbol_versioning.deprecated_in((2, 3, 0)),
2440
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2441
self.assertEqual('name.~2~', name2)
2444
class TestFileMover(tests.TestCaseWithTransport):
2446
def test_file_mover(self):
2447
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2448
mover = _FileMover()
2449
mover.rename('a', 'q')
2450
self.assertPathExists('q')
2451
self.assertPathDoesNotExist('a')
2452
self.assertPathExists('q/b')
2453
self.assertPathExists('c')
2454
self.assertPathExists('c/d')
2456
def test_pre_delete_rollback(self):
2457
self.build_tree(['a/'])
2458
mover = _FileMover()
2459
mover.pre_delete('a', 'q')
2460
self.assertPathExists('q')
2461
self.assertPathDoesNotExist('a')
2463
self.assertPathDoesNotExist('q')
2464
self.assertPathExists('a')
2466
def test_apply_deletions(self):
2467
self.build_tree(['a/', 'b/'])
2468
mover = _FileMover()
2469
mover.pre_delete('a', 'q')
2470
mover.pre_delete('b', 'r')
2471
self.assertPathExists('q')
2472
self.assertPathExists('r')
2473
self.assertPathDoesNotExist('a')
2474
self.assertPathDoesNotExist('b')
2475
mover.apply_deletions()
2476
self.assertPathDoesNotExist('q')
2477
self.assertPathDoesNotExist('r')
2478
self.assertPathDoesNotExist('a')
2479
self.assertPathDoesNotExist('b')
2481
def test_file_mover_rollback(self):
2482
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2483
mover = _FileMover()
2484
mover.rename('c/d', 'c/f')
2485
mover.rename('c/e', 'c/d')
2487
mover.rename('a', 'c')
2488
except errors.FileExists, e:
2490
self.assertPathExists('a')
2491
self.assertPathExists('c/d')
2494
class Bogus(Exception):
2498
class TestTransformRollback(tests.TestCaseWithTransport):
2500
class ExceptionFileMover(_FileMover):
2502
def __init__(self, bad_source=None, bad_target=None):
2503
_FileMover.__init__(self)
2504
self.bad_source = bad_source
2505
self.bad_target = bad_target
2507
def rename(self, source, target):
2508
if (self.bad_source is not None and
2509
source.endswith(self.bad_source)):
2511
elif (self.bad_target is not None and
2512
target.endswith(self.bad_target)):
2515
_FileMover.rename(self, source, target)
2517
def test_rollback_rename(self):
2518
tree = self.make_branch_and_tree('.')
2519
self.build_tree(['a/', 'a/b'])
2520
tt = TreeTransform(tree)
2521
self.addCleanup(tt.finalize)
2522
a_id = tt.trans_id_tree_path('a')
2523
tt.adjust_path('c', tt.root, a_id)
2524
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2525
self.assertRaises(Bogus, tt.apply,
2526
_mover=self.ExceptionFileMover(bad_source='a'))
2527
self.assertPathExists('a')
2528
self.assertPathExists('a/b')
2530
self.assertPathExists('c')
2531
self.assertPathExists('c/d')
2533
def test_rollback_rename_into_place(self):
2534
tree = self.make_branch_and_tree('.')
2535
self.build_tree(['a/', 'a/b'])
2536
tt = TreeTransform(tree)
2537
self.addCleanup(tt.finalize)
2538
a_id = tt.trans_id_tree_path('a')
2539
tt.adjust_path('c', tt.root, a_id)
2540
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2541
self.assertRaises(Bogus, tt.apply,
2542
_mover=self.ExceptionFileMover(bad_target='c/d'))
2543
self.assertPathExists('a')
2544
self.assertPathExists('a/b')
2546
self.assertPathExists('c')
2547
self.assertPathExists('c/d')
2549
def test_rollback_deletion(self):
2550
tree = self.make_branch_and_tree('.')
2551
self.build_tree(['a/', 'a/b'])
2552
tt = TreeTransform(tree)
2553
self.addCleanup(tt.finalize)
2554
a_id = tt.trans_id_tree_path('a')
2555
tt.delete_contents(a_id)
2556
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2557
self.assertRaises(Bogus, tt.apply,
2558
_mover=self.ExceptionFileMover(bad_target='d'))
2559
self.assertPathExists('a')
2560
self.assertPathExists('a/b')
2563
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2564
"""Ensure treetransform creation errors can be safely cleaned up after"""
2566
def _override_globals_in_method(self, instance, method_name, globals):
2567
"""Replace method on instance with one with updated globals"""
2569
func = getattr(instance, method_name).im_func
2570
new_globals = dict(func.func_globals)
2571
new_globals.update(globals)
2572
new_func = types.FunctionType(func.func_code, new_globals,
2573
func.func_name, func.func_defaults)
2574
setattr(instance, method_name,
2575
types.MethodType(new_func, instance, instance.__class__))
2576
self.addCleanup(delattr, instance, method_name)
2579
def _fake_open_raises_before(name, mode):
2580
"""Like open() but raises before doing anything"""
2584
def _fake_open_raises_after(name, mode):
2585
"""Like open() but raises after creating file without returning"""
2586
open(name, mode).close()
2589
def create_transform_and_root_trans_id(self):
2590
"""Setup a transform creating a file in limbo"""
2591
tree = self.make_branch_and_tree('.')
2592
tt = TreeTransform(tree)
2593
return tt, tt.create_path("a", tt.root)
2595
def create_transform_and_subdir_trans_id(self):
2596
"""Setup a transform creating a directory containing a file in limbo"""
2597
tree = self.make_branch_and_tree('.')
2598
tt = TreeTransform(tree)
2599
d_trans_id = tt.create_path("d", tt.root)
2600
tt.create_directory(d_trans_id)
2601
f_trans_id = tt.create_path("a", d_trans_id)
2602
tt.adjust_path("a", d_trans_id, f_trans_id)
2603
return tt, f_trans_id
2605
def test_root_create_file_open_raises_before_creation(self):
2606
tt, trans_id = self.create_transform_and_root_trans_id()
2607
self._override_globals_in_method(tt, "create_file",
2608
{"open": self._fake_open_raises_before})
2609
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2610
path = tt._limbo_name(trans_id)
2611
self.assertPathDoesNotExist(path)
2613
self.assertPathDoesNotExist(tt._limbodir)
2615
def test_root_create_file_open_raises_after_creation(self):
2616
tt, trans_id = self.create_transform_and_root_trans_id()
2617
self._override_globals_in_method(tt, "create_file",
2618
{"open": self._fake_open_raises_after})
2619
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2620
path = tt._limbo_name(trans_id)
2621
self.assertPathExists(path)
2623
self.assertPathDoesNotExist(path)
2624
self.assertPathDoesNotExist(tt._limbodir)
2626
def test_subdir_create_file_open_raises_before_creation(self):
2627
tt, trans_id = self.create_transform_and_subdir_trans_id()
2628
self._override_globals_in_method(tt, "create_file",
2629
{"open": self._fake_open_raises_before})
2630
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2631
path = tt._limbo_name(trans_id)
2632
self.assertPathDoesNotExist(path)
2634
self.assertPathDoesNotExist(tt._limbodir)
2636
def test_subdir_create_file_open_raises_after_creation(self):
2637
tt, trans_id = self.create_transform_and_subdir_trans_id()
2638
self._override_globals_in_method(tt, "create_file",
2639
{"open": self._fake_open_raises_after})
2640
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2641
path = tt._limbo_name(trans_id)
2642
self.assertPathExists(path)
2644
self.assertPathDoesNotExist(path)
2645
self.assertPathDoesNotExist(tt._limbodir)
2647
def test_rename_in_limbo_rename_raises_after_rename(self):
2648
tt, trans_id = self.create_transform_and_root_trans_id()
2649
parent1 = tt.new_directory('parent1', tt.root)
2650
child1 = tt.new_file('child1', parent1, 'contents')
2651
parent2 = tt.new_directory('parent2', tt.root)
2653
class FakeOSModule(object):
2654
def rename(self, old, new):
2657
self._override_globals_in_method(tt, "_rename_in_limbo",
2658
{"os": FakeOSModule()})
2660
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2661
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2662
self.assertPathExists(path)
2664
self.assertPathDoesNotExist(path)
2665
self.assertPathDoesNotExist(tt._limbodir)
2667
def test_rename_in_limbo_rename_raises_before_rename(self):
2668
tt, trans_id = self.create_transform_and_root_trans_id()
2669
parent1 = tt.new_directory('parent1', tt.root)
2670
child1 = tt.new_file('child1', parent1, 'contents')
2671
parent2 = tt.new_directory('parent2', tt.root)
2673
class FakeOSModule(object):
2674
def rename(self, old, new):
2676
self._override_globals_in_method(tt, "_rename_in_limbo",
2677
{"os": FakeOSModule()})
2679
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2680
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2681
self.assertPathExists(path)
2683
self.assertPathDoesNotExist(path)
2684
self.assertPathDoesNotExist(tt._limbodir)
2687
class TestTransformMissingParent(tests.TestCaseWithTransport):
2689
def make_tt_with_versioned_dir(self):
2690
wt = self.make_branch_and_tree('.')
2691
self.build_tree(['dir/',])
2692
wt.add(['dir'], ['dir-id'])
2693
wt.commit('Create dir')
2694
tt = TreeTransform(wt)
2695
self.addCleanup(tt.finalize)
2698
def test_resolve_create_parent_for_versioned_file(self):
2699
wt, tt = self.make_tt_with_versioned_dir()
2700
dir_tid = tt.trans_id_tree_file_id('dir-id')
2701
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2702
tt.delete_contents(dir_tid)
2703
tt.unversion_file(dir_tid)
2704
conflicts = resolve_conflicts(tt)
2705
# one conflict for the missing directory, one for the unversioned
2707
self.assertLength(2, conflicts)
2709
def test_non_versioned_file_create_conflict(self):
2710
wt, tt = self.make_tt_with_versioned_dir()
2711
dir_tid = tt.trans_id_tree_file_id('dir-id')
2712
tt.new_file('file', dir_tid, 'Contents')
2713
tt.delete_contents(dir_tid)
2714
tt.unversion_file(dir_tid)
2715
conflicts = resolve_conflicts(tt)
2716
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2717
self.assertLength(1, conflicts)
2718
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2722
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2723
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2725
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2726
('', ''), ('directory', 'directory'), (False, False))
2729
class TestTransformPreview(tests.TestCaseWithTransport):
2731
def create_tree(self):
2732
tree = self.make_branch_and_tree('.')
2733
self.build_tree_contents([('a', 'content 1')])
2734
tree.set_root_id('TREE_ROOT')
2735
tree.add('a', 'a-id')
2736
tree.commit('rev1', rev_id='rev1')
2737
return tree.branch.repository.revision_tree('rev1')
2739
def get_empty_preview(self):
2740
repository = self.make_repository('repo')
2741
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2742
preview = TransformPreview(tree)
2743
self.addCleanup(preview.finalize)
2746
def test_transform_preview(self):
2747
revision_tree = self.create_tree()
2748
preview = TransformPreview(revision_tree)
2749
self.addCleanup(preview.finalize)
2751
def test_transform_preview_tree(self):
2752
revision_tree = self.create_tree()
2753
preview = TransformPreview(revision_tree)
2754
self.addCleanup(preview.finalize)
2755
preview.get_preview_tree()
2757
def test_transform_new_file(self):
2758
revision_tree = self.create_tree()
2759
preview = TransformPreview(revision_tree)
2760
self.addCleanup(preview.finalize)
2761
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2762
preview_tree = preview.get_preview_tree()
2763
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2765
preview_tree.get_file('file2-id').read(), 'content B\n')
2767
def test_diff_preview_tree(self):
2768
revision_tree = self.create_tree()
2769
preview = TransformPreview(revision_tree)
2770
self.addCleanup(preview.finalize)
2771
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2772
preview_tree = preview.get_preview_tree()
2774
show_diff_trees(revision_tree, preview_tree, out)
2775
lines = out.getvalue().splitlines()
2776
self.assertEqual(lines[0], "=== added file 'file2'")
2777
# 3 lines of diff administrivia
2778
self.assertEqual(lines[4], "+content B")
2780
def test_transform_conflicts(self):
2781
revision_tree = self.create_tree()
2782
preview = TransformPreview(revision_tree)
2783
self.addCleanup(preview.finalize)
2784
preview.new_file('a', preview.root, 'content 2')
2785
resolve_conflicts(preview)
2786
trans_id = preview.trans_id_file_id('a-id')
2787
self.assertEqual('a.moved', preview.final_name(trans_id))
2789
def get_tree_and_preview_tree(self):
2790
revision_tree = self.create_tree()
2791
preview = TransformPreview(revision_tree)
2792
self.addCleanup(preview.finalize)
2793
a_trans_id = preview.trans_id_file_id('a-id')
2794
preview.delete_contents(a_trans_id)
2795
preview.create_file('b content', a_trans_id)
2796
preview_tree = preview.get_preview_tree()
2797
return revision_tree, preview_tree
2799
def test_iter_changes(self):
2800
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2801
root = revision_tree.inventory.root.file_id
2802
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2803
(root, root), ('a', 'a'), ('file', 'file'),
2805
list(preview_tree.iter_changes(revision_tree)))
2807
def test_include_unchanged_succeeds(self):
2808
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2809
changes = preview_tree.iter_changes(revision_tree,
2810
include_unchanged=True)
2811
root = revision_tree.inventory.root.file_id
2813
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2815
def test_specific_files(self):
2816
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2817
changes = preview_tree.iter_changes(revision_tree,
2818
specific_files=[''])
2819
self.assertEqual([A_ENTRY], list(changes))
2821
def test_want_unversioned(self):
2822
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2823
changes = preview_tree.iter_changes(revision_tree,
2824
want_unversioned=True)
2825
self.assertEqual([A_ENTRY], list(changes))
2827
def test_ignore_extra_trees_no_specific_files(self):
2828
# extra_trees is harmless without specific_files, so we'll silently
2829
# accept it, even though we won't use it.
2830
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2831
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2833
def test_ignore_require_versioned_no_specific_files(self):
2834
# require_versioned is meaningless without specific_files.
2835
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2836
preview_tree.iter_changes(revision_tree, require_versioned=False)
2838
def test_ignore_pb(self):
2839
# pb could be supported, but TT.iter_changes doesn't support it.
2840
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2841
preview_tree.iter_changes(revision_tree)
2843
def test_kind(self):
2844
revision_tree = self.create_tree()
2845
preview = TransformPreview(revision_tree)
2846
self.addCleanup(preview.finalize)
2847
preview.new_file('file', preview.root, 'contents', 'file-id')
2848
preview.new_directory('directory', preview.root, 'dir-id')
2849
preview_tree = preview.get_preview_tree()
2850
self.assertEqual('file', preview_tree.kind('file-id'))
2851
self.assertEqual('directory', preview_tree.kind('dir-id'))
2853
def test_get_file_mtime(self):
2854
preview = self.get_empty_preview()
2855
file_trans_id = preview.new_file('file', preview.root, 'contents',
2857
limbo_path = preview._limbo_name(file_trans_id)
2858
preview_tree = preview.get_preview_tree()
2859
self.assertEqual(os.stat(limbo_path).st_mtime,
2860
preview_tree.get_file_mtime('file-id'))
2862
def test_get_file_mtime_renamed(self):
2863
work_tree = self.make_branch_and_tree('tree')
2864
self.build_tree(['tree/file'])
2865
work_tree.add('file', 'file-id')
2866
preview = TransformPreview(work_tree)
2867
self.addCleanup(preview.finalize)
2868
file_trans_id = preview.trans_id_tree_file_id('file-id')
2869
preview.adjust_path('renamed', preview.root, file_trans_id)
2870
preview_tree = preview.get_preview_tree()
2871
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2872
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2874
def test_get_file_size(self):
2875
work_tree = self.make_branch_and_tree('tree')
2876
self.build_tree_contents([('tree/old', 'old')])
2877
work_tree.add('old', 'old-id')
2878
preview = TransformPreview(work_tree)
2879
self.addCleanup(preview.finalize)
2880
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2882
tree = preview.get_preview_tree()
2883
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2884
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2886
def test_get_file(self):
2887
preview = self.get_empty_preview()
2888
preview.new_file('file', preview.root, 'contents', 'file-id')
2889
preview_tree = preview.get_preview_tree()
2890
tree_file = preview_tree.get_file('file-id')
2892
self.assertEqual('contents', tree_file.read())
2896
def test_get_symlink_target(self):
2897
self.requireFeature(SymlinkFeature)
2898
preview = self.get_empty_preview()
2899
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2900
preview_tree = preview.get_preview_tree()
2901
self.assertEqual('target',
2902
preview_tree.get_symlink_target('symlink-id'))
2904
def test_all_file_ids(self):
2905
tree = self.make_branch_and_tree('tree')
2906
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2907
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2908
preview = TransformPreview(tree)
2909
self.addCleanup(preview.finalize)
2910
preview.unversion_file(preview.trans_id_file_id('b-id'))
2911
c_trans_id = preview.trans_id_file_id('c-id')
2912
preview.unversion_file(c_trans_id)
2913
preview.version_file('c-id', c_trans_id)
2914
preview_tree = preview.get_preview_tree()
2915
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2916
preview_tree.all_file_ids())
2918
def test_path2id_deleted_unchanged(self):
2919
tree = self.make_branch_and_tree('tree')
2920
self.build_tree(['tree/unchanged', 'tree/deleted'])
2921
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2922
preview = TransformPreview(tree)
2923
self.addCleanup(preview.finalize)
2924
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2925
preview_tree = preview.get_preview_tree()
2926
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2927
self.assertIs(None, preview_tree.path2id('deleted'))
2929
def test_path2id_created(self):
2930
tree = self.make_branch_and_tree('tree')
2931
self.build_tree(['tree/unchanged'])
2932
tree.add(['unchanged'], ['unchanged-id'])
2933
preview = TransformPreview(tree)
2934
self.addCleanup(preview.finalize)
2935
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2936
'contents', 'new-id')
2937
preview_tree = preview.get_preview_tree()
2938
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2940
def test_path2id_moved(self):
2941
tree = self.make_branch_and_tree('tree')
2942
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2943
tree.add(['old_parent', 'old_parent/child'],
2944
['old_parent-id', 'child-id'])
2945
preview = TransformPreview(tree)
2946
self.addCleanup(preview.finalize)
2947
new_parent = preview.new_directory('new_parent', preview.root,
2949
preview.adjust_path('child', new_parent,
2950
preview.trans_id_file_id('child-id'))
2951
preview_tree = preview.get_preview_tree()
2952
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2953
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2955
def test_path2id_renamed_parent(self):
2956
tree = self.make_branch_and_tree('tree')
2957
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2958
tree.add(['old_name', 'old_name/child'],
2959
['parent-id', 'child-id'])
2960
preview = TransformPreview(tree)
2961
self.addCleanup(preview.finalize)
2962
preview.adjust_path('new_name', preview.root,
2963
preview.trans_id_file_id('parent-id'))
2964
preview_tree = preview.get_preview_tree()
2965
self.assertIs(None, preview_tree.path2id('old_name/child'))
2966
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2968
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2969
preview_tree = tt.get_preview_tree()
2970
preview_result = list(preview_tree.iter_entries_by_dir(
2974
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2975
self.assertEqual(actual_result, preview_result)
2977
def test_iter_entries_by_dir_new(self):
2978
tree = self.make_branch_and_tree('tree')
2979
tt = TreeTransform(tree)
2980
tt.new_file('new', tt.root, 'contents', 'new-id')
2981
self.assertMatchingIterEntries(tt)
2983
def test_iter_entries_by_dir_deleted(self):
2984
tree = self.make_branch_and_tree('tree')
2985
self.build_tree(['tree/deleted'])
2986
tree.add('deleted', 'deleted-id')
2987
tt = TreeTransform(tree)
2988
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2989
self.assertMatchingIterEntries(tt)
2991
def test_iter_entries_by_dir_unversioned(self):
2992
tree = self.make_branch_and_tree('tree')
2993
self.build_tree(['tree/removed'])
2994
tree.add('removed', 'removed-id')
2995
tt = TreeTransform(tree)
2996
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2997
self.assertMatchingIterEntries(tt)
2999
def test_iter_entries_by_dir_moved(self):
3000
tree = self.make_branch_and_tree('tree')
3001
self.build_tree(['tree/moved', 'tree/new_parent/'])
3002
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
3003
tt = TreeTransform(tree)
3004
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
3005
tt.trans_id_file_id('moved-id'))
3006
self.assertMatchingIterEntries(tt)
3008
def test_iter_entries_by_dir_specific_file_ids(self):
3009
tree = self.make_branch_and_tree('tree')
3010
tree.set_root_id('tree-root-id')
3011
self.build_tree(['tree/parent/', 'tree/parent/child'])
3012
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
3013
tt = TreeTransform(tree)
3014
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
3016
def test_symlink_content_summary(self):
3017
self.requireFeature(SymlinkFeature)
3018
preview = self.get_empty_preview()
3019
preview.new_symlink('path', preview.root, 'target', 'path-id')
3020
summary = preview.get_preview_tree().path_content_summary('path')
3021
self.assertEqual(('symlink', None, None, 'target'), summary)
3023
def test_missing_content_summary(self):
3024
preview = self.get_empty_preview()
3025
summary = preview.get_preview_tree().path_content_summary('path')
3026
self.assertEqual(('missing', None, None, None), summary)
3028
def test_deleted_content_summary(self):
3029
tree = self.make_branch_and_tree('tree')
3030
self.build_tree(['tree/path/'])
3032
preview = TransformPreview(tree)
3033
self.addCleanup(preview.finalize)
3034
preview.delete_contents(preview.trans_id_tree_path('path'))
3035
summary = preview.get_preview_tree().path_content_summary('path')
3036
self.assertEqual(('missing', None, None, None), summary)
3038
def test_file_content_summary_executable(self):
3039
preview = self.get_empty_preview()
3040
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3041
preview.set_executability(True, path_id)
3042
summary = preview.get_preview_tree().path_content_summary('path')
3043
self.assertEqual(4, len(summary))
3044
self.assertEqual('file', summary[0])
3045
# size must be known
3046
self.assertEqual(len('contents'), summary[1])
3048
self.assertEqual(True, summary[2])
3049
# will not have hash (not cheap to determine)
3050
self.assertIs(None, summary[3])
3052
def test_change_executability(self):
3053
tree = self.make_branch_and_tree('tree')
3054
self.build_tree(['tree/path'])
3056
preview = TransformPreview(tree)
3057
self.addCleanup(preview.finalize)
3058
path_id = preview.trans_id_tree_path('path')
3059
preview.set_executability(True, path_id)
3060
summary = preview.get_preview_tree().path_content_summary('path')
3061
self.assertEqual(True, summary[2])
3063
def test_file_content_summary_non_exec(self):
3064
preview = self.get_empty_preview()
3065
preview.new_file('path', preview.root, 'contents', 'path-id')
3066
summary = preview.get_preview_tree().path_content_summary('path')
3067
self.assertEqual(4, len(summary))
3068
self.assertEqual('file', summary[0])
3069
# size must be known
3070
self.assertEqual(len('contents'), summary[1])
3072
self.assertEqual(False, summary[2])
3073
# will not have hash (not cheap to determine)
3074
self.assertIs(None, summary[3])
3076
def test_dir_content_summary(self):
3077
preview = self.get_empty_preview()
3078
preview.new_directory('path', preview.root, 'path-id')
3079
summary = preview.get_preview_tree().path_content_summary('path')
3080
self.assertEqual(('directory', None, None, None), summary)
3082
def test_tree_content_summary(self):
3083
preview = self.get_empty_preview()
3084
path = preview.new_directory('path', preview.root, 'path-id')
3085
preview.set_tree_reference('rev-1', path)
3086
summary = preview.get_preview_tree().path_content_summary('path')
3087
self.assertEqual(4, len(summary))
3088
self.assertEqual('tree-reference', summary[0])
3090
def test_annotate(self):
3091
tree = self.make_branch_and_tree('tree')
3092
self.build_tree_contents([('tree/file', 'a\n')])
3093
tree.add('file', 'file-id')
3094
tree.commit('a', rev_id='one')
3095
self.build_tree_contents([('tree/file', 'a\nb\n')])
3096
preview = TransformPreview(tree)
3097
self.addCleanup(preview.finalize)
3098
file_trans_id = preview.trans_id_file_id('file-id')
3099
preview.delete_contents(file_trans_id)
3100
preview.create_file('a\nb\nc\n', file_trans_id)
3101
preview_tree = preview.get_preview_tree()
3107
annotation = preview_tree.annotate_iter('file-id', 'me:')
3108
self.assertEqual(expected, annotation)
3110
def test_annotate_missing(self):
3111
preview = self.get_empty_preview()
3112
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3113
preview_tree = preview.get_preview_tree()
3119
annotation = preview_tree.annotate_iter('file-id', 'me:')
3120
self.assertEqual(expected, annotation)
3122
def test_annotate_rename(self):
3123
tree = self.make_branch_and_tree('tree')
3124
self.build_tree_contents([('tree/file', 'a\n')])
3125
tree.add('file', 'file-id')
3126
tree.commit('a', rev_id='one')
3127
preview = TransformPreview(tree)
3128
self.addCleanup(preview.finalize)
3129
file_trans_id = preview.trans_id_file_id('file-id')
3130
preview.adjust_path('newname', preview.root, file_trans_id)
3131
preview_tree = preview.get_preview_tree()
3135
annotation = preview_tree.annotate_iter('file-id', 'me:')
3136
self.assertEqual(expected, annotation)
3138
def test_annotate_deleted(self):
3139
tree = self.make_branch_and_tree('tree')
3140
self.build_tree_contents([('tree/file', 'a\n')])
3141
tree.add('file', 'file-id')
3142
tree.commit('a', rev_id='one')
3143
self.build_tree_contents([('tree/file', 'a\nb\n')])
3144
preview = TransformPreview(tree)
3145
self.addCleanup(preview.finalize)
3146
file_trans_id = preview.trans_id_file_id('file-id')
3147
preview.delete_contents(file_trans_id)
3148
preview_tree = preview.get_preview_tree()
3149
annotation = preview_tree.annotate_iter('file-id', 'me:')
3150
self.assertIs(None, annotation)
3152
def test_stored_kind(self):
3153
preview = self.get_empty_preview()
3154
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3155
preview_tree = preview.get_preview_tree()
3156
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3158
def test_is_executable(self):
3159
preview = self.get_empty_preview()
3160
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3161
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3162
preview_tree = preview.get_preview_tree()
3163
self.assertEqual(True, preview_tree.is_executable('file-id'))
3165
def test_get_set_parent_ids(self):
3166
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3167
self.assertEqual([], preview_tree.get_parent_ids())
3168
preview_tree.set_parent_ids(['rev-1'])
3169
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3171
def test_plan_file_merge(self):
3172
work_a = self.make_branch_and_tree('wta')
3173
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3174
work_a.add('file', 'file-id')
3175
base_id = work_a.commit('base version')
3176
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3177
preview = TransformPreview(work_a)
3178
self.addCleanup(preview.finalize)
3179
trans_id = preview.trans_id_file_id('file-id')
3180
preview.delete_contents(trans_id)
3181
preview.create_file('b\nc\nd\ne\n', trans_id)
3182
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3183
tree_a = preview.get_preview_tree()
3184
tree_a.set_parent_ids([base_id])
3186
('killed-a', 'a\n'),
3187
('killed-b', 'b\n'),
3188
('unchanged', 'c\n'),
3189
('unchanged', 'd\n'),
3192
], list(tree_a.plan_file_merge('file-id', tree_b)))
3194
def test_plan_file_merge_revision_tree(self):
3195
work_a = self.make_branch_and_tree('wta')
3196
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3197
work_a.add('file', 'file-id')
3198
base_id = work_a.commit('base version')
3199
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3200
preview = TransformPreview(work_a.basis_tree())
3201
self.addCleanup(preview.finalize)
3202
trans_id = preview.trans_id_file_id('file-id')
3203
preview.delete_contents(trans_id)
3204
preview.create_file('b\nc\nd\ne\n', trans_id)
3205
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3206
tree_a = preview.get_preview_tree()
3207
tree_a.set_parent_ids([base_id])
3209
('killed-a', 'a\n'),
3210
('killed-b', 'b\n'),
3211
('unchanged', 'c\n'),
3212
('unchanged', 'd\n'),
3215
], list(tree_a.plan_file_merge('file-id', tree_b)))
3217
def test_walkdirs(self):
3218
preview = self.get_empty_preview()
3219
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3220
# FIXME: new_directory should mark root.
3221
preview.fixup_new_roots()
3222
preview_tree = preview.get_preview_tree()
3223
file_trans_id = preview.new_file('a', preview.root, 'contents',
3225
expected = [(('', 'tree-root'),
3226
[('a', 'a', 'file', None, 'a-id', 'file')])]
3227
self.assertEqual(expected, list(preview_tree.walkdirs()))
3229
def test_extras(self):
3230
work_tree = self.make_branch_and_tree('tree')
3231
self.build_tree(['tree/removed-file', 'tree/existing-file',
3232
'tree/not-removed-file'])
3233
work_tree.add(['removed-file', 'not-removed-file'])
3234
preview = TransformPreview(work_tree)
3235
self.addCleanup(preview.finalize)
3236
preview.new_file('new-file', preview.root, 'contents')
3237
preview.new_file('new-versioned-file', preview.root, 'contents',
3239
tree = preview.get_preview_tree()
3240
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3241
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3244
def test_merge_into_preview(self):
3245
work_tree = self.make_branch_and_tree('tree')
3246
self.build_tree_contents([('tree/file','b\n')])
3247
work_tree.add('file', 'file-id')
3248
work_tree.commit('first commit')
3249
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3250
self.build_tree_contents([('child/file','b\nc\n')])
3251
child_tree.commit('child commit')
3252
child_tree.lock_write()
3253
self.addCleanup(child_tree.unlock)
3254
work_tree.lock_write()
3255
self.addCleanup(work_tree.unlock)
3256
preview = TransformPreview(work_tree)
3257
self.addCleanup(preview.finalize)
3258
file_trans_id = preview.trans_id_file_id('file-id')
3259
preview.delete_contents(file_trans_id)
3260
preview.create_file('a\nb\n', file_trans_id)
3261
preview_tree = preview.get_preview_tree()
3262
merger = Merger.from_revision_ids(None, preview_tree,
3263
child_tree.branch.last_revision(),
3264
other_branch=child_tree.branch,
3265
tree_branch=work_tree.branch)
3266
merger.merge_type = Merge3Merger
3267
tt = merger.make_merger().make_preview_transform()
3268
self.addCleanup(tt.finalize)
3269
final_tree = tt.get_preview_tree()
3270
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3272
def test_merge_preview_into_workingtree(self):
3273
tree = self.make_branch_and_tree('tree')
3274
tree.set_root_id('TREE_ROOT')
3275
tt = TransformPreview(tree)
3276
self.addCleanup(tt.finalize)
3277
tt.new_file('name', tt.root, 'content', 'file-id')
3278
tree2 = self.make_branch_and_tree('tree2')
3279
tree2.set_root_id('TREE_ROOT')
3280
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3281
None, tree.basis_tree())
3282
merger.merge_type = Merge3Merger
3285
def test_merge_preview_into_workingtree_handles_conflicts(self):
3286
tree = self.make_branch_and_tree('tree')
3287
self.build_tree_contents([('tree/foo', 'bar')])
3288
tree.add('foo', 'foo-id')
3290
tt = TransformPreview(tree)
3291
self.addCleanup(tt.finalize)
3292
trans_id = tt.trans_id_file_id('foo-id')
3293
tt.delete_contents(trans_id)
3294
tt.create_file('baz', trans_id)
3295
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3296
self.build_tree_contents([('tree2/foo', 'qux')])
3298
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3299
pb, tree.basis_tree())
3300
merger.merge_type = Merge3Merger
3303
def test_has_filename(self):
3304
wt = self.make_branch_and_tree('tree')
3305
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3306
tt = TransformPreview(wt)
3307
removed_id = tt.trans_id_tree_path('removed')
3308
tt.delete_contents(removed_id)
3309
tt.new_file('new', tt.root, 'contents')
3310
modified_id = tt.trans_id_tree_path('modified')
3311
tt.delete_contents(modified_id)
3312
tt.create_file('modified-contents', modified_id)
3313
self.addCleanup(tt.finalize)
3314
tree = tt.get_preview_tree()
3315
self.assertTrue(tree.has_filename('unmodified'))
3316
self.assertFalse(tree.has_filename('not-present'))
3317
self.assertFalse(tree.has_filename('removed'))
3318
self.assertTrue(tree.has_filename('new'))
3319
self.assertTrue(tree.has_filename('modified'))
3321
def test_is_executable(self):
3322
tree = self.make_branch_and_tree('tree')
3323
preview = TransformPreview(tree)
3324
self.addCleanup(preview.finalize)
3325
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3326
preview_tree = preview.get_preview_tree()
3327
self.assertEqual(False, preview_tree.is_executable('baz-id',
3329
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3331
def test_commit_preview_tree(self):
3332
tree = self.make_branch_and_tree('tree')
3333
rev_id = tree.commit('rev1')
3334
tree.branch.lock_write()
3335
self.addCleanup(tree.branch.unlock)
3336
tt = TransformPreview(tree)
3337
tt.new_file('file', tt.root, 'contents', 'file_id')
3338
self.addCleanup(tt.finalize)
3339
preview = tt.get_preview_tree()
3340
preview.set_parent_ids([rev_id])
3341
builder = tree.branch.get_commit_builder([rev_id])
3342
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3343
builder.finish_inventory()
3344
rev2_id = builder.commit('rev2')
3345
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3346
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3348
def test_ascii_limbo_paths(self):
3349
self.requireFeature(features.UnicodeFilenameFeature)
3350
branch = self.make_branch('any')
3351
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3352
tt = TransformPreview(tree)
3353
self.addCleanup(tt.finalize)
3354
foo_id = tt.new_directory('', ROOT_PARENT)
3355
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3356
limbo_path = tt._limbo_name(bar_id)
3357
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3360
class FakeSerializer(object):
3361
"""Serializer implementation that simply returns the input.
3363
The input is returned in the order used by pack.ContainerPushParser.
3366
def bytes_record(bytes, names):
3370
class TestSerializeTransform(tests.TestCaseWithTransport):
3372
_test_needs_features = [features.UnicodeFilenameFeature]
3374
def get_preview(self, tree=None):
3376
tree = self.make_branch_and_tree('tree')
3377
tt = TransformPreview(tree)
3378
self.addCleanup(tt.finalize)
3381
def assertSerializesTo(self, expected, tt):
3382
records = list(tt.serialize(FakeSerializer()))
3383
self.assertEqual(expected, records)
3386
def default_attribs():
3391
'_new_executability': {},
3393
'_tree_path_ids': {'': 'new-0'},
3395
'_removed_contents': [],
3396
'_non_present_ids': {},
3399
def make_records(self, attribs, contents):
3401
(((('attribs'),),), bencode.bencode(attribs))]
3402
records.extend([(((n, k),), c) for n, k, c in contents])
3405
def creation_records(self):
3406
attribs = self.default_attribs()
3407
attribs['_id_number'] = 3
3408
attribs['_new_name'] = {
3409
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3410
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3411
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3412
attribs['_new_executability'] = {'new-1': 1}
3414
('new-1', 'file', 'i 1\nbar\n'),
3415
('new-2', 'directory', ''),
3417
return self.make_records(attribs, contents)
3419
def test_serialize_creation(self):
3420
tt = self.get_preview()
3421
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3422
tt.new_directory('qux', tt.root, 'quxx')
3423
self.assertSerializesTo(self.creation_records(), tt)
3425
def test_deserialize_creation(self):
3426
tt = self.get_preview()
3427
tt.deserialize(iter(self.creation_records()))
3428
self.assertEqual(3, tt._id_number)
3429
self.assertEqual({'new-1': u'foo\u1234',
3430
'new-2': 'qux'}, tt._new_name)
3431
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3432
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3433
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3434
self.assertEqual({'new-1': True}, tt._new_executability)
3435
self.assertEqual({'new-1': 'file',
3436
'new-2': 'directory'}, tt._new_contents)
3437
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3439
foo_content = foo_limbo.read()
3442
self.assertEqual('bar', foo_content)
3444
def symlink_creation_records(self):
3445
attribs = self.default_attribs()
3446
attribs['_id_number'] = 2
3447
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3448
attribs['_new_parent'] = {'new-1': 'new-0'}
3449
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3450
return self.make_records(attribs, contents)
3452
def test_serialize_symlink_creation(self):
3453
self.requireFeature(features.SymlinkFeature)
3454
tt = self.get_preview()
3455
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3456
self.assertSerializesTo(self.symlink_creation_records(), tt)
3458
def test_deserialize_symlink_creation(self):
3459
self.requireFeature(features.SymlinkFeature)
3460
tt = self.get_preview()
3461
tt.deserialize(iter(self.symlink_creation_records()))
3462
abspath = tt._limbo_name('new-1')
3463
foo_content = osutils.readlink(abspath)
3464
self.assertEqual(u'bar\u1234', foo_content)
3466
def make_destruction_preview(self):
3467
tree = self.make_branch_and_tree('.')
3468
self.build_tree([u'foo\u1234', 'bar'])
3469
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3470
return self.get_preview(tree)
3472
def destruction_records(self):
3473
attribs = self.default_attribs()
3474
attribs['_id_number'] = 3
3475
attribs['_removed_id'] = ['new-1']
3476
attribs['_removed_contents'] = ['new-2']
3477
attribs['_tree_path_ids'] = {
3479
u'foo\u1234'.encode('utf-8'): 'new-1',
3482
return self.make_records(attribs, [])
3484
def test_serialize_destruction(self):
3485
tt = self.make_destruction_preview()
3486
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3487
tt.unversion_file(foo_trans_id)
3488
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3489
tt.delete_contents(bar_trans_id)
3490
self.assertSerializesTo(self.destruction_records(), tt)
3492
def test_deserialize_destruction(self):
3493
tt = self.make_destruction_preview()
3494
tt.deserialize(iter(self.destruction_records()))
3495
self.assertEqual({u'foo\u1234': 'new-1',
3497
'': tt.root}, tt._tree_path_ids)
3498
self.assertEqual({'new-1': u'foo\u1234',
3500
tt.root: ''}, tt._tree_id_paths)
3501
self.assertEqual(set(['new-1']), tt._removed_id)
3502
self.assertEqual(set(['new-2']), tt._removed_contents)
3504
def missing_records(self):
3505
attribs = self.default_attribs()
3506
attribs['_id_number'] = 2
3507
attribs['_non_present_ids'] = {
3509
return self.make_records(attribs, [])
3511
def test_serialize_missing(self):
3512
tt = self.get_preview()
3513
boo_trans_id = tt.trans_id_file_id('boo')
3514
self.assertSerializesTo(self.missing_records(), tt)
3516
def test_deserialize_missing(self):
3517
tt = self.get_preview()
3518
tt.deserialize(iter(self.missing_records()))
3519
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3521
def make_modification_preview(self):
3522
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3523
LINES_TWO = 'z\nbb\nx\ndd\n'
3524
tree = self.make_branch_and_tree('tree')
3525
self.build_tree_contents([('tree/file', LINES_ONE)])
3526
tree.add('file', 'file-id')
3527
return self.get_preview(tree), LINES_TWO
3529
def modification_records(self):
3530
attribs = self.default_attribs()
3531
attribs['_id_number'] = 2
3532
attribs['_tree_path_ids'] = {
3535
attribs['_removed_contents'] = ['new-1']
3536
contents = [('new-1', 'file',
3537
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3538
return self.make_records(attribs, contents)
3540
def test_serialize_modification(self):
3541
tt, LINES = self.make_modification_preview()
3542
trans_id = tt.trans_id_file_id('file-id')
3543
tt.delete_contents(trans_id)
3544
tt.create_file(LINES, trans_id)
3545
self.assertSerializesTo(self.modification_records(), tt)
3547
def test_deserialize_modification(self):
3548
tt, LINES = self.make_modification_preview()
3549
tt.deserialize(iter(self.modification_records()))
3550
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3552
def make_kind_change_preview(self):
3553
LINES = 'a\nb\nc\nd\n'
3554
tree = self.make_branch_and_tree('tree')
3555
self.build_tree(['tree/foo/'])
3556
tree.add('foo', 'foo-id')
3557
return self.get_preview(tree), LINES
3559
def kind_change_records(self):
3560
attribs = self.default_attribs()
3561
attribs['_id_number'] = 2
3562
attribs['_tree_path_ids'] = {
3565
attribs['_removed_contents'] = ['new-1']
3566
contents = [('new-1', 'file',
3567
'i 4\na\nb\nc\nd\n\n')]
3568
return self.make_records(attribs, contents)
3570
def test_serialize_kind_change(self):
3571
tt, LINES = self.make_kind_change_preview()
3572
trans_id = tt.trans_id_file_id('foo-id')
3573
tt.delete_contents(trans_id)
3574
tt.create_file(LINES, trans_id)
3575
self.assertSerializesTo(self.kind_change_records(), tt)
3577
def test_deserialize_kind_change(self):
3578
tt, LINES = self.make_kind_change_preview()
3579
tt.deserialize(iter(self.kind_change_records()))
3580
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3582
def make_add_contents_preview(self):
3583
LINES = 'a\nb\nc\nd\n'
3584
tree = self.make_branch_and_tree('tree')
3585
self.build_tree(['tree/foo'])
3587
os.unlink('tree/foo')
3588
return self.get_preview(tree), LINES
3590
def add_contents_records(self):
3591
attribs = self.default_attribs()
3592
attribs['_id_number'] = 2
3593
attribs['_tree_path_ids'] = {
3596
contents = [('new-1', 'file',
3597
'i 4\na\nb\nc\nd\n\n')]
3598
return self.make_records(attribs, contents)
3600
def test_serialize_add_contents(self):
3601
tt, LINES = self.make_add_contents_preview()
3602
trans_id = tt.trans_id_tree_path('foo')
3603
tt.create_file(LINES, trans_id)
3604
self.assertSerializesTo(self.add_contents_records(), tt)
3606
def test_deserialize_add_contents(self):
3607
tt, LINES = self.make_add_contents_preview()
3608
tt.deserialize(iter(self.add_contents_records()))
3609
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3611
def test_get_parents_lines(self):
3612
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3613
LINES_TWO = 'z\nbb\nx\ndd\n'
3614
tree = self.make_branch_and_tree('tree')
3615
self.build_tree_contents([('tree/file', LINES_ONE)])
3616
tree.add('file', 'file-id')
3617
tt = self.get_preview(tree)
3618
trans_id = tt.trans_id_tree_path('file')
3619
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3620
tt._get_parents_lines(trans_id))
3622
def test_get_parents_texts(self):
3623
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3624
LINES_TWO = 'z\nbb\nx\ndd\n'
3625
tree = self.make_branch_and_tree('tree')
3626
self.build_tree_contents([('tree/file', LINES_ONE)])
3627
tree.add('file', 'file-id')
3628
tt = self.get_preview(tree)
3629
trans_id = tt.trans_id_tree_path('file')
3630
self.assertEqual((LINES_ONE,),
3631
tt._get_parents_texts(trans_id))
3634
class TestOrphan(tests.TestCaseWithTransport):
3636
def test_no_orphan_for_transform_preview(self):
3637
tree = self.make_branch_and_tree('tree')
3638
tt = transform.TransformPreview(tree)
3639
self.addCleanup(tt.finalize)
3640
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3642
def _set_orphan_policy(self, wt, policy):
3643
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3646
def _prepare_orphan(self, wt):
3647
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3648
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3649
wt.commit('add dir and file ignoring foo')
3650
tt = transform.TreeTransform(wt)
3651
self.addCleanup(tt.finalize)
3652
# dir and bar are deleted
3653
dir_tid = tt.trans_id_tree_path('dir')
3654
file_tid = tt.trans_id_tree_path('dir/file')
3655
orphan_tid = tt.trans_id_tree_path('dir/foo')
3656
tt.delete_contents(file_tid)
3657
tt.unversion_file(file_tid)
3658
tt.delete_contents(dir_tid)
3659
tt.unversion_file(dir_tid)
3660
# There should be a conflict because dir still contain foo
3661
raw_conflicts = tt.find_conflicts()
3662
self.assertLength(1, raw_conflicts)
3663
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3664
return tt, orphan_tid
3666
def test_new_orphan_created(self):
3667
wt = self.make_branch_and_tree('.')
3668
self._set_orphan_policy(wt, 'move')
3669
tt, orphan_tid = self._prepare_orphan(wt)
3672
warnings.append(args[0] % args[1:])
3673
self.overrideAttr(trace, 'warning', warning)
3674
remaining_conflicts = resolve_conflicts(tt)
3675
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3677
# Yeah for resolved conflicts !
3678
self.assertLength(0, remaining_conflicts)
3679
# We have a new orphan
3680
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3681
self.assertEquals('bzr-orphans',
3682
tt.final_name(tt.final_parent(orphan_tid)))
3684
def test_never_orphan(self):
3685
wt = self.make_branch_and_tree('.')
3686
self._set_orphan_policy(wt, 'conflict')
3687
tt, orphan_tid = self._prepare_orphan(wt)
3688
remaining_conflicts = resolve_conflicts(tt)
3689
self.assertLength(1, remaining_conflicts)
3690
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3691
remaining_conflicts.pop())
3693
def test_orphan_error(self):
3694
def bogus_orphan(tt, orphan_id, parent_id):
3695
raise transform.OrphaningError(tt.final_name(orphan_id),
3696
tt.final_name(parent_id))
3697
transform.orphaning_registry.register('bogus', bogus_orphan,
3698
'Raise an error when orphaning')
3699
wt = self.make_branch_and_tree('.')
3700
self._set_orphan_policy(wt, 'bogus')
3701
tt, orphan_tid = self._prepare_orphan(wt)
3702
remaining_conflicts = resolve_conflicts(tt)
3703
self.assertLength(1, remaining_conflicts)
3704
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3705
remaining_conflicts.pop())
3707
def test_unknown_orphan_policy(self):
3708
wt = self.make_branch_and_tree('.')
3709
# Set a fictional policy nobody ever implemented
3710
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3711
tt, orphan_tid = self._prepare_orphan(wt)
3714
warnings.append(args[0] % args[1:])
3715
self.overrideAttr(trace, 'warning', warning)
3716
remaining_conflicts = resolve_conflicts(tt)
3717
# We fallback to the default policy which create a conflict
3718
self.assertLength(1, remaining_conflicts)
3719
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3720
remaining_conflicts.pop())
3721
self.assertLength(1, warnings)
3722
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')