1
# Copyright (C) 2006-2010 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
18
from StringIO import StringIO
28
revision as _mod_revision,
33
from bzrlib.bzrdir import BzrDir
34
from bzrlib.conflicts import (
43
from bzrlib.diff import show_diff_trees
44
from bzrlib.errors import (
47
ExistingPendingDeletion,
49
ImmortalPendingDeletion,
55
from bzrlib.osutils import (
59
from bzrlib.merge import Merge3Merger, Merger
60
from bzrlib.tests import (
68
from bzrlib.transform import (
83
class TestTreeTransform(tests.TestCaseWithTransport):
86
super(TestTreeTransform, self).setUp()
87
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
90
def get_transform(self):
91
transform = TreeTransform(self.wt)
92
self.addCleanup(transform.finalize)
93
return transform, transform.root
95
def test_existing_limbo(self):
96
transform, root = self.get_transform()
97
limbo_name = transform._limbodir
98
deletion_path = transform._deletiondir
99
os.mkdir(pathjoin(limbo_name, 'hehe'))
100
self.assertRaises(ImmortalLimbo, transform.apply)
101
self.assertRaises(LockError, self.wt.unlock)
102
self.assertRaises(ExistingLimbo, self.get_transform)
103
self.assertRaises(LockError, self.wt.unlock)
104
os.rmdir(pathjoin(limbo_name, 'hehe'))
106
os.rmdir(deletion_path)
107
transform, root = self.get_transform()
110
def test_existing_pending_deletion(self):
111
transform, root = self.get_transform()
112
deletion_path = self._limbodir = urlutils.local_path_from_url(
113
transform._tree._transport.abspath('pending-deletion'))
114
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
115
self.assertRaises(ImmortalPendingDeletion, transform.apply)
116
self.assertRaises(LockError, self.wt.unlock)
117
self.assertRaises(ExistingPendingDeletion, self.get_transform)
119
def test_build(self):
120
transform, root = self.get_transform()
121
self.wt.lock_tree_write()
122
self.addCleanup(self.wt.unlock)
123
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
124
imaginary_id = transform.trans_id_tree_path('imaginary')
125
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
126
self.assertEqual(imaginary_id, imaginary_id2)
127
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
128
self.assertEqual('directory', transform.final_kind(root))
129
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
130
trans_id = transform.create_path('name', root)
131
self.assertIs(transform.final_file_id(trans_id), None)
132
self.assertIs(None, transform.final_kind(trans_id))
133
transform.create_file('contents', trans_id)
134
transform.set_executability(True, trans_id)
135
transform.version_file('my_pretties', trans_id)
136
self.assertRaises(DuplicateKey, transform.version_file,
137
'my_pretties', trans_id)
138
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
139
self.assertEqual(transform.final_parent(trans_id), root)
140
self.assertIs(transform.final_parent(root), ROOT_PARENT)
141
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
142
oz_id = transform.create_path('oz', root)
143
transform.create_directory(oz_id)
144
transform.version_file('ozzie', oz_id)
145
trans_id2 = transform.create_path('name2', root)
146
transform.create_file('contents', trans_id2)
147
transform.set_executability(False, trans_id2)
148
transform.version_file('my_pretties2', trans_id2)
149
modified_paths = transform.apply().modified_paths
150
self.assertEqual('contents', self.wt.get_file_byname('name').read())
151
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
152
self.assertIs(self.wt.is_executable('my_pretties'), True)
153
self.assertIs(self.wt.is_executable('my_pretties2'), False)
154
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
155
self.assertEqual(len(modified_paths), 3)
156
tree_mod_paths = [self.wt.id2abspath(f) for f in
157
('ozzie', 'my_pretties', 'my_pretties2')]
158
self.assertSubset(tree_mod_paths, modified_paths)
159
# is it safe to finalize repeatedly?
163
def test_create_files_same_timestamp(self):
164
transform, root = self.get_transform()
165
self.wt.lock_tree_write()
166
self.addCleanup(self.wt.unlock)
167
# Roll back the clock, so that we know everything is being set to the
169
transform._creation_mtime = creation_mtime = time.time() - 20.0
170
transform.create_file('content-one',
171
transform.create_path('one', root))
172
time.sleep(1) # *ugly*
173
transform.create_file('content-two',
174
transform.create_path('two', root))
176
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
178
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
180
# We only guarantee 2s resolution
181
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
182
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
183
# But if we have more than that, all files should get the same result
184
self.assertEqual(st1.st_mtime, st2.st_mtime)
186
def test_change_root_id(self):
187
transform, root = self.get_transform()
188
self.assertNotEqual('new-root-id', self.wt.get_root_id())
189
transform.new_directory('', ROOT_PARENT, 'new-root-id')
190
transform.delete_contents(root)
191
transform.unversion_file(root)
192
transform.fixup_new_roots()
194
self.assertEqual('new-root-id', self.wt.get_root_id())
196
def test_change_root_id_add_files(self):
197
transform, root = self.get_transform()
198
self.assertNotEqual('new-root-id', self.wt.get_root_id())
199
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
200
transform.new_file('file', new_trans_id, ['new-contents\n'],
202
transform.delete_contents(root)
203
transform.unversion_file(root)
204
transform.fixup_new_roots()
206
self.assertEqual('new-root-id', self.wt.get_root_id())
207
self.assertEqual('new-file-id', self.wt.path2id('file'))
208
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
210
def test_add_two_roots(self):
211
transform, root = self.get_transform()
212
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
213
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
214
self.assertRaises(ValueError, transform.fixup_new_roots)
216
def test_hardlink(self):
217
self.requireFeature(HardlinkFeature)
218
transform, root = self.get_transform()
219
transform.new_file('file1', root, 'contents')
221
target = self.make_branch_and_tree('target')
222
target_transform = TreeTransform(target)
223
trans_id = target_transform.create_path('file1', target_transform.root)
224
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
225
target_transform.apply()
226
self.failUnlessExists('target/file1')
227
source_stat = os.stat(self.wt.abspath('file1'))
228
target_stat = os.stat('target/file1')
229
self.assertEqual(source_stat, target_stat)
231
def test_convenience(self):
232
transform, root = self.get_transform()
233
self.wt.lock_tree_write()
234
self.addCleanup(self.wt.unlock)
235
trans_id = transform.new_file('name', root, 'contents',
237
oz = transform.new_directory('oz', root, 'oz-id')
238
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
239
toto = transform.new_file('toto', dorothy, 'toto-contents',
242
self.assertEqual(len(transform.find_conflicts()), 0)
244
self.assertRaises(ReusingTransform, transform.find_conflicts)
245
self.assertEqual('contents', file(self.wt.abspath('name')).read())
246
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
247
self.assertIs(self.wt.is_executable('my_pretties'), True)
248
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
249
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
250
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
252
self.assertEqual('toto-contents',
253
self.wt.get_file_byname('oz/dorothy/toto').read())
254
self.assertIs(self.wt.is_executable('toto-id'), False)
256
def test_tree_reference(self):
257
transform, root = self.get_transform()
258
tree = transform._tree
259
trans_id = transform.new_directory('reference', root, 'subtree-id')
260
transform.set_tree_reference('subtree-revision', trans_id)
263
self.addCleanup(tree.unlock)
264
self.assertEqual('subtree-revision',
265
tree.inventory['subtree-id'].reference_revision)
267
def test_conflicts(self):
268
transform, root = self.get_transform()
269
trans_id = transform.new_file('name', root, 'contents',
271
self.assertEqual(len(transform.find_conflicts()), 0)
272
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
273
self.assertEqual(transform.find_conflicts(),
274
[('duplicate', trans_id, trans_id2, 'name')])
275
self.assertRaises(MalformedTransform, transform.apply)
276
transform.adjust_path('name', trans_id, trans_id2)
277
self.assertEqual(transform.find_conflicts(),
278
[('non-directory parent', trans_id)])
279
tinman_id = transform.trans_id_tree_path('tinman')
280
transform.adjust_path('name', tinman_id, trans_id2)
281
self.assertEqual(transform.find_conflicts(),
282
[('unversioned parent', tinman_id),
283
('missing parent', tinman_id)])
284
lion_id = transform.create_path('lion', root)
285
self.assertEqual(transform.find_conflicts(),
286
[('unversioned parent', tinman_id),
287
('missing parent', tinman_id)])
288
transform.adjust_path('name', lion_id, trans_id2)
289
self.assertEqual(transform.find_conflicts(),
290
[('unversioned parent', lion_id),
291
('missing parent', lion_id)])
292
transform.version_file("Courage", lion_id)
293
self.assertEqual(transform.find_conflicts(),
294
[('missing parent', lion_id),
295
('versioning no contents', lion_id)])
296
transform.adjust_path('name2', root, trans_id2)
297
self.assertEqual(transform.find_conflicts(),
298
[('versioning no contents', lion_id)])
299
transform.create_file('Contents, okay?', lion_id)
300
transform.adjust_path('name2', trans_id2, trans_id2)
301
self.assertEqual(transform.find_conflicts(),
302
[('parent loop', trans_id2),
303
('non-directory parent', trans_id2)])
304
transform.adjust_path('name2', root, trans_id2)
305
oz_id = transform.new_directory('oz', root)
306
transform.set_executability(True, oz_id)
307
self.assertEqual(transform.find_conflicts(),
308
[('unversioned executability', oz_id)])
309
transform.version_file('oz-id', oz_id)
310
self.assertEqual(transform.find_conflicts(),
311
[('non-file executability', oz_id)])
312
transform.set_executability(None, oz_id)
313
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
315
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
316
self.assertEqual('contents', file(self.wt.abspath('name')).read())
317
transform2, root = self.get_transform()
318
oz_id = transform2.trans_id_tree_file_id('oz-id')
319
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
320
result = transform2.find_conflicts()
321
fp = FinalPaths(transform2)
322
self.assert_('oz/tip' in transform2._tree_path_ids)
323
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
324
self.assertEqual(len(result), 2)
325
self.assertEqual((result[0][0], result[0][1]),
326
('duplicate', newtip))
327
self.assertEqual((result[1][0], result[1][2]),
328
('duplicate id', newtip))
329
transform2.finalize()
330
transform3 = TreeTransform(self.wt)
331
self.addCleanup(transform3.finalize)
332
oz_id = transform3.trans_id_tree_file_id('oz-id')
333
transform3.delete_contents(oz_id)
334
self.assertEqual(transform3.find_conflicts(),
335
[('missing parent', oz_id)])
336
root_id = transform3.root
337
tip_id = transform3.trans_id_tree_file_id('tip-id')
338
transform3.adjust_path('tip', root_id, tip_id)
341
def test_conflict_on_case_insensitive(self):
342
tree = self.make_branch_and_tree('tree')
343
# Don't try this at home, kids!
344
# Force the tree to report that it is case sensitive, for conflict
346
tree.case_sensitive = True
347
transform = TreeTransform(tree)
348
self.addCleanup(transform.finalize)
349
transform.new_file('file', transform.root, 'content')
350
transform.new_file('FiLe', transform.root, 'content')
351
result = transform.find_conflicts()
352
self.assertEqual([], result)
354
# Force the tree to report that it is case insensitive, for conflict
356
tree.case_sensitive = False
357
transform = TreeTransform(tree)
358
self.addCleanup(transform.finalize)
359
transform.new_file('file', transform.root, 'content')
360
transform.new_file('FiLe', transform.root, 'content')
361
result = transform.find_conflicts()
362
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
364
def test_conflict_on_case_insensitive_existing(self):
365
tree = self.make_branch_and_tree('tree')
366
self.build_tree(['tree/FiLe'])
367
# Don't try this at home, kids!
368
# Force the tree to report that it is case sensitive, for conflict
370
tree.case_sensitive = True
371
transform = TreeTransform(tree)
372
self.addCleanup(transform.finalize)
373
transform.new_file('file', transform.root, 'content')
374
result = transform.find_conflicts()
375
self.assertEqual([], result)
377
# Force the tree to report that it is case insensitive, for conflict
379
tree.case_sensitive = False
380
transform = TreeTransform(tree)
381
self.addCleanup(transform.finalize)
382
transform.new_file('file', transform.root, 'content')
383
result = transform.find_conflicts()
384
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
386
def test_resolve_case_insensitive_conflict(self):
387
tree = self.make_branch_and_tree('tree')
388
# Don't try this at home, kids!
389
# Force the tree to report that it is case insensitive, for conflict
391
tree.case_sensitive = False
392
transform = TreeTransform(tree)
393
self.addCleanup(transform.finalize)
394
transform.new_file('file', transform.root, 'content')
395
transform.new_file('FiLe', transform.root, 'content')
396
resolve_conflicts(transform)
398
self.failUnlessExists('tree/file')
399
self.failUnlessExists('tree/FiLe.moved')
401
def test_resolve_checkout_case_conflict(self):
402
tree = self.make_branch_and_tree('tree')
403
# Don't try this at home, kids!
404
# Force the tree to report that it is case insensitive, for conflict
406
tree.case_sensitive = False
407
transform = TreeTransform(tree)
408
self.addCleanup(transform.finalize)
409
transform.new_file('file', transform.root, 'content')
410
transform.new_file('FiLe', transform.root, 'content')
411
resolve_conflicts(transform,
412
pass_func=lambda t, c: resolve_checkout(t, c, []))
414
self.failUnlessExists('tree/file')
415
self.failUnlessExists('tree/FiLe.moved')
417
def test_apply_case_conflict(self):
418
"""Ensure that a transform with case conflicts can always be applied"""
419
tree = self.make_branch_and_tree('tree')
420
transform = TreeTransform(tree)
421
self.addCleanup(transform.finalize)
422
transform.new_file('file', transform.root, 'content')
423
transform.new_file('FiLe', transform.root, 'content')
424
dir = transform.new_directory('dir', transform.root)
425
transform.new_file('dirfile', dir, 'content')
426
transform.new_file('dirFiLe', dir, 'content')
427
resolve_conflicts(transform)
429
self.failUnlessExists('tree/file')
430
if not os.path.exists('tree/FiLe.moved'):
431
self.failUnlessExists('tree/FiLe')
432
self.failUnlessExists('tree/dir/dirfile')
433
if not os.path.exists('tree/dir/dirFiLe.moved'):
434
self.failUnlessExists('tree/dir/dirFiLe')
436
def test_case_insensitive_limbo(self):
437
tree = self.make_branch_and_tree('tree')
438
# Don't try this at home, kids!
439
# Force the tree to report that it is case insensitive
440
tree.case_sensitive = False
441
transform = TreeTransform(tree)
442
self.addCleanup(transform.finalize)
443
dir = transform.new_directory('dir', transform.root)
444
first = transform.new_file('file', dir, 'content')
445
second = transform.new_file('FiLe', dir, 'content')
446
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
447
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
449
def test_adjust_path_updates_child_limbo_names(self):
450
tree = self.make_branch_and_tree('tree')
451
transform = TreeTransform(tree)
452
self.addCleanup(transform.finalize)
453
foo_id = transform.new_directory('foo', transform.root)
454
bar_id = transform.new_directory('bar', foo_id)
455
baz_id = transform.new_directory('baz', bar_id)
456
qux_id = transform.new_directory('qux', baz_id)
457
transform.adjust_path('quxx', foo_id, bar_id)
458
self.assertStartsWith(transform._limbo_name(qux_id),
459
transform._limbo_name(bar_id))
461
def test_add_del(self):
462
start, root = self.get_transform()
463
start.new_directory('a', root, 'a')
465
transform, root = self.get_transform()
466
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
467
transform.new_directory('a', root, 'a')
470
def test_unversioning(self):
471
create_tree, root = self.get_transform()
472
parent_id = create_tree.new_directory('parent', root, 'parent-id')
473
create_tree.new_file('child', parent_id, 'child', 'child-id')
475
unversion = TreeTransform(self.wt)
476
self.addCleanup(unversion.finalize)
477
parent = unversion.trans_id_tree_path('parent')
478
unversion.unversion_file(parent)
479
self.assertEqual(unversion.find_conflicts(),
480
[('unversioned parent', parent_id)])
481
file_id = unversion.trans_id_tree_file_id('child-id')
482
unversion.unversion_file(file_id)
485
def test_name_invariants(self):
486
create_tree, root = self.get_transform()
488
root = create_tree.root
489
create_tree.new_file('name1', root, 'hello1', 'name1')
490
create_tree.new_file('name2', root, 'hello2', 'name2')
491
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
492
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
493
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
494
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
497
mangle_tree,root = self.get_transform()
498
root = mangle_tree.root
500
name1 = mangle_tree.trans_id_tree_file_id('name1')
501
name2 = mangle_tree.trans_id_tree_file_id('name2')
502
mangle_tree.adjust_path('name2', root, name1)
503
mangle_tree.adjust_path('name1', root, name2)
505
#tests for deleting parent directories
506
ddir = mangle_tree.trans_id_tree_file_id('ddir')
507
mangle_tree.delete_contents(ddir)
508
dfile = mangle_tree.trans_id_tree_file_id('dfile')
509
mangle_tree.delete_versioned(dfile)
510
mangle_tree.unversion_file(dfile)
511
mfile = mangle_tree.trans_id_tree_file_id('mfile')
512
mangle_tree.adjust_path('mfile', root, mfile)
514
#tests for adding parent directories
515
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
516
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
517
mangle_tree.adjust_path('mfile2', newdir, mfile2)
518
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
519
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
520
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
521
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
523
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
524
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
525
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
526
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
527
self.assertEqual(file(mfile2_path).read(), 'later2')
528
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
529
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
530
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
531
self.assertEqual(file(newfile_path).read(), 'hello3')
532
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
533
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
534
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
536
def test_both_rename(self):
537
create_tree,root = self.get_transform()
538
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
539
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
541
mangle_tree,root = self.get_transform()
542
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
543
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
544
mangle_tree.adjust_path('test', root, selftest)
545
mangle_tree.adjust_path('test_too_much', root, selftest)
546
mangle_tree.set_executability(True, blackbox)
549
def test_both_rename2(self):
550
create_tree,root = self.get_transform()
551
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
552
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
553
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
554
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
557
mangle_tree,root = self.get_transform()
558
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
559
tests = mangle_tree.trans_id_tree_file_id('tests-id')
560
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
561
mangle_tree.adjust_path('selftest', bzrlib, tests)
562
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
563
mangle_tree.set_executability(True, test_too_much)
566
def test_both_rename3(self):
567
create_tree,root = self.get_transform()
568
tests = create_tree.new_directory('tests', root, 'tests-id')
569
create_tree.new_file('test_too_much.py', tests, 'hello1',
572
mangle_tree,root = self.get_transform()
573
tests = mangle_tree.trans_id_tree_file_id('tests-id')
574
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
575
mangle_tree.adjust_path('selftest', root, tests)
576
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
577
mangle_tree.set_executability(True, test_too_much)
580
def test_move_dangling_ie(self):
581
create_tree, root = self.get_transform()
583
root = create_tree.root
584
create_tree.new_file('name1', root, 'hello1', 'name1')
586
delete_contents, root = self.get_transform()
587
file = delete_contents.trans_id_tree_file_id('name1')
588
delete_contents.delete_contents(file)
589
delete_contents.apply()
590
move_id, root = self.get_transform()
591
name1 = move_id.trans_id_tree_file_id('name1')
592
newdir = move_id.new_directory('dir', root, 'newdir')
593
move_id.adjust_path('name2', newdir, name1)
596
def test_replace_dangling_ie(self):
597
create_tree, root = self.get_transform()
599
root = create_tree.root
600
create_tree.new_file('name1', root, 'hello1', 'name1')
602
delete_contents = TreeTransform(self.wt)
603
self.addCleanup(delete_contents.finalize)
604
file = delete_contents.trans_id_tree_file_id('name1')
605
delete_contents.delete_contents(file)
606
delete_contents.apply()
607
delete_contents.finalize()
608
replace = TreeTransform(self.wt)
609
self.addCleanup(replace.finalize)
610
name2 = replace.new_file('name2', root, 'hello2', 'name1')
611
conflicts = replace.find_conflicts()
612
name1 = replace.trans_id_tree_file_id('name1')
613
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
614
resolve_conflicts(replace)
617
def _test_symlinks(self, link_name1,link_target1,
618
link_name2, link_target2):
620
def ozpath(p): return 'oz/' + p
622
self.requireFeature(SymlinkFeature)
623
transform, root = self.get_transform()
624
oz_id = transform.new_directory('oz', root, 'oz-id')
625
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
627
wiz_id = transform.create_path(link_name2, oz_id)
628
transform.create_symlink(link_target2, wiz_id)
629
transform.version_file('wiz-id2', wiz_id)
630
transform.set_executability(True, wiz_id)
631
self.assertEqual(transform.find_conflicts(),
632
[('non-file executability', wiz_id)])
633
transform.set_executability(None, wiz_id)
635
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
636
self.assertEqual('symlink',
637
file_kind(self.wt.abspath(ozpath(link_name1))))
638
self.assertEqual(link_target2,
639
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
640
self.assertEqual(link_target1,
641
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
643
def test_symlinks(self):
644
self._test_symlinks('wizard', 'wizard-target',
645
'wizard2', 'behind_curtain')
647
def test_symlinks_unicode(self):
648
self.requireFeature(tests.UnicodeFilenameFeature)
649
self._test_symlinks(u'\N{Euro Sign}wizard',
650
u'wizard-targ\N{Euro Sign}t',
651
u'\N{Euro Sign}wizard2',
652
u'b\N{Euro Sign}hind_curtain')
654
def test_unable_create_symlink(self):
656
wt = self.make_branch_and_tree('.')
657
tt = TreeTransform(wt) # TreeTransform obtains write lock
659
tt.new_symlink('foo', tt.root, 'bar')
663
os_symlink = getattr(os, 'symlink', None)
666
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
668
"Unable to create symlink 'foo' on this platform",
672
os.symlink = os_symlink
674
def get_conflicted(self):
675
create,root = self.get_transform()
676
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
677
oz = create.new_directory('oz', root, 'oz-id')
678
create.new_directory('emeraldcity', oz, 'emerald-id')
680
conflicts,root = self.get_transform()
681
# set up duplicate entry, duplicate id
682
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
684
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
685
oz = conflicts.trans_id_tree_file_id('oz-id')
686
# set up DeletedParent parent conflict
687
conflicts.delete_versioned(oz)
688
emerald = conflicts.trans_id_tree_file_id('emerald-id')
689
# set up MissingParent conflict
690
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
691
conflicts.adjust_path('munchkincity', root, munchkincity)
692
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
694
conflicts.adjust_path('emeraldcity', emerald, emerald)
695
return conflicts, emerald, oz, old_dorothy, new_dorothy
697
def test_conflict_resolution(self):
698
conflicts, emerald, oz, old_dorothy, new_dorothy =\
699
self.get_conflicted()
700
resolve_conflicts(conflicts)
701
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
702
self.assertIs(conflicts.final_file_id(old_dorothy), None)
703
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
704
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
705
self.assertEqual(conflicts.final_parent(emerald), oz)
708
def test_cook_conflicts(self):
709
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
710
raw_conflicts = resolve_conflicts(tt)
711
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
712
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
713
'dorothy', None, 'dorothy-id')
714
self.assertEqual(cooked_conflicts[0], duplicate)
715
duplicate_id = DuplicateID('Unversioned existing file',
716
'dorothy.moved', 'dorothy', None,
718
self.assertEqual(cooked_conflicts[1], duplicate_id)
719
missing_parent = MissingParent('Created directory', 'munchkincity',
721
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
722
self.assertEqual(cooked_conflicts[2], missing_parent)
723
unversioned_parent = UnversionedParent('Versioned directory',
726
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
728
self.assertEqual(cooked_conflicts[3], unversioned_parent)
729
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
730
'oz/emeraldcity', 'emerald-id', 'emerald-id')
731
self.assertEqual(cooked_conflicts[4], deleted_parent)
732
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
733
self.assertEqual(cooked_conflicts[6], parent_loop)
734
self.assertEqual(len(cooked_conflicts), 7)
737
def test_string_conflicts(self):
738
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
739
raw_conflicts = resolve_conflicts(tt)
740
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
742
conflicts_s = [str(c) for c in cooked_conflicts]
743
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
744
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
745
'Moved existing file to '
747
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
748
'Unversioned existing file '
750
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
751
' munchkincity. Created directory.')
752
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
753
' versioned, but has versioned'
754
' children. Versioned directory.')
755
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
756
" is not empty. Not deleting.")
757
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
758
' versioned, but has versioned'
759
' children. Versioned directory.')
760
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
761
' oz/emeraldcity. Cancelled move.')
763
def prepare_wrong_parent_kind(self):
764
tt, root = self.get_transform()
765
tt.new_file('parent', root, 'contents', 'parent-id')
767
tt, root = self.get_transform()
768
parent_id = tt.trans_id_file_id('parent-id')
769
tt.new_file('child,', parent_id, 'contents2', 'file-id')
772
def test_find_conflicts_wrong_parent_kind(self):
773
tt = self.prepare_wrong_parent_kind()
776
def test_resolve_conflicts_wrong_existing_parent_kind(self):
777
tt = self.prepare_wrong_parent_kind()
778
raw_conflicts = resolve_conflicts(tt)
779
self.assertEqual(set([('non-directory parent', 'Created directory',
780
'new-3')]), raw_conflicts)
781
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
782
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
783
'parent-id')], cooked_conflicts)
785
self.assertEqual(None, self.wt.path2id('parent'))
786
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
788
def test_resolve_conflicts_wrong_new_parent_kind(self):
789
tt, root = self.get_transform()
790
parent_id = tt.new_directory('parent', root, 'parent-id')
791
tt.new_file('child,', parent_id, 'contents2', 'file-id')
793
tt, root = self.get_transform()
794
parent_id = tt.trans_id_file_id('parent-id')
795
tt.delete_contents(parent_id)
796
tt.create_file('contents', parent_id)
797
raw_conflicts = resolve_conflicts(tt)
798
self.assertEqual(set([('non-directory parent', 'Created directory',
799
'new-3')]), raw_conflicts)
801
self.assertEqual(None, self.wt.path2id('parent'))
802
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
804
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
805
tt, root = self.get_transform()
806
parent_id = tt.new_directory('parent', root)
807
tt.new_file('child,', parent_id, 'contents2')
809
tt, root = self.get_transform()
810
parent_id = tt.trans_id_tree_path('parent')
811
tt.delete_contents(parent_id)
812
tt.create_file('contents', parent_id)
813
resolve_conflicts(tt)
815
self.assertIs(None, self.wt.path2id('parent'))
816
self.assertIs(None, self.wt.path2id('parent.new'))
818
def test_moving_versioned_directories(self):
819
create, root = self.get_transform()
820
kansas = create.new_directory('kansas', root, 'kansas-id')
821
create.new_directory('house', kansas, 'house-id')
822
create.new_directory('oz', root, 'oz-id')
824
cyclone, root = self.get_transform()
825
oz = cyclone.trans_id_tree_file_id('oz-id')
826
house = cyclone.trans_id_tree_file_id('house-id')
827
cyclone.adjust_path('house', oz, house)
830
def test_moving_root(self):
831
create, root = self.get_transform()
832
fun = create.new_directory('fun', root, 'fun-id')
833
create.new_directory('sun', root, 'sun-id')
834
create.new_directory('moon', root, 'moon')
836
transform, root = self.get_transform()
837
transform.adjust_root_path('oldroot', fun)
838
new_root = transform.trans_id_tree_path('')
839
transform.version_file('new-root', new_root)
842
def test_renames(self):
843
create, root = self.get_transform()
844
old = create.new_directory('old-parent', root, 'old-id')
845
intermediate = create.new_directory('intermediate', old, 'im-id')
846
myfile = create.new_file('myfile', intermediate, 'myfile-text',
849
rename, root = self.get_transform()
850
old = rename.trans_id_file_id('old-id')
851
rename.adjust_path('new', root, old)
852
myfile = rename.trans_id_file_id('myfile-id')
853
rename.set_executability(True, myfile)
856
def test_rename_fails(self):
857
self.requireFeature(features.not_running_as_root)
858
# see https://bugs.launchpad.net/bzr/+bug/491763
859
create, root_id = self.get_transform()
860
first_dir = create.new_directory('first-dir', root_id, 'first-id')
861
myfile = create.new_file('myfile', root_id, 'myfile-text',
864
if os.name == "posix" and sys.platform != "cygwin":
865
# posix filesystems fail on renaming if the readonly bit is set
866
osutils.make_readonly(self.wt.abspath('first-dir'))
867
elif os.name == "nt":
868
# windows filesystems fail on renaming open files
869
self.addCleanup(file(self.wt.abspath('myfile')).close)
871
self.skip("Don't know how to force a permissions error on rename")
872
# now transform to rename
873
rename_transform, root_id = self.get_transform()
874
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
875
dir_id = rename_transform.trans_id_file_id('first-id')
876
rename_transform.adjust_path('newname', dir_id, file_trans_id)
877
e = self.assertRaises(errors.TransformRenameFailed,
878
rename_transform.apply)
880
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
881
# to .../first-dir/newname: [Errno 13] Permission denied"
882
# On windows looks like:
883
# "Failed to rename .../work/myfile to
884
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
885
# The strerror will vary per OS and language so it's not checked here
886
self.assertContainsRe(str(e),
887
"Failed to rename .*(first-dir.newname:|myfile)")
889
def test_set_executability_order(self):
890
"""Ensure that executability behaves the same, no matter what order.
892
- create file and set executability simultaneously
893
- create file and set executability afterward
894
- unsetting the executability of a file whose executability has not been
895
declared should throw an exception (this may happen when a
896
merge attempts to create a file with a duplicate ID)
898
transform, root = self.get_transform()
901
self.addCleanup(wt.unlock)
902
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
904
sac = transform.new_file('set_after_creation', root,
905
'Set after creation', 'sac')
906
transform.set_executability(True, sac)
907
uws = transform.new_file('unset_without_set', root, 'Unset badly',
909
self.assertRaises(KeyError, transform.set_executability, None, uws)
911
self.assertTrue(wt.is_executable('soc'))
912
self.assertTrue(wt.is_executable('sac'))
914
def test_preserve_mode(self):
915
"""File mode is preserved when replacing content"""
916
if sys.platform == 'win32':
917
raise TestSkipped('chmod has no effect on win32')
918
transform, root = self.get_transform()
919
transform.new_file('file1', root, 'contents', 'file1-id', True)
922
self.addCleanup(self.wt.unlock)
923
self.assertTrue(self.wt.is_executable('file1-id'))
924
transform, root = self.get_transform()
925
file1_id = transform.trans_id_tree_file_id('file1-id')
926
transform.delete_contents(file1_id)
927
transform.create_file('contents2', file1_id)
929
self.assertTrue(self.wt.is_executable('file1-id'))
931
def test__set_mode_stats_correctly(self):
932
"""_set_mode stats to determine file mode."""
933
if sys.platform == 'win32':
934
raise TestSkipped('chmod has no effect on win32')
938
def instrumented_stat(path):
939
stat_paths.append(path)
940
return real_stat(path)
942
transform, root = self.get_transform()
944
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
945
file_id='bar-id-1', executable=False)
948
transform, root = self.get_transform()
949
bar1_id = transform.trans_id_tree_path('bar')
950
bar2_id = transform.trans_id_tree_path('bar2')
952
os.stat = instrumented_stat
953
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
958
bar1_abspath = self.wt.abspath('bar')
959
self.assertEqual([bar1_abspath], stat_paths)
961
def test_iter_changes(self):
962
self.wt.set_root_id('eert_toor')
963
transform, root = self.get_transform()
964
transform.new_file('old', root, 'blah', 'id-1', True)
966
transform, root = self.get_transform()
968
self.assertEqual([], list(transform.iter_changes()))
969
old = transform.trans_id_tree_file_id('id-1')
970
transform.unversion_file(old)
971
self.assertEqual([('id-1', ('old', None), False, (True, False),
972
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
973
(True, True))], list(transform.iter_changes()))
974
transform.new_directory('new', root, 'id-1')
975
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
976
('eert_toor', 'eert_toor'), ('old', 'new'),
977
('file', 'directory'),
978
(True, False))], list(transform.iter_changes()))
982
def test_iter_changes_new(self):
983
self.wt.set_root_id('eert_toor')
984
transform, root = self.get_transform()
985
transform.new_file('old', root, 'blah')
987
transform, root = self.get_transform()
989
old = transform.trans_id_tree_path('old')
990
transform.version_file('id-1', old)
991
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
992
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
993
(False, False))], list(transform.iter_changes()))
997
def test_iter_changes_modifications(self):
998
self.wt.set_root_id('eert_toor')
999
transform, root = self.get_transform()
1000
transform.new_file('old', root, 'blah', 'id-1')
1001
transform.new_file('new', root, 'blah')
1002
transform.new_directory('subdir', root, 'subdir-id')
1004
transform, root = self.get_transform()
1006
old = transform.trans_id_tree_path('old')
1007
subdir = transform.trans_id_tree_file_id('subdir-id')
1008
new = transform.trans_id_tree_path('new')
1009
self.assertEqual([], list(transform.iter_changes()))
1012
transform.delete_contents(old)
1013
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1014
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1015
(False, False))], list(transform.iter_changes()))
1018
transform.create_file('blah', old)
1019
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1020
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1021
(False, False))], list(transform.iter_changes()))
1022
transform.cancel_deletion(old)
1023
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1024
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1025
(False, False))], list(transform.iter_changes()))
1026
transform.cancel_creation(old)
1028
# move file_id to a different file
1029
self.assertEqual([], list(transform.iter_changes()))
1030
transform.unversion_file(old)
1031
transform.version_file('id-1', new)
1032
transform.adjust_path('old', root, new)
1033
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1034
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1035
(False, False))], list(transform.iter_changes()))
1036
transform.cancel_versioning(new)
1037
transform._removed_id = set()
1040
self.assertEqual([], list(transform.iter_changes()))
1041
transform.set_executability(True, old)
1042
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1043
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1044
(False, True))], list(transform.iter_changes()))
1045
transform.set_executability(None, old)
1048
self.assertEqual([], list(transform.iter_changes()))
1049
transform.adjust_path('new', root, old)
1050
transform._new_parent = {}
1051
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1052
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1053
(False, False))], list(transform.iter_changes()))
1054
transform._new_name = {}
1057
self.assertEqual([], list(transform.iter_changes()))
1058
transform.adjust_path('new', subdir, old)
1059
transform._new_name = {}
1060
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1061
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1062
('file', 'file'), (False, False))],
1063
list(transform.iter_changes()))
1064
transform._new_path = {}
1067
transform.finalize()
1069
def test_iter_changes_modified_bleed(self):
1070
self.wt.set_root_id('eert_toor')
1071
"""Modified flag should not bleed from one change to another"""
1072
# unfortunately, we have no guarantee that file1 (which is modified)
1073
# will be applied before file2. And if it's applied after file2, it
1074
# obviously can't bleed into file2's change output. But for now, it
1076
transform, root = self.get_transform()
1077
transform.new_file('file1', root, 'blah', 'id-1')
1078
transform.new_file('file2', root, 'blah', 'id-2')
1080
transform, root = self.get_transform()
1082
transform.delete_contents(transform.trans_id_file_id('id-1'))
1083
transform.set_executability(True,
1084
transform.trans_id_file_id('id-2'))
1085
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1086
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1087
('file', None), (False, False)),
1088
('id-2', (u'file2', u'file2'), False, (True, True),
1089
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1090
('file', 'file'), (False, True))],
1091
list(transform.iter_changes()))
1093
transform.finalize()
1095
def test_iter_changes_move_missing(self):
1096
"""Test moving ids with no files around"""
1097
self.wt.set_root_id('toor_eert')
1098
# Need two steps because versioning a non-existant file is a conflict.
1099
transform, root = self.get_transform()
1100
transform.new_directory('floater', root, 'floater-id')
1102
transform, root = self.get_transform()
1103
transform.delete_contents(transform.trans_id_tree_path('floater'))
1105
transform, root = self.get_transform()
1106
floater = transform.trans_id_tree_path('floater')
1108
transform.adjust_path('flitter', root, floater)
1109
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1110
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1111
(None, None), (False, False))], list(transform.iter_changes()))
1113
transform.finalize()
1115
def test_iter_changes_pointless(self):
1116
"""Ensure that no-ops are not treated as modifications"""
1117
self.wt.set_root_id('eert_toor')
1118
transform, root = self.get_transform()
1119
transform.new_file('old', root, 'blah', 'id-1')
1120
transform.new_directory('subdir', root, 'subdir-id')
1122
transform, root = self.get_transform()
1124
old = transform.trans_id_tree_path('old')
1125
subdir = transform.trans_id_tree_file_id('subdir-id')
1126
self.assertEqual([], list(transform.iter_changes()))
1127
transform.delete_contents(subdir)
1128
transform.create_directory(subdir)
1129
transform.set_executability(False, old)
1130
transform.unversion_file(old)
1131
transform.version_file('id-1', old)
1132
transform.adjust_path('old', root, old)
1133
self.assertEqual([], list(transform.iter_changes()))
1135
transform.finalize()
1137
def test_rename_count(self):
1138
transform, root = self.get_transform()
1139
transform.new_file('name1', root, 'contents')
1140
self.assertEqual(transform.rename_count, 0)
1142
self.assertEqual(transform.rename_count, 1)
1143
transform2, root = self.get_transform()
1144
transform2.adjust_path('name2', root,
1145
transform2.trans_id_tree_path('name1'))
1146
self.assertEqual(transform2.rename_count, 0)
1148
self.assertEqual(transform2.rename_count, 2)
1150
def test_change_parent(self):
1151
"""Ensure that after we change a parent, the results are still right.
1153
Renames and parent changes on pending transforms can happen as part
1154
of conflict resolution, and are explicitly permitted by the
1157
This test ensures they work correctly with the rename-avoidance
1160
transform, root = self.get_transform()
1161
parent1 = transform.new_directory('parent1', root)
1162
child1 = transform.new_file('child1', parent1, 'contents')
1163
parent2 = transform.new_directory('parent2', root)
1164
transform.adjust_path('child1', parent2, child1)
1166
self.failIfExists(self.wt.abspath('parent1/child1'))
1167
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1168
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1169
# no rename for child1 (counting only renames during apply)
1170
self.failUnlessEqual(2, transform.rename_count)
1172
def test_cancel_parent(self):
1173
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1175
This is like the test_change_parent, except that we cancel the parent
1176
before adjusting the path. The transform must detect that the
1177
directory is non-empty, and move children to safe locations.
1179
transform, root = self.get_transform()
1180
parent1 = transform.new_directory('parent1', root)
1181
child1 = transform.new_file('child1', parent1, 'contents')
1182
child2 = transform.new_file('child2', parent1, 'contents')
1184
transform.cancel_creation(parent1)
1186
self.fail('Failed to move child1 before deleting parent1')
1187
transform.cancel_creation(child2)
1188
transform.create_directory(parent1)
1190
transform.cancel_creation(parent1)
1191
# If the transform incorrectly believes that child2 is still in
1192
# parent1's limbo directory, it will try to rename it and fail
1193
# because was already moved by the first cancel_creation.
1195
self.fail('Transform still thinks child2 is a child of parent1')
1196
parent2 = transform.new_directory('parent2', root)
1197
transform.adjust_path('child1', parent2, child1)
1199
self.failIfExists(self.wt.abspath('parent1'))
1200
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1201
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1202
self.failUnlessEqual(2, transform.rename_count)
1204
def test_adjust_and_cancel(self):
1205
"""Make sure adjust_path keeps track of limbo children properly"""
1206
transform, root = self.get_transform()
1207
parent1 = transform.new_directory('parent1', root)
1208
child1 = transform.new_file('child1', parent1, 'contents')
1209
parent2 = transform.new_directory('parent2', root)
1210
transform.adjust_path('child1', parent2, child1)
1211
transform.cancel_creation(child1)
1213
transform.cancel_creation(parent1)
1214
# if the transform thinks child1 is still in parent1's limbo
1215
# directory, it will attempt to move it and fail.
1217
self.fail('Transform still thinks child1 is a child of parent1')
1218
transform.finalize()
1220
def test_noname_contents(self):
1221
"""TreeTransform should permit deferring naming files."""
1222
transform, root = self.get_transform()
1223
parent = transform.trans_id_file_id('parent-id')
1225
transform.create_directory(parent)
1227
self.fail("Can't handle contents with no name")
1228
transform.finalize()
1230
def test_noname_contents_nested(self):
1231
"""TreeTransform should permit deferring naming files."""
1232
transform, root = self.get_transform()
1233
parent = transform.trans_id_file_id('parent-id')
1235
transform.create_directory(parent)
1237
self.fail("Can't handle contents with no name")
1238
child = transform.new_directory('child', parent)
1239
transform.adjust_path('parent', root, parent)
1241
self.failUnlessExists(self.wt.abspath('parent/child'))
1242
self.assertEqual(1, transform.rename_count)
1244
def test_reuse_name(self):
1245
"""Avoid reusing the same limbo name for different files"""
1246
transform, root = self.get_transform()
1247
parent = transform.new_directory('parent', root)
1248
child1 = transform.new_directory('child', parent)
1250
child2 = transform.new_directory('child', parent)
1252
self.fail('Tranform tried to use the same limbo name twice')
1253
transform.adjust_path('child2', parent, child2)
1255
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1256
# child2 is put into top-level limbo because child1 has already
1257
# claimed the direct limbo path when child2 is created. There is no
1258
# advantage in renaming files once they're in top-level limbo, except
1260
self.assertEqual(2, transform.rename_count)
1262
def test_reuse_when_first_moved(self):
1263
"""Don't avoid direct paths when it is safe to use them"""
1264
transform, root = self.get_transform()
1265
parent = transform.new_directory('parent', root)
1266
child1 = transform.new_directory('child', parent)
1267
transform.adjust_path('child1', parent, child1)
1268
child2 = transform.new_directory('child', parent)
1270
# limbo/new-1 => parent
1271
self.assertEqual(1, transform.rename_count)
1273
def test_reuse_after_cancel(self):
1274
"""Don't avoid direct paths when it is safe to use them"""
1275
transform, root = self.get_transform()
1276
parent2 = transform.new_directory('parent2', root)
1277
child1 = transform.new_directory('child1', parent2)
1278
transform.cancel_creation(parent2)
1279
transform.create_directory(parent2)
1280
child2 = transform.new_directory('child1', parent2)
1281
transform.adjust_path('child2', parent2, child1)
1283
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1284
self.assertEqual(2, transform.rename_count)
1286
def test_finalize_order(self):
1287
"""Finalize must be done in child-to-parent order"""
1288
transform, root = self.get_transform()
1289
parent = transform.new_directory('parent', root)
1290
child = transform.new_directory('child', parent)
1292
transform.finalize()
1294
self.fail('Tried to remove parent before child1')
1296
def test_cancel_with_cancelled_child_should_succeed(self):
1297
transform, root = self.get_transform()
1298
parent = transform.new_directory('parent', root)
1299
child = transform.new_directory('child', parent)
1300
transform.cancel_creation(child)
1301
transform.cancel_creation(parent)
1302
transform.finalize()
1304
def test_rollback_on_directory_clash(self):
1306
wt = self.make_branch_and_tree('.')
1307
tt = TreeTransform(wt) # TreeTransform obtains write lock
1309
foo = tt.new_directory('foo', tt.root)
1310
tt.new_file('bar', foo, 'foobar')
1311
baz = tt.new_directory('baz', tt.root)
1312
tt.new_file('qux', baz, 'quux')
1313
# Ask for a rename 'foo' -> 'baz'
1314
tt.adjust_path('baz', tt.root, foo)
1315
# Lie to tt that we've already resolved all conflicts.
1316
tt.apply(no_conflicts=True)
1320
# The rename will fail because the target directory is not empty (but
1321
# raises FileExists anyway).
1322
err = self.assertRaises(errors.FileExists, tt_helper)
1323
self.assertContainsRe(str(err),
1324
"^File exists: .+/baz")
1326
def test_two_directories_clash(self):
1328
wt = self.make_branch_and_tree('.')
1329
tt = TreeTransform(wt) # TreeTransform obtains write lock
1331
foo_1 = tt.new_directory('foo', tt.root)
1332
tt.new_directory('bar', foo_1)
1333
# Adding the same directory with a different content
1334
foo_2 = tt.new_directory('foo', tt.root)
1335
tt.new_directory('baz', foo_2)
1336
# Lie to tt that we've already resolved all conflicts.
1337
tt.apply(no_conflicts=True)
1341
err = self.assertRaises(errors.FileExists, tt_helper)
1342
self.assertContainsRe(str(err),
1343
"^File exists: .+/foo")
1345
def test_two_directories_clash_finalize(self):
1347
wt = self.make_branch_and_tree('.')
1348
tt = TreeTransform(wt) # TreeTransform obtains write lock
1350
foo_1 = tt.new_directory('foo', tt.root)
1351
tt.new_directory('bar', foo_1)
1352
# Adding the same directory with a different content
1353
foo_2 = tt.new_directory('foo', tt.root)
1354
tt.new_directory('baz', foo_2)
1355
# Lie to tt that we've already resolved all conflicts.
1356
tt.apply(no_conflicts=True)
1360
err = self.assertRaises(errors.FileExists, tt_helper)
1361
self.assertContainsRe(str(err),
1362
"^File exists: .+/foo")
1364
def test_file_to_directory(self):
1365
wt = self.make_branch_and_tree('.')
1366
self.build_tree(['foo'])
1369
tt = TreeTransform(wt)
1370
self.addCleanup(tt.finalize)
1371
foo_trans_id = tt.trans_id_tree_path("foo")
1372
tt.delete_contents(foo_trans_id)
1373
tt.create_directory(foo_trans_id)
1374
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1375
tt.create_file(["aa\n"], bar_trans_id)
1376
tt.version_file("bar-1", bar_trans_id)
1378
self.failUnlessExists("foo/bar")
1381
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1386
changes = wt.changes_from(wt.basis_tree())
1387
self.assertFalse(changes.has_changed(), changes)
1389
def test_file_to_symlink(self):
1390
self.requireFeature(SymlinkFeature)
1391
wt = self.make_branch_and_tree('.')
1392
self.build_tree(['foo'])
1395
tt = TreeTransform(wt)
1396
self.addCleanup(tt.finalize)
1397
foo_trans_id = tt.trans_id_tree_path("foo")
1398
tt.delete_contents(foo_trans_id)
1399
tt.create_symlink("bar", foo_trans_id)
1401
self.failUnlessExists("foo")
1403
self.addCleanup(wt.unlock)
1404
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1407
def test_dir_to_file(self):
1408
wt = self.make_branch_and_tree('.')
1409
self.build_tree(['foo/', 'foo/bar'])
1410
wt.add(['foo', 'foo/bar'])
1412
tt = TreeTransform(wt)
1413
self.addCleanup(tt.finalize)
1414
foo_trans_id = tt.trans_id_tree_path("foo")
1415
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1416
tt.delete_contents(foo_trans_id)
1417
tt.delete_versioned(bar_trans_id)
1418
tt.create_file(["aa\n"], foo_trans_id)
1420
self.failUnlessExists("foo")
1422
self.addCleanup(wt.unlock)
1423
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1426
def test_dir_to_hardlink(self):
1427
self.requireFeature(HardlinkFeature)
1428
wt = self.make_branch_and_tree('.')
1429
self.build_tree(['foo/', 'foo/bar'])
1430
wt.add(['foo', 'foo/bar'])
1432
tt = TreeTransform(wt)
1433
self.addCleanup(tt.finalize)
1434
foo_trans_id = tt.trans_id_tree_path("foo")
1435
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1436
tt.delete_contents(foo_trans_id)
1437
tt.delete_versioned(bar_trans_id)
1438
self.build_tree(['baz'])
1439
tt.create_hardlink("baz", foo_trans_id)
1441
self.failUnlessExists("foo")
1442
self.failUnlessExists("baz")
1444
self.addCleanup(wt.unlock)
1445
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1448
def test_no_final_path(self):
1449
transform, root = self.get_transform()
1450
trans_id = transform.trans_id_file_id('foo')
1451
transform.create_file('bar', trans_id)
1452
transform.cancel_creation(trans_id)
1455
def test_create_from_tree(self):
1456
tree1 = self.make_branch_and_tree('tree1')
1457
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1458
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1459
tree2 = self.make_branch_and_tree('tree2')
1460
tt = TreeTransform(tree2)
1461
foo_trans_id = tt.create_path('foo', tt.root)
1462
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1463
bar_trans_id = tt.create_path('bar', tt.root)
1464
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1466
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1467
self.assertFileEqual('baz', 'tree2/bar')
1469
def test_create_from_tree_bytes(self):
1470
"""Provided lines are used instead of tree content."""
1471
tree1 = self.make_branch_and_tree('tree1')
1472
self.build_tree_contents([('tree1/foo', 'bar'),])
1473
tree1.add('foo', 'foo-id')
1474
tree2 = self.make_branch_and_tree('tree2')
1475
tt = TreeTransform(tree2)
1476
foo_trans_id = tt.create_path('foo', tt.root)
1477
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1479
self.assertFileEqual('qux', 'tree2/foo')
1481
def test_create_from_tree_symlink(self):
1482
self.requireFeature(SymlinkFeature)
1483
tree1 = self.make_branch_and_tree('tree1')
1484
os.symlink('bar', 'tree1/foo')
1485
tree1.add('foo', 'foo-id')
1486
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1487
foo_trans_id = tt.create_path('foo', tt.root)
1488
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1490
self.assertEqual('bar', os.readlink('tree2/foo'))
1493
class TransformGroup(object):
1495
def __init__(self, dirname, root_id):
1498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1499
self.wt.set_root_id(root_id)
1500
self.b = self.wt.branch
1501
self.tt = TreeTransform(self.wt)
1502
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1505
def conflict_text(tree, merge):
1506
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1507
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1510
class TestTransformMerge(TestCaseInTempDir):
1512
def test_text_merge(self):
1513
root_id = generate_ids.gen_root_id()
1514
base = TransformGroup("base", root_id)
1515
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1516
base.tt.new_file('b', base.root, 'b1', 'b')
1517
base.tt.new_file('c', base.root, 'c', 'c')
1518
base.tt.new_file('d', base.root, 'd', 'd')
1519
base.tt.new_file('e', base.root, 'e', 'e')
1520
base.tt.new_file('f', base.root, 'f', 'f')
1521
base.tt.new_directory('g', base.root, 'g')
1522
base.tt.new_directory('h', base.root, 'h')
1524
other = TransformGroup("other", root_id)
1525
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1526
other.tt.new_file('b', other.root, 'b2', 'b')
1527
other.tt.new_file('c', other.root, 'c2', 'c')
1528
other.tt.new_file('d', other.root, 'd', 'd')
1529
other.tt.new_file('e', other.root, 'e2', 'e')
1530
other.tt.new_file('f', other.root, 'f', 'f')
1531
other.tt.new_file('g', other.root, 'g', 'g')
1532
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1533
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1535
this = TransformGroup("this", root_id)
1536
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1537
this.tt.new_file('b', this.root, 'b', 'b')
1538
this.tt.new_file('c', this.root, 'c', 'c')
1539
this.tt.new_file('d', this.root, 'd2', 'd')
1540
this.tt.new_file('e', this.root, 'e2', 'e')
1541
this.tt.new_file('f', this.root, 'f', 'f')
1542
this.tt.new_file('g', this.root, 'g', 'g')
1543
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1544
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1546
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1549
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1550
# three-way text conflict
1551
self.assertEqual(this.wt.get_file('b').read(),
1552
conflict_text('b', 'b2'))
1554
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1556
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1557
# Ambigious clean merge
1558
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1560
self.assertEqual(this.wt.get_file('f').read(), 'f')
1561
# Correct correct results when THIS == OTHER
1562
self.assertEqual(this.wt.get_file('g').read(), 'g')
1563
# Text conflict when THIS & OTHER are text and BASE is dir
1564
self.assertEqual(this.wt.get_file('h').read(),
1565
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1566
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1568
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1570
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1571
self.assertEqual(this.wt.get_file('i').read(),
1572
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1573
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1575
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1577
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1578
modified = ['a', 'b', 'c', 'h', 'i']
1579
merge_modified = this.wt.merge_modified()
1580
self.assertSubset(merge_modified, modified)
1581
self.assertEqual(len(merge_modified), len(modified))
1582
file(this.wt.id2abspath('a'), 'wb').write('booga')
1584
merge_modified = this.wt.merge_modified()
1585
self.assertSubset(merge_modified, modified)
1586
self.assertEqual(len(merge_modified), len(modified))
1590
def test_file_merge(self):
1591
self.requireFeature(SymlinkFeature)
1592
root_id = generate_ids.gen_root_id()
1593
base = TransformGroup("BASE", root_id)
1594
this = TransformGroup("THIS", root_id)
1595
other = TransformGroup("OTHER", root_id)
1596
for tg in this, base, other:
1597
tg.tt.new_directory('a', tg.root, 'a')
1598
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1599
tg.tt.new_file('c', tg.root, 'c', 'c')
1600
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1601
targets = ((base, 'base-e', 'base-f', None, None),
1602
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1603
(other, 'other-e', None, 'other-g', 'other-h'))
1604
for tg, e_target, f_target, g_target, h_target in targets:
1605
for link, target in (('e', e_target), ('f', f_target),
1606
('g', g_target), ('h', h_target)):
1607
if target is not None:
1608
tg.tt.new_symlink(link, tg.root, target, link)
1610
for tg in this, base, other:
1612
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1613
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1614
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1615
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1616
for suffix in ('THIS', 'BASE', 'OTHER'):
1617
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1618
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1619
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1620
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1621
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1622
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1623
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1624
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1625
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1626
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1627
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1628
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1629
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1630
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1632
def test_filename_merge(self):
1633
root_id = generate_ids.gen_root_id()
1634
base = TransformGroup("BASE", root_id)
1635
this = TransformGroup("THIS", root_id)
1636
other = TransformGroup("OTHER", root_id)
1637
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1638
for t in [base, this, other]]
1639
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1640
for t in [base, this, other]]
1641
base.tt.new_directory('c', base_a, 'c')
1642
this.tt.new_directory('c1', this_a, 'c')
1643
other.tt.new_directory('c', other_b, 'c')
1645
base.tt.new_directory('d', base_a, 'd')
1646
this.tt.new_directory('d1', this_b, 'd')
1647
other.tt.new_directory('d', other_a, 'd')
1649
base.tt.new_directory('e', base_a, 'e')
1650
this.tt.new_directory('e', this_a, 'e')
1651
other.tt.new_directory('e1', other_b, 'e')
1653
base.tt.new_directory('f', base_a, 'f')
1654
this.tt.new_directory('f1', this_b, 'f')
1655
other.tt.new_directory('f1', other_b, 'f')
1657
for tg in [this, base, other]:
1659
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1660
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1661
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1662
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1663
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1665
def test_filename_merge_conflicts(self):
1666
root_id = generate_ids.gen_root_id()
1667
base = TransformGroup("BASE", root_id)
1668
this = TransformGroup("THIS", root_id)
1669
other = TransformGroup("OTHER", root_id)
1670
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1671
for t in [base, this, other]]
1672
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1673
for t in [base, this, other]]
1675
base.tt.new_file('g', base_a, 'g', 'g')
1676
other.tt.new_file('g1', other_b, 'g1', 'g')
1678
base.tt.new_file('h', base_a, 'h', 'h')
1679
this.tt.new_file('h1', this_b, 'h1', 'h')
1681
base.tt.new_file('i', base.root, 'i', 'i')
1682
other.tt.new_directory('i1', this_b, 'i')
1684
for tg in [this, base, other]:
1686
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1688
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1689
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1690
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1691
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1692
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1693
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1694
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1697
class TestBuildTree(tests.TestCaseWithTransport):
1699
def test_build_tree_with_symlinks(self):
1700
self.requireFeature(SymlinkFeature)
1702
a = BzrDir.create_standalone_workingtree('a')
1704
file('a/foo/bar', 'wb').write('contents')
1705
os.symlink('a/foo/bar', 'a/foo/baz')
1706
a.add(['foo', 'foo/bar', 'foo/baz'])
1707
a.commit('initial commit')
1708
b = BzrDir.create_standalone_workingtree('b')
1709
basis = a.basis_tree()
1711
self.addCleanup(basis.unlock)
1712
build_tree(basis, b)
1713
self.assertIs(os.path.isdir('b/foo'), True)
1714
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1715
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1717
def test_build_with_references(self):
1718
tree = self.make_branch_and_tree('source',
1719
format='dirstate-with-subtree')
1720
subtree = self.make_branch_and_tree('source/subtree',
1721
format='dirstate-with-subtree')
1722
tree.add_reference(subtree)
1723
tree.commit('a revision')
1724
tree.branch.create_checkout('target')
1725
self.failUnlessExists('target')
1726
self.failUnlessExists('target/subtree')
1728
def test_file_conflict_handling(self):
1729
"""Ensure that when building trees, conflict handling is done"""
1730
source = self.make_branch_and_tree('source')
1731
target = self.make_branch_and_tree('target')
1732
self.build_tree(['source/file', 'target/file'])
1733
source.add('file', 'new-file')
1734
source.commit('added file')
1735
build_tree(source.basis_tree(), target)
1736
self.assertEqual([DuplicateEntry('Moved existing file to',
1737
'file.moved', 'file', None, 'new-file')],
1739
target2 = self.make_branch_and_tree('target2')
1740
target_file = file('target2/file', 'wb')
1742
source_file = file('source/file', 'rb')
1744
target_file.write(source_file.read())
1749
build_tree(source.basis_tree(), target2)
1750
self.assertEqual([], target2.conflicts())
1752
def test_symlink_conflict_handling(self):
1753
"""Ensure that when building trees, conflict handling is done"""
1754
self.requireFeature(SymlinkFeature)
1755
source = self.make_branch_and_tree('source')
1756
os.symlink('foo', 'source/symlink')
1757
source.add('symlink', 'new-symlink')
1758
source.commit('added file')
1759
target = self.make_branch_and_tree('target')
1760
os.symlink('bar', 'target/symlink')
1761
build_tree(source.basis_tree(), target)
1762
self.assertEqual([DuplicateEntry('Moved existing file to',
1763
'symlink.moved', 'symlink', None, 'new-symlink')],
1765
target = self.make_branch_and_tree('target2')
1766
os.symlink('foo', 'target2/symlink')
1767
build_tree(source.basis_tree(), target)
1768
self.assertEqual([], target.conflicts())
1770
def test_directory_conflict_handling(self):
1771
"""Ensure that when building trees, conflict handling is done"""
1772
source = self.make_branch_and_tree('source')
1773
target = self.make_branch_and_tree('target')
1774
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1775
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1776
source.commit('added file')
1777
build_tree(source.basis_tree(), target)
1778
self.assertEqual([], target.conflicts())
1779
self.failUnlessExists('target/dir1/file')
1781
# Ensure contents are merged
1782
target = self.make_branch_and_tree('target2')
1783
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1784
build_tree(source.basis_tree(), target)
1785
self.assertEqual([], target.conflicts())
1786
self.failUnlessExists('target2/dir1/file2')
1787
self.failUnlessExists('target2/dir1/file')
1789
# Ensure new contents are suppressed for existing branches
1790
target = self.make_branch_and_tree('target3')
1791
self.make_branch('target3/dir1')
1792
self.build_tree(['target3/dir1/file2'])
1793
build_tree(source.basis_tree(), target)
1794
self.failIfExists('target3/dir1/file')
1795
self.failUnlessExists('target3/dir1/file2')
1796
self.failUnlessExists('target3/dir1.diverted/file')
1797
self.assertEqual([DuplicateEntry('Diverted to',
1798
'dir1.diverted', 'dir1', 'new-dir1', None)],
1801
target = self.make_branch_and_tree('target4')
1802
self.build_tree(['target4/dir1/'])
1803
self.make_branch('target4/dir1/file')
1804
build_tree(source.basis_tree(), target)
1805
self.failUnlessExists('target4/dir1/file')
1806
self.assertEqual('directory', file_kind('target4/dir1/file'))
1807
self.failUnlessExists('target4/dir1/file.diverted')
1808
self.assertEqual([DuplicateEntry('Diverted to',
1809
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1812
def test_mixed_conflict_handling(self):
1813
"""Ensure that when building trees, conflict handling is done"""
1814
source = self.make_branch_and_tree('source')
1815
target = self.make_branch_and_tree('target')
1816
self.build_tree(['source/name', 'target/name/'])
1817
source.add('name', 'new-name')
1818
source.commit('added file')
1819
build_tree(source.basis_tree(), target)
1820
self.assertEqual([DuplicateEntry('Moved existing file to',
1821
'name.moved', 'name', None, 'new-name')], target.conflicts())
1823
def test_raises_in_populated(self):
1824
source = self.make_branch_and_tree('source')
1825
self.build_tree(['source/name'])
1827
source.commit('added name')
1828
target = self.make_branch_and_tree('target')
1829
self.build_tree(['target/name'])
1831
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1832
build_tree, source.basis_tree(), target)
1834
def test_build_tree_rename_count(self):
1835
source = self.make_branch_and_tree('source')
1836
self.build_tree(['source/file1', 'source/dir1/'])
1837
source.add(['file1', 'dir1'])
1838
source.commit('add1')
1839
target1 = self.make_branch_and_tree('target1')
1840
transform_result = build_tree(source.basis_tree(), target1)
1841
self.assertEqual(2, transform_result.rename_count)
1843
self.build_tree(['source/dir1/file2'])
1844
source.add(['dir1/file2'])
1845
source.commit('add3')
1846
target2 = self.make_branch_and_tree('target2')
1847
transform_result = build_tree(source.basis_tree(), target2)
1848
# children of non-root directories should not be renamed
1849
self.assertEqual(2, transform_result.rename_count)
1851
def create_ab_tree(self):
1852
"""Create a committed test tree with two files"""
1853
source = self.make_branch_and_tree('source')
1854
self.build_tree_contents([('source/file1', 'A')])
1855
self.build_tree_contents([('source/file2', 'B')])
1856
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1857
source.commit('commit files')
1859
self.addCleanup(source.unlock)
1862
def test_build_tree_accelerator_tree(self):
1863
source = self.create_ab_tree()
1864
self.build_tree_contents([('source/file2', 'C')])
1866
real_source_get_file = source.get_file
1867
def get_file(file_id, path=None):
1868
calls.append(file_id)
1869
return real_source_get_file(file_id, path)
1870
source.get_file = get_file
1871
target = self.make_branch_and_tree('target')
1872
revision_tree = source.basis_tree()
1873
revision_tree.lock_read()
1874
self.addCleanup(revision_tree.unlock)
1875
build_tree(revision_tree, target, source)
1876
self.assertEqual(['file1-id'], calls)
1878
self.addCleanup(target.unlock)
1879
self.assertEqual([], list(target.iter_changes(revision_tree)))
1881
def test_build_tree_accelerator_tree_missing_file(self):
1882
source = self.create_ab_tree()
1883
os.unlink('source/file1')
1884
source.remove(['file2'])
1885
target = self.make_branch_and_tree('target')
1886
revision_tree = source.basis_tree()
1887
revision_tree.lock_read()
1888
self.addCleanup(revision_tree.unlock)
1889
build_tree(revision_tree, target, source)
1891
self.addCleanup(target.unlock)
1892
self.assertEqual([], list(target.iter_changes(revision_tree)))
1894
def test_build_tree_accelerator_wrong_kind(self):
1895
self.requireFeature(SymlinkFeature)
1896
source = self.make_branch_and_tree('source')
1897
self.build_tree_contents([('source/file1', '')])
1898
self.build_tree_contents([('source/file2', '')])
1899
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1900
source.commit('commit files')
1901
os.unlink('source/file2')
1902
self.build_tree_contents([('source/file2/', 'C')])
1903
os.unlink('source/file1')
1904
os.symlink('file2', 'source/file1')
1906
real_source_get_file = source.get_file
1907
def get_file(file_id, path=None):
1908
calls.append(file_id)
1909
return real_source_get_file(file_id, path)
1910
source.get_file = get_file
1911
target = self.make_branch_and_tree('target')
1912
revision_tree = source.basis_tree()
1913
revision_tree.lock_read()
1914
self.addCleanup(revision_tree.unlock)
1915
build_tree(revision_tree, target, source)
1916
self.assertEqual([], calls)
1918
self.addCleanup(target.unlock)
1919
self.assertEqual([], list(target.iter_changes(revision_tree)))
1921
def test_build_tree_hardlink(self):
1922
self.requireFeature(HardlinkFeature)
1923
source = self.create_ab_tree()
1924
target = self.make_branch_and_tree('target')
1925
revision_tree = source.basis_tree()
1926
revision_tree.lock_read()
1927
self.addCleanup(revision_tree.unlock)
1928
build_tree(revision_tree, target, source, hardlink=True)
1930
self.addCleanup(target.unlock)
1931
self.assertEqual([], list(target.iter_changes(revision_tree)))
1932
source_stat = os.stat('source/file1')
1933
target_stat = os.stat('target/file1')
1934
self.assertEqual(source_stat, target_stat)
1936
# Explicitly disallowing hardlinks should prevent them.
1937
target2 = self.make_branch_and_tree('target2')
1938
build_tree(revision_tree, target2, source, hardlink=False)
1940
self.addCleanup(target2.unlock)
1941
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1942
source_stat = os.stat('source/file1')
1943
target2_stat = os.stat('target2/file1')
1944
self.assertNotEqual(source_stat, target2_stat)
1946
def test_build_tree_accelerator_tree_moved(self):
1947
source = self.make_branch_and_tree('source')
1948
self.build_tree_contents([('source/file1', 'A')])
1949
source.add(['file1'], ['file1-id'])
1950
source.commit('commit files')
1951
source.rename_one('file1', 'file2')
1953
self.addCleanup(source.unlock)
1954
target = self.make_branch_and_tree('target')
1955
revision_tree = source.basis_tree()
1956
revision_tree.lock_read()
1957
self.addCleanup(revision_tree.unlock)
1958
build_tree(revision_tree, target, source)
1960
self.addCleanup(target.unlock)
1961
self.assertEqual([], list(target.iter_changes(revision_tree)))
1963
def test_build_tree_hardlinks_preserve_execute(self):
1964
self.requireFeature(HardlinkFeature)
1965
source = self.create_ab_tree()
1966
tt = TreeTransform(source)
1967
trans_id = tt.trans_id_tree_file_id('file1-id')
1968
tt.set_executability(True, trans_id)
1970
self.assertTrue(source.is_executable('file1-id'))
1971
target = self.make_branch_and_tree('target')
1972
revision_tree = source.basis_tree()
1973
revision_tree.lock_read()
1974
self.addCleanup(revision_tree.unlock)
1975
build_tree(revision_tree, target, source, hardlink=True)
1977
self.addCleanup(target.unlock)
1978
self.assertEqual([], list(target.iter_changes(revision_tree)))
1979
self.assertTrue(source.is_executable('file1-id'))
1981
def install_rot13_content_filter(self, pattern):
1983
# self.addCleanup(filters._reset_registry, filters._reset_registry())
1984
# below, but that looks a bit... hard to read even if it's exactly
1986
original_registry = filters._reset_registry()
1987
def restore_registry():
1988
filters._reset_registry(original_registry)
1989
self.addCleanup(restore_registry)
1990
def rot13(chunks, context=None):
1991
return [''.join(chunks).encode('rot13')]
1992
rot13filter = filters.ContentFilter(rot13, rot13)
1993
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1994
os.mkdir(self.test_home_dir + '/.bazaar')
1995
rules_filename = self.test_home_dir + '/.bazaar/rules'
1996
f = open(rules_filename, 'wb')
1997
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1999
def uninstall_rules():
2000
os.remove(rules_filename)
2002
self.addCleanup(uninstall_rules)
2005
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2006
"""build_tree will not hardlink files that have content filtering rules
2007
applied to them (but will still hardlink other files from the same tree
2010
self.requireFeature(HardlinkFeature)
2011
self.install_rot13_content_filter('file1')
2012
source = self.create_ab_tree()
2013
target = self.make_branch_and_tree('target')
2014
revision_tree = source.basis_tree()
2015
revision_tree.lock_read()
2016
self.addCleanup(revision_tree.unlock)
2017
build_tree(revision_tree, target, source, hardlink=True)
2019
self.addCleanup(target.unlock)
2020
self.assertEqual([], list(target.iter_changes(revision_tree)))
2021
source_stat = os.stat('source/file1')
2022
target_stat = os.stat('target/file1')
2023
self.assertNotEqual(source_stat, target_stat)
2024
source_stat = os.stat('source/file2')
2025
target_stat = os.stat('target/file2')
2026
self.assertEqualStat(source_stat, target_stat)
2028
def test_case_insensitive_build_tree_inventory(self):
2029
if (tests.CaseInsensitiveFilesystemFeature.available()
2030
or tests.CaseInsCasePresFilenameFeature.available()):
2031
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2032
source = self.make_branch_and_tree('source')
2033
self.build_tree(['source/file', 'source/FILE'])
2034
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2035
source.commit('added files')
2036
# Don't try this at home, kids!
2037
# Force the tree to report that it is case insensitive
2038
target = self.make_branch_and_tree('target')
2039
target.case_sensitive = False
2040
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2041
self.assertEqual('file.moved', target.id2path('lower-id'))
2042
self.assertEqual('FILE', target.id2path('upper-id'))
2045
class TestCommitTransform(tests.TestCaseWithTransport):
2047
def get_branch(self):
2048
tree = self.make_branch_and_tree('tree')
2050
self.addCleanup(tree.unlock)
2051
tree.commit('empty commit')
2054
def get_branch_and_transform(self):
2055
branch = self.get_branch()
2056
tt = TransformPreview(branch.basis_tree())
2057
self.addCleanup(tt.finalize)
2060
def test_commit_wrong_basis(self):
2061
branch = self.get_branch()
2062
basis = branch.repository.revision_tree(
2063
_mod_revision.NULL_REVISION)
2064
tt = TransformPreview(basis)
2065
self.addCleanup(tt.finalize)
2066
e = self.assertRaises(ValueError, tt.commit, branch, '')
2067
self.assertEqual('TreeTransform not based on branch basis: null:',
2070
def test_empy_commit(self):
2071
branch, tt = self.get_branch_and_transform()
2072
rev = tt.commit(branch, 'my message')
2073
self.assertEqual(2, branch.revno())
2074
repo = branch.repository
2075
self.assertEqual('my message', repo.get_revision(rev).message)
2077
def test_merge_parents(self):
2078
branch, tt = self.get_branch_and_transform()
2079
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2080
self.assertEqual(['rev1b', 'rev1c'],
2081
branch.basis_tree().get_parent_ids()[1:])
2083
def test_first_commit(self):
2084
branch = self.make_branch('branch')
2086
self.addCleanup(branch.unlock)
2087
tt = TransformPreview(branch.basis_tree())
2088
self.addCleanup(tt.finalize)
2089
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2090
rev = tt.commit(branch, 'my message')
2091
self.assertEqual([], branch.basis_tree().get_parent_ids())
2092
self.assertNotEqual(_mod_revision.NULL_REVISION,
2093
branch.last_revision())
2095
def test_first_commit_with_merge_parents(self):
2096
branch = self.make_branch('branch')
2098
self.addCleanup(branch.unlock)
2099
tt = TransformPreview(branch.basis_tree())
2100
self.addCleanup(tt.finalize)
2101
e = self.assertRaises(ValueError, tt.commit, branch,
2102
'my message', ['rev1b-id'])
2103
self.assertEqual('Cannot supply merge parents for first commit.',
2105
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2107
def test_add_files(self):
2108
branch, tt = self.get_branch_and_transform()
2109
tt.new_file('file', tt.root, 'contents', 'file-id')
2110
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2111
if SymlinkFeature.available():
2112
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2113
rev = tt.commit(branch, 'message')
2114
tree = branch.basis_tree()
2115
self.assertEqual('file', tree.id2path('file-id'))
2116
self.assertEqual('contents', tree.get_file_text('file-id'))
2117
self.assertEqual('dir', tree.id2path('dir-id'))
2118
if SymlinkFeature.available():
2119
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2120
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2122
def test_add_unversioned(self):
2123
branch, tt = self.get_branch_and_transform()
2124
tt.new_file('file', tt.root, 'contents')
2125
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2126
'message', strict=True)
2128
def test_modify_strict(self):
2129
branch, tt = self.get_branch_and_transform()
2130
tt.new_file('file', tt.root, 'contents', 'file-id')
2131
tt.commit(branch, 'message', strict=True)
2132
tt = TransformPreview(branch.basis_tree())
2133
self.addCleanup(tt.finalize)
2134
trans_id = tt.trans_id_file_id('file-id')
2135
tt.delete_contents(trans_id)
2136
tt.create_file('contents', trans_id)
2137
tt.commit(branch, 'message', strict=True)
2139
def test_commit_malformed(self):
2140
"""Committing a malformed transform should raise an exception.
2142
In this case, we are adding a file without adding its parent.
2144
branch, tt = self.get_branch_and_transform()
2145
parent_id = tt.trans_id_file_id('parent-id')
2146
tt.new_file('file', parent_id, 'contents', 'file-id')
2147
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2150
def test_commit_rich_revision_data(self):
2151
branch, tt = self.get_branch_and_transform()
2152
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2153
committer='me <me@example.com>',
2154
revprops={'foo': 'bar'}, revision_id='revid-1',
2155
authors=['Author1 <author1@example.com>',
2156
'Author2 <author2@example.com>',
2158
self.assertEqual('revid-1', rev_id)
2159
revision = branch.repository.get_revision(rev_id)
2160
self.assertEqual(1, revision.timestamp)
2161
self.assertEqual(43201, revision.timezone)
2162
self.assertEqual('me <me@example.com>', revision.committer)
2163
self.assertEqual(['Author1 <author1@example.com>',
2164
'Author2 <author2@example.com>'],
2165
revision.get_apparent_authors())
2166
del revision.properties['authors']
2167
self.assertEqual({'foo': 'bar',
2168
'branch-nick': 'tree'},
2169
revision.properties)
2171
def test_no_explicit_revprops(self):
2172
branch, tt = self.get_branch_and_transform()
2173
rev_id = tt.commit(branch, 'message', authors=[
2174
'Author1 <author1@example.com>',
2175
'Author2 <author2@example.com>', ])
2176
revision = branch.repository.get_revision(rev_id)
2177
self.assertEqual(['Author1 <author1@example.com>',
2178
'Author2 <author2@example.com>'],
2179
revision.get_apparent_authors())
2180
self.assertEqual('tree', revision.properties['branch-nick'])
2183
class MockTransform(object):
2185
def has_named_child(self, by_parent, parent_id, name):
2186
for child_id in by_parent[parent_id]:
2190
elif name == "name.~%s~" % child_id:
2195
class MockEntry(object):
2197
object.__init__(self)
2201
class TestGetBackupName(TestCase):
2202
def test_get_backup_name(self):
2203
tt = MockTransform()
2204
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2205
self.assertEqual(name, 'name.~1~')
2206
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2207
self.assertEqual(name, 'name.~2~')
2208
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2209
self.assertEqual(name, 'name.~1~')
2210
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2211
self.assertEqual(name, 'name.~1~')
2212
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2213
self.assertEqual(name, 'name.~4~')
2216
class TestFileMover(tests.TestCaseWithTransport):
2218
def test_file_mover(self):
2219
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2220
mover = _FileMover()
2221
mover.rename('a', 'q')
2222
self.failUnlessExists('q')
2223
self.failIfExists('a')
2224
self.failUnlessExists('q/b')
2225
self.failUnlessExists('c')
2226
self.failUnlessExists('c/d')
2228
def test_pre_delete_rollback(self):
2229
self.build_tree(['a/'])
2230
mover = _FileMover()
2231
mover.pre_delete('a', 'q')
2232
self.failUnlessExists('q')
2233
self.failIfExists('a')
2235
self.failIfExists('q')
2236
self.failUnlessExists('a')
2238
def test_apply_deletions(self):
2239
self.build_tree(['a/', 'b/'])
2240
mover = _FileMover()
2241
mover.pre_delete('a', 'q')
2242
mover.pre_delete('b', 'r')
2243
self.failUnlessExists('q')
2244
self.failUnlessExists('r')
2245
self.failIfExists('a')
2246
self.failIfExists('b')
2247
mover.apply_deletions()
2248
self.failIfExists('q')
2249
self.failIfExists('r')
2250
self.failIfExists('a')
2251
self.failIfExists('b')
2253
def test_file_mover_rollback(self):
2254
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2255
mover = _FileMover()
2256
mover.rename('c/d', 'c/f')
2257
mover.rename('c/e', 'c/d')
2259
mover.rename('a', 'c')
2260
except errors.FileExists, e:
2262
self.failUnlessExists('a')
2263
self.failUnlessExists('c/d')
2266
class Bogus(Exception):
2270
class TestTransformRollback(tests.TestCaseWithTransport):
2272
class ExceptionFileMover(_FileMover):
2274
def __init__(self, bad_source=None, bad_target=None):
2275
_FileMover.__init__(self)
2276
self.bad_source = bad_source
2277
self.bad_target = bad_target
2279
def rename(self, source, target):
2280
if (self.bad_source is not None and
2281
source.endswith(self.bad_source)):
2283
elif (self.bad_target is not None and
2284
target.endswith(self.bad_target)):
2287
_FileMover.rename(self, source, target)
2289
def test_rollback_rename(self):
2290
tree = self.make_branch_and_tree('.')
2291
self.build_tree(['a/', 'a/b'])
2292
tt = TreeTransform(tree)
2293
self.addCleanup(tt.finalize)
2294
a_id = tt.trans_id_tree_path('a')
2295
tt.adjust_path('c', tt.root, a_id)
2296
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2297
self.assertRaises(Bogus, tt.apply,
2298
_mover=self.ExceptionFileMover(bad_source='a'))
2299
self.failUnlessExists('a')
2300
self.failUnlessExists('a/b')
2302
self.failUnlessExists('c')
2303
self.failUnlessExists('c/d')
2305
def test_rollback_rename_into_place(self):
2306
tree = self.make_branch_and_tree('.')
2307
self.build_tree(['a/', 'a/b'])
2308
tt = TreeTransform(tree)
2309
self.addCleanup(tt.finalize)
2310
a_id = tt.trans_id_tree_path('a')
2311
tt.adjust_path('c', tt.root, a_id)
2312
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2313
self.assertRaises(Bogus, tt.apply,
2314
_mover=self.ExceptionFileMover(bad_target='c/d'))
2315
self.failUnlessExists('a')
2316
self.failUnlessExists('a/b')
2318
self.failUnlessExists('c')
2319
self.failUnlessExists('c/d')
2321
def test_rollback_deletion(self):
2322
tree = self.make_branch_and_tree('.')
2323
self.build_tree(['a/', 'a/b'])
2324
tt = TreeTransform(tree)
2325
self.addCleanup(tt.finalize)
2326
a_id = tt.trans_id_tree_path('a')
2327
tt.delete_contents(a_id)
2328
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2329
self.assertRaises(Bogus, tt.apply,
2330
_mover=self.ExceptionFileMover(bad_target='d'))
2331
self.failUnlessExists('a')
2332
self.failUnlessExists('a/b')
2334
def test_resolve_no_parent(self):
2335
wt = self.make_branch_and_tree('.')
2336
tt = TreeTransform(wt)
2337
self.addCleanup(tt.finalize)
2338
parent = tt.trans_id_file_id('parent-id')
2339
tt.new_file('file', parent, 'Contents')
2340
resolve_conflicts(tt)
2343
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2344
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2346
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2347
('', ''), ('directory', 'directory'), (False, None))
2350
class TestTransformPreview(tests.TestCaseWithTransport):
2352
def create_tree(self):
2353
tree = self.make_branch_and_tree('.')
2354
self.build_tree_contents([('a', 'content 1')])
2355
tree.set_root_id('TREE_ROOT')
2356
tree.add('a', 'a-id')
2357
tree.commit('rev1', rev_id='rev1')
2358
return tree.branch.repository.revision_tree('rev1')
2360
def get_empty_preview(self):
2361
repository = self.make_repository('repo')
2362
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2363
preview = TransformPreview(tree)
2364
self.addCleanup(preview.finalize)
2367
def test_transform_preview(self):
2368
revision_tree = self.create_tree()
2369
preview = TransformPreview(revision_tree)
2370
self.addCleanup(preview.finalize)
2372
def test_transform_preview_tree(self):
2373
revision_tree = self.create_tree()
2374
preview = TransformPreview(revision_tree)
2375
self.addCleanup(preview.finalize)
2376
preview.get_preview_tree()
2378
def test_transform_new_file(self):
2379
revision_tree = self.create_tree()
2380
preview = TransformPreview(revision_tree)
2381
self.addCleanup(preview.finalize)
2382
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2383
preview_tree = preview.get_preview_tree()
2384
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2386
preview_tree.get_file('file2-id').read(), 'content B\n')
2388
def test_diff_preview_tree(self):
2389
revision_tree = self.create_tree()
2390
preview = TransformPreview(revision_tree)
2391
self.addCleanup(preview.finalize)
2392
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2393
preview_tree = preview.get_preview_tree()
2395
show_diff_trees(revision_tree, preview_tree, out)
2396
lines = out.getvalue().splitlines()
2397
self.assertEqual(lines[0], "=== added file 'file2'")
2398
# 3 lines of diff administrivia
2399
self.assertEqual(lines[4], "+content B")
2401
def test_transform_conflicts(self):
2402
revision_tree = self.create_tree()
2403
preview = TransformPreview(revision_tree)
2404
self.addCleanup(preview.finalize)
2405
preview.new_file('a', preview.root, 'content 2')
2406
resolve_conflicts(preview)
2407
trans_id = preview.trans_id_file_id('a-id')
2408
self.assertEqual('a.moved', preview.final_name(trans_id))
2410
def get_tree_and_preview_tree(self):
2411
revision_tree = self.create_tree()
2412
preview = TransformPreview(revision_tree)
2413
self.addCleanup(preview.finalize)
2414
a_trans_id = preview.trans_id_file_id('a-id')
2415
preview.delete_contents(a_trans_id)
2416
preview.create_file('b content', a_trans_id)
2417
preview_tree = preview.get_preview_tree()
2418
return revision_tree, preview_tree
2420
def test_iter_changes(self):
2421
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2422
root = revision_tree.inventory.root.file_id
2423
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2424
(root, root), ('a', 'a'), ('file', 'file'),
2426
list(preview_tree.iter_changes(revision_tree)))
2428
def test_include_unchanged_succeeds(self):
2429
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2430
changes = preview_tree.iter_changes(revision_tree,
2431
include_unchanged=True)
2432
root = revision_tree.inventory.root.file_id
2434
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2436
def test_specific_files(self):
2437
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2438
changes = preview_tree.iter_changes(revision_tree,
2439
specific_files=[''])
2440
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2442
def test_want_unversioned(self):
2443
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2444
changes = preview_tree.iter_changes(revision_tree,
2445
want_unversioned=True)
2446
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2448
def test_ignore_extra_trees_no_specific_files(self):
2449
# extra_trees is harmless without specific_files, so we'll silently
2450
# accept it, even though we won't use it.
2451
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2452
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2454
def test_ignore_require_versioned_no_specific_files(self):
2455
# require_versioned is meaningless without specific_files.
2456
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2457
preview_tree.iter_changes(revision_tree, require_versioned=False)
2459
def test_ignore_pb(self):
2460
# pb could be supported, but TT.iter_changes doesn't support it.
2461
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2462
preview_tree.iter_changes(revision_tree)
2464
def test_kind(self):
2465
revision_tree = self.create_tree()
2466
preview = TransformPreview(revision_tree)
2467
self.addCleanup(preview.finalize)
2468
preview.new_file('file', preview.root, 'contents', 'file-id')
2469
preview.new_directory('directory', preview.root, 'dir-id')
2470
preview_tree = preview.get_preview_tree()
2471
self.assertEqual('file', preview_tree.kind('file-id'))
2472
self.assertEqual('directory', preview_tree.kind('dir-id'))
2474
def test_get_file_mtime(self):
2475
preview = self.get_empty_preview()
2476
file_trans_id = preview.new_file('file', preview.root, 'contents',
2478
limbo_path = preview._limbo_name(file_trans_id)
2479
preview_tree = preview.get_preview_tree()
2480
self.assertEqual(os.stat(limbo_path).st_mtime,
2481
preview_tree.get_file_mtime('file-id'))
2483
def test_get_file_mtime_renamed(self):
2484
work_tree = self.make_branch_and_tree('tree')
2485
self.build_tree(['tree/file'])
2486
work_tree.add('file', 'file-id')
2487
preview = TransformPreview(work_tree)
2488
self.addCleanup(preview.finalize)
2489
file_trans_id = preview.trans_id_tree_file_id('file-id')
2490
preview.adjust_path('renamed', preview.root, file_trans_id)
2491
preview_tree = preview.get_preview_tree()
2492
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2493
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2495
def test_get_file(self):
2496
preview = self.get_empty_preview()
2497
preview.new_file('file', preview.root, 'contents', 'file-id')
2498
preview_tree = preview.get_preview_tree()
2499
tree_file = preview_tree.get_file('file-id')
2501
self.assertEqual('contents', tree_file.read())
2505
def test_get_symlink_target(self):
2506
self.requireFeature(SymlinkFeature)
2507
preview = self.get_empty_preview()
2508
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2509
preview_tree = preview.get_preview_tree()
2510
self.assertEqual('target',
2511
preview_tree.get_symlink_target('symlink-id'))
2513
def test_all_file_ids(self):
2514
tree = self.make_branch_and_tree('tree')
2515
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2516
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2517
preview = TransformPreview(tree)
2518
self.addCleanup(preview.finalize)
2519
preview.unversion_file(preview.trans_id_file_id('b-id'))
2520
c_trans_id = preview.trans_id_file_id('c-id')
2521
preview.unversion_file(c_trans_id)
2522
preview.version_file('c-id', c_trans_id)
2523
preview_tree = preview.get_preview_tree()
2524
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2525
preview_tree.all_file_ids())
2527
def test_path2id_deleted_unchanged(self):
2528
tree = self.make_branch_and_tree('tree')
2529
self.build_tree(['tree/unchanged', 'tree/deleted'])
2530
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2531
preview = TransformPreview(tree)
2532
self.addCleanup(preview.finalize)
2533
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2534
preview_tree = preview.get_preview_tree()
2535
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2536
self.assertIs(None, preview_tree.path2id('deleted'))
2538
def test_path2id_created(self):
2539
tree = self.make_branch_and_tree('tree')
2540
self.build_tree(['tree/unchanged'])
2541
tree.add(['unchanged'], ['unchanged-id'])
2542
preview = TransformPreview(tree)
2543
self.addCleanup(preview.finalize)
2544
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2545
'contents', 'new-id')
2546
preview_tree = preview.get_preview_tree()
2547
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2549
def test_path2id_moved(self):
2550
tree = self.make_branch_and_tree('tree')
2551
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2552
tree.add(['old_parent', 'old_parent/child'],
2553
['old_parent-id', 'child-id'])
2554
preview = TransformPreview(tree)
2555
self.addCleanup(preview.finalize)
2556
new_parent = preview.new_directory('new_parent', preview.root,
2558
preview.adjust_path('child', new_parent,
2559
preview.trans_id_file_id('child-id'))
2560
preview_tree = preview.get_preview_tree()
2561
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2562
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2564
def test_path2id_renamed_parent(self):
2565
tree = self.make_branch_and_tree('tree')
2566
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2567
tree.add(['old_name', 'old_name/child'],
2568
['parent-id', 'child-id'])
2569
preview = TransformPreview(tree)
2570
self.addCleanup(preview.finalize)
2571
preview.adjust_path('new_name', preview.root,
2572
preview.trans_id_file_id('parent-id'))
2573
preview_tree = preview.get_preview_tree()
2574
self.assertIs(None, preview_tree.path2id('old_name/child'))
2575
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2577
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2578
preview_tree = tt.get_preview_tree()
2579
preview_result = list(preview_tree.iter_entries_by_dir(
2583
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2584
self.assertEqual(actual_result, preview_result)
2586
def test_iter_entries_by_dir_new(self):
2587
tree = self.make_branch_and_tree('tree')
2588
tt = TreeTransform(tree)
2589
tt.new_file('new', tt.root, 'contents', 'new-id')
2590
self.assertMatchingIterEntries(tt)
2592
def test_iter_entries_by_dir_deleted(self):
2593
tree = self.make_branch_and_tree('tree')
2594
self.build_tree(['tree/deleted'])
2595
tree.add('deleted', 'deleted-id')
2596
tt = TreeTransform(tree)
2597
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2598
self.assertMatchingIterEntries(tt)
2600
def test_iter_entries_by_dir_unversioned(self):
2601
tree = self.make_branch_and_tree('tree')
2602
self.build_tree(['tree/removed'])
2603
tree.add('removed', 'removed-id')
2604
tt = TreeTransform(tree)
2605
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2606
self.assertMatchingIterEntries(tt)
2608
def test_iter_entries_by_dir_moved(self):
2609
tree = self.make_branch_and_tree('tree')
2610
self.build_tree(['tree/moved', 'tree/new_parent/'])
2611
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2612
tt = TreeTransform(tree)
2613
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2614
tt.trans_id_file_id('moved-id'))
2615
self.assertMatchingIterEntries(tt)
2617
def test_iter_entries_by_dir_specific_file_ids(self):
2618
tree = self.make_branch_and_tree('tree')
2619
tree.set_root_id('tree-root-id')
2620
self.build_tree(['tree/parent/', 'tree/parent/child'])
2621
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2622
tt = TreeTransform(tree)
2623
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2625
def test_symlink_content_summary(self):
2626
self.requireFeature(SymlinkFeature)
2627
preview = self.get_empty_preview()
2628
preview.new_symlink('path', preview.root, 'target', 'path-id')
2629
summary = preview.get_preview_tree().path_content_summary('path')
2630
self.assertEqual(('symlink', None, None, 'target'), summary)
2632
def test_missing_content_summary(self):
2633
preview = self.get_empty_preview()
2634
summary = preview.get_preview_tree().path_content_summary('path')
2635
self.assertEqual(('missing', None, None, None), summary)
2637
def test_deleted_content_summary(self):
2638
tree = self.make_branch_and_tree('tree')
2639
self.build_tree(['tree/path/'])
2641
preview = TransformPreview(tree)
2642
self.addCleanup(preview.finalize)
2643
preview.delete_contents(preview.trans_id_tree_path('path'))
2644
summary = preview.get_preview_tree().path_content_summary('path')
2645
self.assertEqual(('missing', None, None, None), summary)
2647
def test_file_content_summary_executable(self):
2648
preview = self.get_empty_preview()
2649
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2650
preview.set_executability(True, path_id)
2651
summary = preview.get_preview_tree().path_content_summary('path')
2652
self.assertEqual(4, len(summary))
2653
self.assertEqual('file', summary[0])
2654
# size must be known
2655
self.assertEqual(len('contents'), summary[1])
2657
self.assertEqual(True, summary[2])
2658
# will not have hash (not cheap to determine)
2659
self.assertIs(None, summary[3])
2661
def test_change_executability(self):
2662
tree = self.make_branch_and_tree('tree')
2663
self.build_tree(['tree/path'])
2665
preview = TransformPreview(tree)
2666
self.addCleanup(preview.finalize)
2667
path_id = preview.trans_id_tree_path('path')
2668
preview.set_executability(True, path_id)
2669
summary = preview.get_preview_tree().path_content_summary('path')
2670
self.assertEqual(True, summary[2])
2672
def test_file_content_summary_non_exec(self):
2673
preview = self.get_empty_preview()
2674
preview.new_file('path', preview.root, 'contents', 'path-id')
2675
summary = preview.get_preview_tree().path_content_summary('path')
2676
self.assertEqual(4, len(summary))
2677
self.assertEqual('file', summary[0])
2678
# size must be known
2679
self.assertEqual(len('contents'), summary[1])
2681
self.assertEqual(False, summary[2])
2682
# will not have hash (not cheap to determine)
2683
self.assertIs(None, summary[3])
2685
def test_dir_content_summary(self):
2686
preview = self.get_empty_preview()
2687
preview.new_directory('path', preview.root, 'path-id')
2688
summary = preview.get_preview_tree().path_content_summary('path')
2689
self.assertEqual(('directory', None, None, None), summary)
2691
def test_tree_content_summary(self):
2692
preview = self.get_empty_preview()
2693
path = preview.new_directory('path', preview.root, 'path-id')
2694
preview.set_tree_reference('rev-1', path)
2695
summary = preview.get_preview_tree().path_content_summary('path')
2696
self.assertEqual(4, len(summary))
2697
self.assertEqual('tree-reference', summary[0])
2699
def test_annotate(self):
2700
tree = self.make_branch_and_tree('tree')
2701
self.build_tree_contents([('tree/file', 'a\n')])
2702
tree.add('file', 'file-id')
2703
tree.commit('a', rev_id='one')
2704
self.build_tree_contents([('tree/file', 'a\nb\n')])
2705
preview = TransformPreview(tree)
2706
self.addCleanup(preview.finalize)
2707
file_trans_id = preview.trans_id_file_id('file-id')
2708
preview.delete_contents(file_trans_id)
2709
preview.create_file('a\nb\nc\n', file_trans_id)
2710
preview_tree = preview.get_preview_tree()
2716
annotation = preview_tree.annotate_iter('file-id', 'me:')
2717
self.assertEqual(expected, annotation)
2719
def test_annotate_missing(self):
2720
preview = self.get_empty_preview()
2721
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2722
preview_tree = preview.get_preview_tree()
2728
annotation = preview_tree.annotate_iter('file-id', 'me:')
2729
self.assertEqual(expected, annotation)
2731
def test_annotate_rename(self):
2732
tree = self.make_branch_and_tree('tree')
2733
self.build_tree_contents([('tree/file', 'a\n')])
2734
tree.add('file', 'file-id')
2735
tree.commit('a', rev_id='one')
2736
preview = TransformPreview(tree)
2737
self.addCleanup(preview.finalize)
2738
file_trans_id = preview.trans_id_file_id('file-id')
2739
preview.adjust_path('newname', preview.root, file_trans_id)
2740
preview_tree = preview.get_preview_tree()
2744
annotation = preview_tree.annotate_iter('file-id', 'me:')
2745
self.assertEqual(expected, annotation)
2747
def test_annotate_deleted(self):
2748
tree = self.make_branch_and_tree('tree')
2749
self.build_tree_contents([('tree/file', 'a\n')])
2750
tree.add('file', 'file-id')
2751
tree.commit('a', rev_id='one')
2752
self.build_tree_contents([('tree/file', 'a\nb\n')])
2753
preview = TransformPreview(tree)
2754
self.addCleanup(preview.finalize)
2755
file_trans_id = preview.trans_id_file_id('file-id')
2756
preview.delete_contents(file_trans_id)
2757
preview_tree = preview.get_preview_tree()
2758
annotation = preview_tree.annotate_iter('file-id', 'me:')
2759
self.assertIs(None, annotation)
2761
def test_stored_kind(self):
2762
preview = self.get_empty_preview()
2763
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2764
preview_tree = preview.get_preview_tree()
2765
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2767
def test_is_executable(self):
2768
preview = self.get_empty_preview()
2769
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2770
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2771
preview_tree = preview.get_preview_tree()
2772
self.assertEqual(True, preview_tree.is_executable('file-id'))
2774
def test_get_set_parent_ids(self):
2775
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2776
self.assertEqual([], preview_tree.get_parent_ids())
2777
preview_tree.set_parent_ids(['rev-1'])
2778
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2780
def test_plan_file_merge(self):
2781
work_a = self.make_branch_and_tree('wta')
2782
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2783
work_a.add('file', 'file-id')
2784
base_id = work_a.commit('base version')
2785
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2786
preview = TransformPreview(work_a)
2787
self.addCleanup(preview.finalize)
2788
trans_id = preview.trans_id_file_id('file-id')
2789
preview.delete_contents(trans_id)
2790
preview.create_file('b\nc\nd\ne\n', trans_id)
2791
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2792
tree_a = preview.get_preview_tree()
2793
tree_a.set_parent_ids([base_id])
2795
('killed-a', 'a\n'),
2796
('killed-b', 'b\n'),
2797
('unchanged', 'c\n'),
2798
('unchanged', 'd\n'),
2801
], list(tree_a.plan_file_merge('file-id', tree_b)))
2803
def test_plan_file_merge_revision_tree(self):
2804
work_a = self.make_branch_and_tree('wta')
2805
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2806
work_a.add('file', 'file-id')
2807
base_id = work_a.commit('base version')
2808
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2809
preview = TransformPreview(work_a.basis_tree())
2810
self.addCleanup(preview.finalize)
2811
trans_id = preview.trans_id_file_id('file-id')
2812
preview.delete_contents(trans_id)
2813
preview.create_file('b\nc\nd\ne\n', trans_id)
2814
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2815
tree_a = preview.get_preview_tree()
2816
tree_a.set_parent_ids([base_id])
2818
('killed-a', 'a\n'),
2819
('killed-b', 'b\n'),
2820
('unchanged', 'c\n'),
2821
('unchanged', 'd\n'),
2824
], list(tree_a.plan_file_merge('file-id', tree_b)))
2826
def test_walkdirs(self):
2827
preview = self.get_empty_preview()
2828
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2829
# FIXME: new_directory should mark root.
2830
preview.fixup_new_roots()
2831
preview_tree = preview.get_preview_tree()
2832
file_trans_id = preview.new_file('a', preview.root, 'contents',
2834
expected = [(('', 'tree-root'),
2835
[('a', 'a', 'file', None, 'a-id', 'file')])]
2836
self.assertEqual(expected, list(preview_tree.walkdirs()))
2838
def test_extras(self):
2839
work_tree = self.make_branch_and_tree('tree')
2840
self.build_tree(['tree/removed-file', 'tree/existing-file',
2841
'tree/not-removed-file'])
2842
work_tree.add(['removed-file', 'not-removed-file'])
2843
preview = TransformPreview(work_tree)
2844
self.addCleanup(preview.finalize)
2845
preview.new_file('new-file', preview.root, 'contents')
2846
preview.new_file('new-versioned-file', preview.root, 'contents',
2848
tree = preview.get_preview_tree()
2849
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2850
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2853
def test_merge_into_preview(self):
2854
work_tree = self.make_branch_and_tree('tree')
2855
self.build_tree_contents([('tree/file','b\n')])
2856
work_tree.add('file', 'file-id')
2857
work_tree.commit('first commit')
2858
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2859
self.build_tree_contents([('child/file','b\nc\n')])
2860
child_tree.commit('child commit')
2861
child_tree.lock_write()
2862
self.addCleanup(child_tree.unlock)
2863
work_tree.lock_write()
2864
self.addCleanup(work_tree.unlock)
2865
preview = TransformPreview(work_tree)
2866
self.addCleanup(preview.finalize)
2867
file_trans_id = preview.trans_id_file_id('file-id')
2868
preview.delete_contents(file_trans_id)
2869
preview.create_file('a\nb\n', file_trans_id)
2870
preview_tree = preview.get_preview_tree()
2871
merger = Merger.from_revision_ids(None, preview_tree,
2872
child_tree.branch.last_revision(),
2873
other_branch=child_tree.branch,
2874
tree_branch=work_tree.branch)
2875
merger.merge_type = Merge3Merger
2876
tt = merger.make_merger().make_preview_transform()
2877
self.addCleanup(tt.finalize)
2878
final_tree = tt.get_preview_tree()
2879
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2881
def test_merge_preview_into_workingtree(self):
2882
tree = self.make_branch_and_tree('tree')
2883
tree.set_root_id('TREE_ROOT')
2884
tt = TransformPreview(tree)
2885
self.addCleanup(tt.finalize)
2886
tt.new_file('name', tt.root, 'content', 'file-id')
2887
tree2 = self.make_branch_and_tree('tree2')
2888
tree2.set_root_id('TREE_ROOT')
2889
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2890
None, tree.basis_tree())
2891
merger.merge_type = Merge3Merger
2894
def test_merge_preview_into_workingtree_handles_conflicts(self):
2895
tree = self.make_branch_and_tree('tree')
2896
self.build_tree_contents([('tree/foo', 'bar')])
2897
tree.add('foo', 'foo-id')
2899
tt = TransformPreview(tree)
2900
self.addCleanup(tt.finalize)
2901
trans_id = tt.trans_id_file_id('foo-id')
2902
tt.delete_contents(trans_id)
2903
tt.create_file('baz', trans_id)
2904
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2905
self.build_tree_contents([('tree2/foo', 'qux')])
2907
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2908
pb, tree.basis_tree())
2909
merger.merge_type = Merge3Merger
2912
def test_is_executable(self):
2913
tree = self.make_branch_and_tree('tree')
2914
preview = TransformPreview(tree)
2915
self.addCleanup(preview.finalize)
2916
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2917
preview_tree = preview.get_preview_tree()
2918
self.assertEqual(False, preview_tree.is_executable('baz-id',
2920
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2922
def test_commit_preview_tree(self):
2923
tree = self.make_branch_and_tree('tree')
2924
rev_id = tree.commit('rev1')
2925
tree.branch.lock_write()
2926
self.addCleanup(tree.branch.unlock)
2927
tt = TransformPreview(tree)
2928
tt.new_file('file', tt.root, 'contents', 'file_id')
2929
self.addCleanup(tt.finalize)
2930
preview = tt.get_preview_tree()
2931
preview.set_parent_ids([rev_id])
2932
builder = tree.branch.get_commit_builder([rev_id])
2933
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2934
builder.finish_inventory()
2935
rev2_id = builder.commit('rev2')
2936
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2937
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2939
def test_ascii_limbo_paths(self):
2940
self.requireFeature(tests.UnicodeFilenameFeature)
2941
branch = self.make_branch('any')
2942
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2943
tt = TransformPreview(tree)
2944
self.addCleanup(tt.finalize)
2945
foo_id = tt.new_directory('', ROOT_PARENT)
2946
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2947
limbo_path = tt._limbo_name(bar_id)
2948
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2951
class FakeSerializer(object):
2952
"""Serializer implementation that simply returns the input.
2954
The input is returned in the order used by pack.ContainerPushParser.
2957
def bytes_record(bytes, names):
2961
class TestSerializeTransform(tests.TestCaseWithTransport):
2963
_test_needs_features = [tests.UnicodeFilenameFeature]
2965
def get_preview(self, tree=None):
2967
tree = self.make_branch_and_tree('tree')
2968
tt = TransformPreview(tree)
2969
self.addCleanup(tt.finalize)
2972
def assertSerializesTo(self, expected, tt):
2973
records = list(tt.serialize(FakeSerializer()))
2974
self.assertEqual(expected, records)
2977
def default_attribs():
2982
'_new_executability': {},
2984
'_tree_path_ids': {'': 'new-0'},
2986
'_removed_contents': [],
2987
'_non_present_ids': {},
2990
def make_records(self, attribs, contents):
2992
(((('attribs'),),), bencode.bencode(attribs))]
2993
records.extend([(((n, k),), c) for n, k, c in contents])
2996
def creation_records(self):
2997
attribs = self.default_attribs()
2998
attribs['_id_number'] = 3
2999
attribs['_new_name'] = {
3000
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3001
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3002
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3003
attribs['_new_executability'] = {'new-1': 1}
3005
('new-1', 'file', 'i 1\nbar\n'),
3006
('new-2', 'directory', ''),
3008
return self.make_records(attribs, contents)
3010
def test_serialize_creation(self):
3011
tt = self.get_preview()
3012
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3013
tt.new_directory('qux', tt.root, 'quxx')
3014
self.assertSerializesTo(self.creation_records(), tt)
3016
def test_deserialize_creation(self):
3017
tt = self.get_preview()
3018
tt.deserialize(iter(self.creation_records()))
3019
self.assertEqual(3, tt._id_number)
3020
self.assertEqual({'new-1': u'foo\u1234',
3021
'new-2': 'qux'}, tt._new_name)
3022
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3023
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3024
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3025
self.assertEqual({'new-1': True}, tt._new_executability)
3026
self.assertEqual({'new-1': 'file',
3027
'new-2': 'directory'}, tt._new_contents)
3028
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3030
foo_content = foo_limbo.read()
3033
self.assertEqual('bar', foo_content)
3035
def symlink_creation_records(self):
3036
attribs = self.default_attribs()
3037
attribs['_id_number'] = 2
3038
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3039
attribs['_new_parent'] = {'new-1': 'new-0'}
3040
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3041
return self.make_records(attribs, contents)
3043
def test_serialize_symlink_creation(self):
3044
self.requireFeature(tests.SymlinkFeature)
3045
tt = self.get_preview()
3046
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3047
self.assertSerializesTo(self.symlink_creation_records(), tt)
3049
def test_deserialize_symlink_creation(self):
3050
self.requireFeature(tests.SymlinkFeature)
3051
tt = self.get_preview()
3052
tt.deserialize(iter(self.symlink_creation_records()))
3053
abspath = tt._limbo_name('new-1')
3054
foo_content = osutils.readlink(abspath)
3055
self.assertEqual(u'bar\u1234', foo_content)
3057
def make_destruction_preview(self):
3058
tree = self.make_branch_and_tree('.')
3059
self.build_tree([u'foo\u1234', 'bar'])
3060
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3061
return self.get_preview(tree)
3063
def destruction_records(self):
3064
attribs = self.default_attribs()
3065
attribs['_id_number'] = 3
3066
attribs['_removed_id'] = ['new-1']
3067
attribs['_removed_contents'] = ['new-2']
3068
attribs['_tree_path_ids'] = {
3070
u'foo\u1234'.encode('utf-8'): 'new-1',
3073
return self.make_records(attribs, [])
3075
def test_serialize_destruction(self):
3076
tt = self.make_destruction_preview()
3077
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3078
tt.unversion_file(foo_trans_id)
3079
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3080
tt.delete_contents(bar_trans_id)
3081
self.assertSerializesTo(self.destruction_records(), tt)
3083
def test_deserialize_destruction(self):
3084
tt = self.make_destruction_preview()
3085
tt.deserialize(iter(self.destruction_records()))
3086
self.assertEqual({u'foo\u1234': 'new-1',
3088
'': tt.root}, tt._tree_path_ids)
3089
self.assertEqual({'new-1': u'foo\u1234',
3091
tt.root: ''}, tt._tree_id_paths)
3092
self.assertEqual(set(['new-1']), tt._removed_id)
3093
self.assertEqual(set(['new-2']), tt._removed_contents)
3095
def missing_records(self):
3096
attribs = self.default_attribs()
3097
attribs['_id_number'] = 2
3098
attribs['_non_present_ids'] = {
3100
return self.make_records(attribs, [])
3102
def test_serialize_missing(self):
3103
tt = self.get_preview()
3104
boo_trans_id = tt.trans_id_file_id('boo')
3105
self.assertSerializesTo(self.missing_records(), tt)
3107
def test_deserialize_missing(self):
3108
tt = self.get_preview()
3109
tt.deserialize(iter(self.missing_records()))
3110
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3112
def make_modification_preview(self):
3113
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3114
LINES_TWO = 'z\nbb\nx\ndd\n'
3115
tree = self.make_branch_and_tree('tree')
3116
self.build_tree_contents([('tree/file', LINES_ONE)])
3117
tree.add('file', 'file-id')
3118
return self.get_preview(tree), LINES_TWO
3120
def modification_records(self):
3121
attribs = self.default_attribs()
3122
attribs['_id_number'] = 2
3123
attribs['_tree_path_ids'] = {
3126
attribs['_removed_contents'] = ['new-1']
3127
contents = [('new-1', 'file',
3128
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3129
return self.make_records(attribs, contents)
3131
def test_serialize_modification(self):
3132
tt, LINES = self.make_modification_preview()
3133
trans_id = tt.trans_id_file_id('file-id')
3134
tt.delete_contents(trans_id)
3135
tt.create_file(LINES, trans_id)
3136
self.assertSerializesTo(self.modification_records(), tt)
3138
def test_deserialize_modification(self):
3139
tt, LINES = self.make_modification_preview()
3140
tt.deserialize(iter(self.modification_records()))
3141
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3143
def make_kind_change_preview(self):
3144
LINES = 'a\nb\nc\nd\n'
3145
tree = self.make_branch_and_tree('tree')
3146
self.build_tree(['tree/foo/'])
3147
tree.add('foo', 'foo-id')
3148
return self.get_preview(tree), LINES
3150
def kind_change_records(self):
3151
attribs = self.default_attribs()
3152
attribs['_id_number'] = 2
3153
attribs['_tree_path_ids'] = {
3156
attribs['_removed_contents'] = ['new-1']
3157
contents = [('new-1', 'file',
3158
'i 4\na\nb\nc\nd\n\n')]
3159
return self.make_records(attribs, contents)
3161
def test_serialize_kind_change(self):
3162
tt, LINES = self.make_kind_change_preview()
3163
trans_id = tt.trans_id_file_id('foo-id')
3164
tt.delete_contents(trans_id)
3165
tt.create_file(LINES, trans_id)
3166
self.assertSerializesTo(self.kind_change_records(), tt)
3168
def test_deserialize_kind_change(self):
3169
tt, LINES = self.make_kind_change_preview()
3170
tt.deserialize(iter(self.kind_change_records()))
3171
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3173
def make_add_contents_preview(self):
3174
LINES = 'a\nb\nc\nd\n'
3175
tree = self.make_branch_and_tree('tree')
3176
self.build_tree(['tree/foo'])
3178
os.unlink('tree/foo')
3179
return self.get_preview(tree), LINES
3181
def add_contents_records(self):
3182
attribs = self.default_attribs()
3183
attribs['_id_number'] = 2
3184
attribs['_tree_path_ids'] = {
3187
contents = [('new-1', 'file',
3188
'i 4\na\nb\nc\nd\n\n')]
3189
return self.make_records(attribs, contents)
3191
def test_serialize_add_contents(self):
3192
tt, LINES = self.make_add_contents_preview()
3193
trans_id = tt.trans_id_tree_path('foo')
3194
tt.create_file(LINES, trans_id)
3195
self.assertSerializesTo(self.add_contents_records(), tt)
3197
def test_deserialize_add_contents(self):
3198
tt, LINES = self.make_add_contents_preview()
3199
tt.deserialize(iter(self.add_contents_records()))
3200
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3202
def test_get_parents_lines(self):
3203
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3204
LINES_TWO = 'z\nbb\nx\ndd\n'
3205
tree = self.make_branch_and_tree('tree')
3206
self.build_tree_contents([('tree/file', LINES_ONE)])
3207
tree.add('file', 'file-id')
3208
tt = self.get_preview(tree)
3209
trans_id = tt.trans_id_tree_path('file')
3210
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3211
tt._get_parents_lines(trans_id))
3213
def test_get_parents_texts(self):
3214
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3215
LINES_TWO = 'z\nbb\nx\ndd\n'
3216
tree = self.make_branch_and_tree('tree')
3217
self.build_tree_contents([('tree/file', LINES_ONE)])
3218
tree.add('file', 'file-id')
3219
tt = self.get_preview(tree)
3220
trans_id = tt.trans_id_tree_path('file')
3221
self.assertEqual((LINES_ONE,),
3222
tt._get_parents_texts(trans_id))