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 (
67
from bzrlib.transform import (
82
class TestTreeTransform(tests.TestCaseWithTransport):
85
super(TestTreeTransform, self).setUp()
86
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
89
def get_transform(self):
90
transform = TreeTransform(self.wt)
91
self.addCleanup(transform.finalize)
92
return transform, transform.root
94
def test_existing_limbo(self):
95
transform, root = self.get_transform()
96
limbo_name = transform._limbodir
97
deletion_path = transform._deletiondir
98
os.mkdir(pathjoin(limbo_name, 'hehe'))
99
self.assertRaises(ImmortalLimbo, transform.apply)
100
self.assertRaises(LockError, self.wt.unlock)
101
self.assertRaises(ExistingLimbo, self.get_transform)
102
self.assertRaises(LockError, self.wt.unlock)
103
os.rmdir(pathjoin(limbo_name, 'hehe'))
105
os.rmdir(deletion_path)
106
transform, root = self.get_transform()
109
def test_existing_pending_deletion(self):
110
transform, root = self.get_transform()
111
deletion_path = self._limbodir = urlutils.local_path_from_url(
112
transform._tree._transport.abspath('pending-deletion'))
113
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
114
self.assertRaises(ImmortalPendingDeletion, transform.apply)
115
self.assertRaises(LockError, self.wt.unlock)
116
self.assertRaises(ExistingPendingDeletion, self.get_transform)
118
def test_build(self):
119
transform, root = self.get_transform()
120
self.wt.lock_tree_write()
121
self.addCleanup(self.wt.unlock)
122
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
123
imaginary_id = transform.trans_id_tree_path('imaginary')
124
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
125
self.assertEqual(imaginary_id, imaginary_id2)
126
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
127
self.assertEqual(transform.final_kind(root), 'directory')
128
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
129
trans_id = transform.create_path('name', root)
130
self.assertIs(transform.final_file_id(trans_id), None)
131
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
132
transform.create_file('contents', trans_id)
133
transform.set_executability(True, trans_id)
134
transform.version_file('my_pretties', trans_id)
135
self.assertRaises(DuplicateKey, transform.version_file,
136
'my_pretties', trans_id)
137
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
138
self.assertEqual(transform.final_parent(trans_id), root)
139
self.assertIs(transform.final_parent(root), ROOT_PARENT)
140
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
141
oz_id = transform.create_path('oz', root)
142
transform.create_directory(oz_id)
143
transform.version_file('ozzie', oz_id)
144
trans_id2 = transform.create_path('name2', root)
145
transform.create_file('contents', trans_id2)
146
transform.set_executability(False, trans_id2)
147
transform.version_file('my_pretties2', trans_id2)
148
modified_paths = transform.apply().modified_paths
149
self.assertEqual('contents', self.wt.get_file_byname('name').read())
150
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
151
self.assertIs(self.wt.is_executable('my_pretties'), True)
152
self.assertIs(self.wt.is_executable('my_pretties2'), False)
153
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
154
self.assertEqual(len(modified_paths), 3)
155
tree_mod_paths = [self.wt.id2abspath(f) for f in
156
('ozzie', 'my_pretties', 'my_pretties2')]
157
self.assertSubset(tree_mod_paths, modified_paths)
158
# is it safe to finalize repeatedly?
162
def test_create_files_same_timestamp(self):
163
transform, root = self.get_transform()
164
self.wt.lock_tree_write()
165
self.addCleanup(self.wt.unlock)
166
# Roll back the clock, so that we know everything is being set to the
168
transform._creation_mtime = creation_mtime = time.time() - 20.0
169
transform.create_file('content-one',
170
transform.create_path('one', root))
171
time.sleep(1) # *ugly*
172
transform.create_file('content-two',
173
transform.create_path('two', root))
175
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
177
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
179
# We only guarantee 2s resolution
180
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
181
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
182
# But if we have more than that, all files should get the same result
183
self.assertEqual(st1.st_mtime, st2.st_mtime)
185
def test_change_root_id(self):
186
transform, root = self.get_transform()
187
self.assertNotEqual('new-root-id', self.wt.get_root_id())
188
transform.new_directory('', ROOT_PARENT, 'new-root-id')
189
transform.delete_contents(root)
190
transform.unversion_file(root)
191
transform.fixup_new_roots()
193
self.assertEqual('new-root-id', self.wt.get_root_id())
195
def test_change_root_id_add_files(self):
196
transform, root = self.get_transform()
197
self.assertNotEqual('new-root-id', self.wt.get_root_id())
198
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
199
transform.new_file('file', new_trans_id, ['new-contents\n'],
201
transform.delete_contents(root)
202
transform.unversion_file(root)
203
transform.fixup_new_roots()
205
self.assertEqual('new-root-id', self.wt.get_root_id())
206
self.assertEqual('new-file-id', self.wt.path2id('file'))
207
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
209
def test_add_two_roots(self):
210
transform, root = self.get_transform()
211
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
212
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
213
self.assertRaises(ValueError, transform.fixup_new_roots)
215
def test_hardlink(self):
216
self.requireFeature(HardlinkFeature)
217
transform, root = self.get_transform()
218
transform.new_file('file1', root, 'contents')
220
target = self.make_branch_and_tree('target')
221
target_transform = TreeTransform(target)
222
trans_id = target_transform.create_path('file1', target_transform.root)
223
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
224
target_transform.apply()
225
self.failUnlessExists('target/file1')
226
source_stat = os.stat(self.wt.abspath('file1'))
227
target_stat = os.stat('target/file1')
228
self.assertEqual(source_stat, target_stat)
230
def test_convenience(self):
231
transform, root = self.get_transform()
232
self.wt.lock_tree_write()
233
self.addCleanup(self.wt.unlock)
234
trans_id = transform.new_file('name', root, 'contents',
236
oz = transform.new_directory('oz', root, 'oz-id')
237
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
238
toto = transform.new_file('toto', dorothy, 'toto-contents',
241
self.assertEqual(len(transform.find_conflicts()), 0)
243
self.assertRaises(ReusingTransform, transform.find_conflicts)
244
self.assertEqual('contents', file(self.wt.abspath('name')).read())
245
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
246
self.assertIs(self.wt.is_executable('my_pretties'), True)
247
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
248
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
249
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
251
self.assertEqual('toto-contents',
252
self.wt.get_file_byname('oz/dorothy/toto').read())
253
self.assertIs(self.wt.is_executable('toto-id'), False)
255
def test_tree_reference(self):
256
transform, root = self.get_transform()
257
tree = transform._tree
258
trans_id = transform.new_directory('reference', root, 'subtree-id')
259
transform.set_tree_reference('subtree-revision', trans_id)
262
self.addCleanup(tree.unlock)
263
self.assertEqual('subtree-revision',
264
tree.inventory['subtree-id'].reference_revision)
266
def test_conflicts(self):
267
transform, root = self.get_transform()
268
trans_id = transform.new_file('name', root, 'contents',
270
self.assertEqual(len(transform.find_conflicts()), 0)
271
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
272
self.assertEqual(transform.find_conflicts(),
273
[('duplicate', trans_id, trans_id2, 'name')])
274
self.assertRaises(MalformedTransform, transform.apply)
275
transform.adjust_path('name', trans_id, trans_id2)
276
self.assertEqual(transform.find_conflicts(),
277
[('non-directory parent', trans_id)])
278
tinman_id = transform.trans_id_tree_path('tinman')
279
transform.adjust_path('name', tinman_id, trans_id2)
280
self.assertEqual(transform.find_conflicts(),
281
[('unversioned parent', tinman_id),
282
('missing parent', tinman_id)])
283
lion_id = transform.create_path('lion', root)
284
self.assertEqual(transform.find_conflicts(),
285
[('unversioned parent', tinman_id),
286
('missing parent', tinman_id)])
287
transform.adjust_path('name', lion_id, trans_id2)
288
self.assertEqual(transform.find_conflicts(),
289
[('unversioned parent', lion_id),
290
('missing parent', lion_id)])
291
transform.version_file("Courage", lion_id)
292
self.assertEqual(transform.find_conflicts(),
293
[('missing parent', lion_id),
294
('versioning no contents', lion_id)])
295
transform.adjust_path('name2', root, trans_id2)
296
self.assertEqual(transform.find_conflicts(),
297
[('versioning no contents', lion_id)])
298
transform.create_file('Contents, okay?', lion_id)
299
transform.adjust_path('name2', trans_id2, trans_id2)
300
self.assertEqual(transform.find_conflicts(),
301
[('parent loop', trans_id2),
302
('non-directory parent', trans_id2)])
303
transform.adjust_path('name2', root, trans_id2)
304
oz_id = transform.new_directory('oz', root)
305
transform.set_executability(True, oz_id)
306
self.assertEqual(transform.find_conflicts(),
307
[('unversioned executability', oz_id)])
308
transform.version_file('oz-id', oz_id)
309
self.assertEqual(transform.find_conflicts(),
310
[('non-file executability', oz_id)])
311
transform.set_executability(None, oz_id)
312
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
314
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
315
self.assertEqual('contents', file(self.wt.abspath('name')).read())
316
transform2, root = self.get_transform()
317
oz_id = transform2.trans_id_tree_file_id('oz-id')
318
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
319
result = transform2.find_conflicts()
320
fp = FinalPaths(transform2)
321
self.assert_('oz/tip' in transform2._tree_path_ids)
322
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
323
self.assertEqual(len(result), 2)
324
self.assertEqual((result[0][0], result[0][1]),
325
('duplicate', newtip))
326
self.assertEqual((result[1][0], result[1][2]),
327
('duplicate id', newtip))
328
transform2.finalize()
329
transform3 = TreeTransform(self.wt)
330
self.addCleanup(transform3.finalize)
331
oz_id = transform3.trans_id_tree_file_id('oz-id')
332
transform3.delete_contents(oz_id)
333
self.assertEqual(transform3.find_conflicts(),
334
[('missing parent', oz_id)])
335
root_id = transform3.root
336
tip_id = transform3.trans_id_tree_file_id('tip-id')
337
transform3.adjust_path('tip', root_id, tip_id)
340
def test_conflict_on_case_insensitive(self):
341
tree = self.make_branch_and_tree('tree')
342
# Don't try this at home, kids!
343
# Force the tree to report that it is case sensitive, for conflict
345
tree.case_sensitive = True
346
transform = TreeTransform(tree)
347
self.addCleanup(transform.finalize)
348
transform.new_file('file', transform.root, 'content')
349
transform.new_file('FiLe', transform.root, 'content')
350
result = transform.find_conflicts()
351
self.assertEqual([], result)
353
# Force the tree to report that it is case insensitive, for conflict
355
tree.case_sensitive = False
356
transform = TreeTransform(tree)
357
self.addCleanup(transform.finalize)
358
transform.new_file('file', transform.root, 'content')
359
transform.new_file('FiLe', transform.root, 'content')
360
result = transform.find_conflicts()
361
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
363
def test_conflict_on_case_insensitive_existing(self):
364
tree = self.make_branch_and_tree('tree')
365
self.build_tree(['tree/FiLe'])
366
# Don't try this at home, kids!
367
# Force the tree to report that it is case sensitive, for conflict
369
tree.case_sensitive = True
370
transform = TreeTransform(tree)
371
self.addCleanup(transform.finalize)
372
transform.new_file('file', transform.root, 'content')
373
result = transform.find_conflicts()
374
self.assertEqual([], result)
376
# Force the tree to report that it is case insensitive, for conflict
378
tree.case_sensitive = False
379
transform = TreeTransform(tree)
380
self.addCleanup(transform.finalize)
381
transform.new_file('file', transform.root, 'content')
382
result = transform.find_conflicts()
383
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
385
def test_resolve_case_insensitive_conflict(self):
386
tree = self.make_branch_and_tree('tree')
387
# Don't try this at home, kids!
388
# Force the tree to report that it is case insensitive, for conflict
390
tree.case_sensitive = False
391
transform = TreeTransform(tree)
392
self.addCleanup(transform.finalize)
393
transform.new_file('file', transform.root, 'content')
394
transform.new_file('FiLe', transform.root, 'content')
395
resolve_conflicts(transform)
397
self.failUnlessExists('tree/file')
398
self.failUnlessExists('tree/FiLe.moved')
400
def test_resolve_checkout_case_conflict(self):
401
tree = self.make_branch_and_tree('tree')
402
# Don't try this at home, kids!
403
# Force the tree to report that it is case insensitive, for conflict
405
tree.case_sensitive = False
406
transform = TreeTransform(tree)
407
self.addCleanup(transform.finalize)
408
transform.new_file('file', transform.root, 'content')
409
transform.new_file('FiLe', transform.root, 'content')
410
resolve_conflicts(transform,
411
pass_func=lambda t, c: resolve_checkout(t, c, []))
413
self.failUnlessExists('tree/file')
414
self.failUnlessExists('tree/FiLe.moved')
416
def test_apply_case_conflict(self):
417
"""Ensure that a transform with case conflicts can always be applied"""
418
tree = self.make_branch_and_tree('tree')
419
transform = TreeTransform(tree)
420
self.addCleanup(transform.finalize)
421
transform.new_file('file', transform.root, 'content')
422
transform.new_file('FiLe', transform.root, 'content')
423
dir = transform.new_directory('dir', transform.root)
424
transform.new_file('dirfile', dir, 'content')
425
transform.new_file('dirFiLe', dir, 'content')
426
resolve_conflicts(transform)
428
self.failUnlessExists('tree/file')
429
if not os.path.exists('tree/FiLe.moved'):
430
self.failUnlessExists('tree/FiLe')
431
self.failUnlessExists('tree/dir/dirfile')
432
if not os.path.exists('tree/dir/dirFiLe.moved'):
433
self.failUnlessExists('tree/dir/dirFiLe')
435
def test_case_insensitive_limbo(self):
436
tree = self.make_branch_and_tree('tree')
437
# Don't try this at home, kids!
438
# Force the tree to report that it is case insensitive
439
tree.case_sensitive = False
440
transform = TreeTransform(tree)
441
self.addCleanup(transform.finalize)
442
dir = transform.new_directory('dir', transform.root)
443
first = transform.new_file('file', dir, 'content')
444
second = transform.new_file('FiLe', dir, 'content')
445
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
446
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
448
def test_adjust_path_updates_child_limbo_names(self):
449
tree = self.make_branch_and_tree('tree')
450
transform = TreeTransform(tree)
451
self.addCleanup(transform.finalize)
452
foo_id = transform.new_directory('foo', transform.root)
453
bar_id = transform.new_directory('bar', foo_id)
454
baz_id = transform.new_directory('baz', bar_id)
455
qux_id = transform.new_directory('qux', baz_id)
456
transform.adjust_path('quxx', foo_id, bar_id)
457
self.assertStartsWith(transform._limbo_name(qux_id),
458
transform._limbo_name(bar_id))
460
def test_add_del(self):
461
start, root = self.get_transform()
462
start.new_directory('a', root, 'a')
464
transform, root = self.get_transform()
465
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
466
transform.new_directory('a', root, 'a')
469
def test_unversioning(self):
470
create_tree, root = self.get_transform()
471
parent_id = create_tree.new_directory('parent', root, 'parent-id')
472
create_tree.new_file('child', parent_id, 'child', 'child-id')
474
unversion = TreeTransform(self.wt)
475
self.addCleanup(unversion.finalize)
476
parent = unversion.trans_id_tree_path('parent')
477
unversion.unversion_file(parent)
478
self.assertEqual(unversion.find_conflicts(),
479
[('unversioned parent', parent_id)])
480
file_id = unversion.trans_id_tree_file_id('child-id')
481
unversion.unversion_file(file_id)
484
def test_name_invariants(self):
485
create_tree, root = self.get_transform()
487
root = create_tree.root
488
create_tree.new_file('name1', root, 'hello1', 'name1')
489
create_tree.new_file('name2', root, 'hello2', 'name2')
490
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
491
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
492
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
493
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
496
mangle_tree,root = self.get_transform()
497
root = mangle_tree.root
499
name1 = mangle_tree.trans_id_tree_file_id('name1')
500
name2 = mangle_tree.trans_id_tree_file_id('name2')
501
mangle_tree.adjust_path('name2', root, name1)
502
mangle_tree.adjust_path('name1', root, name2)
504
#tests for deleting parent directories
505
ddir = mangle_tree.trans_id_tree_file_id('ddir')
506
mangle_tree.delete_contents(ddir)
507
dfile = mangle_tree.trans_id_tree_file_id('dfile')
508
mangle_tree.delete_versioned(dfile)
509
mangle_tree.unversion_file(dfile)
510
mfile = mangle_tree.trans_id_tree_file_id('mfile')
511
mangle_tree.adjust_path('mfile', root, mfile)
513
#tests for adding parent directories
514
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
515
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
516
mangle_tree.adjust_path('mfile2', newdir, mfile2)
517
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
518
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
519
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
520
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
522
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
523
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
524
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
525
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
526
self.assertEqual(file(mfile2_path).read(), 'later2')
527
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
528
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
529
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
530
self.assertEqual(file(newfile_path).read(), 'hello3')
531
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
532
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
533
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
535
def test_both_rename(self):
536
create_tree,root = self.get_transform()
537
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
538
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
540
mangle_tree,root = self.get_transform()
541
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
542
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
543
mangle_tree.adjust_path('test', root, selftest)
544
mangle_tree.adjust_path('test_too_much', root, selftest)
545
mangle_tree.set_executability(True, blackbox)
548
def test_both_rename2(self):
549
create_tree,root = self.get_transform()
550
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
551
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
552
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
553
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
556
mangle_tree,root = self.get_transform()
557
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
558
tests = mangle_tree.trans_id_tree_file_id('tests-id')
559
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
560
mangle_tree.adjust_path('selftest', bzrlib, tests)
561
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
562
mangle_tree.set_executability(True, test_too_much)
565
def test_both_rename3(self):
566
create_tree,root = self.get_transform()
567
tests = create_tree.new_directory('tests', root, 'tests-id')
568
create_tree.new_file('test_too_much.py', tests, 'hello1',
571
mangle_tree,root = self.get_transform()
572
tests = mangle_tree.trans_id_tree_file_id('tests-id')
573
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
574
mangle_tree.adjust_path('selftest', root, tests)
575
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
576
mangle_tree.set_executability(True, test_too_much)
579
def test_move_dangling_ie(self):
580
create_tree, root = self.get_transform()
582
root = create_tree.root
583
create_tree.new_file('name1', root, 'hello1', 'name1')
585
delete_contents, root = self.get_transform()
586
file = delete_contents.trans_id_tree_file_id('name1')
587
delete_contents.delete_contents(file)
588
delete_contents.apply()
589
move_id, root = self.get_transform()
590
name1 = move_id.trans_id_tree_file_id('name1')
591
newdir = move_id.new_directory('dir', root, 'newdir')
592
move_id.adjust_path('name2', newdir, name1)
595
def test_replace_dangling_ie(self):
596
create_tree, root = self.get_transform()
598
root = create_tree.root
599
create_tree.new_file('name1', root, 'hello1', 'name1')
601
delete_contents = TreeTransform(self.wt)
602
self.addCleanup(delete_contents.finalize)
603
file = delete_contents.trans_id_tree_file_id('name1')
604
delete_contents.delete_contents(file)
605
delete_contents.apply()
606
delete_contents.finalize()
607
replace = TreeTransform(self.wt)
608
self.addCleanup(replace.finalize)
609
name2 = replace.new_file('name2', root, 'hello2', 'name1')
610
conflicts = replace.find_conflicts()
611
name1 = replace.trans_id_tree_file_id('name1')
612
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
613
resolve_conflicts(replace)
616
def _test_symlinks(self, link_name1,link_target1,
617
link_name2, link_target2):
619
def ozpath(p): return 'oz/' + p
621
self.requireFeature(SymlinkFeature)
622
transform, root = self.get_transform()
623
oz_id = transform.new_directory('oz', root, 'oz-id')
624
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
626
wiz_id = transform.create_path(link_name2, oz_id)
627
transform.create_symlink(link_target2, wiz_id)
628
transform.version_file('wiz-id2', wiz_id)
629
transform.set_executability(True, wiz_id)
630
self.assertEqual(transform.find_conflicts(),
631
[('non-file executability', wiz_id)])
632
transform.set_executability(None, wiz_id)
634
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
635
self.assertEqual('symlink',
636
file_kind(self.wt.abspath(ozpath(link_name1))))
637
self.assertEqual(link_target2,
638
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
639
self.assertEqual(link_target1,
640
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
642
def test_symlinks(self):
643
self._test_symlinks('wizard', 'wizard-target',
644
'wizard2', 'behind_curtain')
646
def test_symlinks_unicode(self):
647
self.requireFeature(tests.UnicodeFilenameFeature)
648
self._test_symlinks(u'\N{Euro Sign}wizard',
649
u'wizard-targ\N{Euro Sign}t',
650
u'\N{Euro Sign}wizard2',
651
u'b\N{Euro Sign}hind_curtain')
653
def test_unable_create_symlink(self):
655
wt = self.make_branch_and_tree('.')
656
tt = TreeTransform(wt) # TreeTransform obtains write lock
658
tt.new_symlink('foo', tt.root, 'bar')
662
os_symlink = getattr(os, 'symlink', None)
665
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
667
"Unable to create symlink 'foo' on this platform",
671
os.symlink = os_symlink
673
def get_conflicted(self):
674
create,root = self.get_transform()
675
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
676
oz = create.new_directory('oz', root, 'oz-id')
677
create.new_directory('emeraldcity', oz, 'emerald-id')
679
conflicts,root = self.get_transform()
680
# set up duplicate entry, duplicate id
681
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
683
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
684
oz = conflicts.trans_id_tree_file_id('oz-id')
685
# set up DeletedParent parent conflict
686
conflicts.delete_versioned(oz)
687
emerald = conflicts.trans_id_tree_file_id('emerald-id')
688
# set up MissingParent conflict
689
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
690
conflicts.adjust_path('munchkincity', root, munchkincity)
691
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
693
conflicts.adjust_path('emeraldcity', emerald, emerald)
694
return conflicts, emerald, oz, old_dorothy, new_dorothy
696
def test_conflict_resolution(self):
697
conflicts, emerald, oz, old_dorothy, new_dorothy =\
698
self.get_conflicted()
699
resolve_conflicts(conflicts)
700
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
701
self.assertIs(conflicts.final_file_id(old_dorothy), None)
702
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
703
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
704
self.assertEqual(conflicts.final_parent(emerald), oz)
707
def test_cook_conflicts(self):
708
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
709
raw_conflicts = resolve_conflicts(tt)
710
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
711
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
712
'dorothy', None, 'dorothy-id')
713
self.assertEqual(cooked_conflicts[0], duplicate)
714
duplicate_id = DuplicateID('Unversioned existing file',
715
'dorothy.moved', 'dorothy', None,
717
self.assertEqual(cooked_conflicts[1], duplicate_id)
718
missing_parent = MissingParent('Created directory', 'munchkincity',
720
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
721
self.assertEqual(cooked_conflicts[2], missing_parent)
722
unversioned_parent = UnversionedParent('Versioned directory',
725
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
727
self.assertEqual(cooked_conflicts[3], unversioned_parent)
728
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
729
'oz/emeraldcity', 'emerald-id', 'emerald-id')
730
self.assertEqual(cooked_conflicts[4], deleted_parent)
731
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
732
self.assertEqual(cooked_conflicts[6], parent_loop)
733
self.assertEqual(len(cooked_conflicts), 7)
736
def test_string_conflicts(self):
737
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
738
raw_conflicts = resolve_conflicts(tt)
739
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
741
conflicts_s = [str(c) for c in cooked_conflicts]
742
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
743
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
744
'Moved existing file to '
746
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
747
'Unversioned existing file '
749
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
750
' munchkincity. Created directory.')
751
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
752
' versioned, but has versioned'
753
' children. Versioned directory.')
754
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
755
" is not empty. Not deleting.")
756
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
757
' versioned, but has versioned'
758
' children. Versioned directory.')
759
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
760
' oz/emeraldcity. Cancelled move.')
762
def prepare_wrong_parent_kind(self):
763
tt, root = self.get_transform()
764
tt.new_file('parent', root, 'contents', 'parent-id')
766
tt, root = self.get_transform()
767
parent_id = tt.trans_id_file_id('parent-id')
768
tt.new_file('child,', parent_id, 'contents2', 'file-id')
771
def test_find_conflicts_wrong_parent_kind(self):
772
tt = self.prepare_wrong_parent_kind()
775
def test_resolve_conflicts_wrong_existing_parent_kind(self):
776
tt = self.prepare_wrong_parent_kind()
777
raw_conflicts = resolve_conflicts(tt)
778
self.assertEqual(set([('non-directory parent', 'Created directory',
779
'new-3')]), raw_conflicts)
780
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
781
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
782
'parent-id')], cooked_conflicts)
784
self.assertEqual(None, self.wt.path2id('parent'))
785
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
787
def test_resolve_conflicts_wrong_new_parent_kind(self):
788
tt, root = self.get_transform()
789
parent_id = tt.new_directory('parent', root, 'parent-id')
790
tt.new_file('child,', parent_id, 'contents2', 'file-id')
792
tt, root = self.get_transform()
793
parent_id = tt.trans_id_file_id('parent-id')
794
tt.delete_contents(parent_id)
795
tt.create_file('contents', parent_id)
796
raw_conflicts = resolve_conflicts(tt)
797
self.assertEqual(set([('non-directory parent', 'Created directory',
798
'new-3')]), raw_conflicts)
800
self.assertEqual(None, self.wt.path2id('parent'))
801
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
803
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
804
tt, root = self.get_transform()
805
parent_id = tt.new_directory('parent', root)
806
tt.new_file('child,', parent_id, 'contents2')
808
tt, root = self.get_transform()
809
parent_id = tt.trans_id_tree_path('parent')
810
tt.delete_contents(parent_id)
811
tt.create_file('contents', parent_id)
812
resolve_conflicts(tt)
814
self.assertIs(None, self.wt.path2id('parent'))
815
self.assertIs(None, self.wt.path2id('parent.new'))
817
def test_moving_versioned_directories(self):
818
create, root = self.get_transform()
819
kansas = create.new_directory('kansas', root, 'kansas-id')
820
create.new_directory('house', kansas, 'house-id')
821
create.new_directory('oz', root, 'oz-id')
823
cyclone, root = self.get_transform()
824
oz = cyclone.trans_id_tree_file_id('oz-id')
825
house = cyclone.trans_id_tree_file_id('house-id')
826
cyclone.adjust_path('house', oz, house)
829
def test_moving_root(self):
830
create, root = self.get_transform()
831
fun = create.new_directory('fun', root, 'fun-id')
832
create.new_directory('sun', root, 'sun-id')
833
create.new_directory('moon', root, 'moon')
835
transform, root = self.get_transform()
836
transform.adjust_root_path('oldroot', fun)
837
new_root = transform.trans_id_tree_path('')
838
transform.version_file('new-root', new_root)
841
def test_renames(self):
842
create, root = self.get_transform()
843
old = create.new_directory('old-parent', root, 'old-id')
844
intermediate = create.new_directory('intermediate', old, 'im-id')
845
myfile = create.new_file('myfile', intermediate, 'myfile-text',
848
rename, root = self.get_transform()
849
old = rename.trans_id_file_id('old-id')
850
rename.adjust_path('new', root, old)
851
myfile = rename.trans_id_file_id('myfile-id')
852
rename.set_executability(True, myfile)
855
def test_rename_fails(self):
856
# see https://bugs.launchpad.net/bzr/+bug/491763
857
create, root_id = self.get_transform()
858
first_dir = create.new_directory('first-dir', root_id, 'first-id')
859
myfile = create.new_file('myfile', root_id, 'myfile-text',
862
if os.name == "posix" and sys.platform != "cygwin":
863
# posix filesystems fail on renaming if the readonly bit is set
864
osutils.make_readonly(self.wt.abspath('first-dir'))
865
elif os.name == "nt":
866
# windows filesystems fail on renaming open files
867
self.addCleanup(file(self.wt.abspath('myfile')).close)
869
self.skip("Don't know how to force a permissions error on rename")
870
# now transform to rename
871
rename_transform, root_id = self.get_transform()
872
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
873
dir_id = rename_transform.trans_id_file_id('first-id')
874
rename_transform.adjust_path('newname', dir_id, file_trans_id)
875
e = self.assertRaises(errors.TransformRenameFailed,
876
rename_transform.apply)
878
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
879
# to .../first-dir/newname: [Errno 13] Permission denied"
880
# On windows looks like:
881
# "Failed to rename .../work/myfile to
882
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
883
# The strerror will vary per OS and language so it's not checked here
884
self.assertContainsRe(str(e),
885
"Failed to rename .*(first-dir.newname:|myfile)")
887
def test_set_executability_order(self):
888
"""Ensure that executability behaves the same, no matter what order.
890
- create file and set executability simultaneously
891
- create file and set executability afterward
892
- unsetting the executability of a file whose executability has not been
893
declared should throw an exception (this may happen when a
894
merge attempts to create a file with a duplicate ID)
896
transform, root = self.get_transform()
899
self.addCleanup(wt.unlock)
900
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
902
sac = transform.new_file('set_after_creation', root,
903
'Set after creation', 'sac')
904
transform.set_executability(True, sac)
905
uws = transform.new_file('unset_without_set', root, 'Unset badly',
907
self.assertRaises(KeyError, transform.set_executability, None, uws)
909
self.assertTrue(wt.is_executable('soc'))
910
self.assertTrue(wt.is_executable('sac'))
912
def test_preserve_mode(self):
913
"""File mode is preserved when replacing content"""
914
if sys.platform == 'win32':
915
raise TestSkipped('chmod has no effect on win32')
916
transform, root = self.get_transform()
917
transform.new_file('file1', root, 'contents', 'file1-id', True)
920
self.addCleanup(self.wt.unlock)
921
self.assertTrue(self.wt.is_executable('file1-id'))
922
transform, root = self.get_transform()
923
file1_id = transform.trans_id_tree_file_id('file1-id')
924
transform.delete_contents(file1_id)
925
transform.create_file('contents2', file1_id)
927
self.assertTrue(self.wt.is_executable('file1-id'))
929
def test__set_mode_stats_correctly(self):
930
"""_set_mode stats to determine file mode."""
931
if sys.platform == 'win32':
932
raise TestSkipped('chmod has no effect on win32')
936
def instrumented_stat(path):
937
stat_paths.append(path)
938
return real_stat(path)
940
transform, root = self.get_transform()
942
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
943
file_id='bar-id-1', executable=False)
946
transform, root = self.get_transform()
947
bar1_id = transform.trans_id_tree_path('bar')
948
bar2_id = transform.trans_id_tree_path('bar2')
950
os.stat = instrumented_stat
951
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
956
bar1_abspath = self.wt.abspath('bar')
957
self.assertEqual([bar1_abspath], stat_paths)
959
def test_iter_changes(self):
960
self.wt.set_root_id('eert_toor')
961
transform, root = self.get_transform()
962
transform.new_file('old', root, 'blah', 'id-1', True)
964
transform, root = self.get_transform()
966
self.assertEqual([], list(transform.iter_changes()))
967
old = transform.trans_id_tree_file_id('id-1')
968
transform.unversion_file(old)
969
self.assertEqual([('id-1', ('old', None), False, (True, False),
970
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
971
(True, True))], list(transform.iter_changes()))
972
transform.new_directory('new', root, 'id-1')
973
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
974
('eert_toor', 'eert_toor'), ('old', 'new'),
975
('file', 'directory'),
976
(True, False))], list(transform.iter_changes()))
980
def test_iter_changes_new(self):
981
self.wt.set_root_id('eert_toor')
982
transform, root = self.get_transform()
983
transform.new_file('old', root, 'blah')
985
transform, root = self.get_transform()
987
old = transform.trans_id_tree_path('old')
988
transform.version_file('id-1', old)
989
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
990
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
991
(False, False))], list(transform.iter_changes()))
995
def test_iter_changes_modifications(self):
996
self.wt.set_root_id('eert_toor')
997
transform, root = self.get_transform()
998
transform.new_file('old', root, 'blah', 'id-1')
999
transform.new_file('new', root, 'blah')
1000
transform.new_directory('subdir', root, 'subdir-id')
1002
transform, root = self.get_transform()
1004
old = transform.trans_id_tree_path('old')
1005
subdir = transform.trans_id_tree_file_id('subdir-id')
1006
new = transform.trans_id_tree_path('new')
1007
self.assertEqual([], list(transform.iter_changes()))
1010
transform.delete_contents(old)
1011
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1012
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1013
(False, False))], list(transform.iter_changes()))
1016
transform.create_file('blah', old)
1017
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1018
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1019
(False, False))], list(transform.iter_changes()))
1020
transform.cancel_deletion(old)
1021
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1022
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1023
(False, False))], list(transform.iter_changes()))
1024
transform.cancel_creation(old)
1026
# move file_id to a different file
1027
self.assertEqual([], list(transform.iter_changes()))
1028
transform.unversion_file(old)
1029
transform.version_file('id-1', new)
1030
transform.adjust_path('old', root, new)
1031
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1032
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1033
(False, False))], list(transform.iter_changes()))
1034
transform.cancel_versioning(new)
1035
transform._removed_id = set()
1038
self.assertEqual([], list(transform.iter_changes()))
1039
transform.set_executability(True, old)
1040
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1041
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1042
(False, True))], list(transform.iter_changes()))
1043
transform.set_executability(None, old)
1046
self.assertEqual([], list(transform.iter_changes()))
1047
transform.adjust_path('new', root, old)
1048
transform._new_parent = {}
1049
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1050
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1051
(False, False))], list(transform.iter_changes()))
1052
transform._new_name = {}
1055
self.assertEqual([], list(transform.iter_changes()))
1056
transform.adjust_path('new', subdir, old)
1057
transform._new_name = {}
1058
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1059
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1060
('file', 'file'), (False, False))],
1061
list(transform.iter_changes()))
1062
transform._new_path = {}
1065
transform.finalize()
1067
def test_iter_changes_modified_bleed(self):
1068
self.wt.set_root_id('eert_toor')
1069
"""Modified flag should not bleed from one change to another"""
1070
# unfortunately, we have no guarantee that file1 (which is modified)
1071
# will be applied before file2. And if it's applied after file2, it
1072
# obviously can't bleed into file2's change output. But for now, it
1074
transform, root = self.get_transform()
1075
transform.new_file('file1', root, 'blah', 'id-1')
1076
transform.new_file('file2', root, 'blah', 'id-2')
1078
transform, root = self.get_transform()
1080
transform.delete_contents(transform.trans_id_file_id('id-1'))
1081
transform.set_executability(True,
1082
transform.trans_id_file_id('id-2'))
1083
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1084
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1085
('file', None), (False, False)),
1086
('id-2', (u'file2', u'file2'), False, (True, True),
1087
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1088
('file', 'file'), (False, True))],
1089
list(transform.iter_changes()))
1091
transform.finalize()
1093
def test_iter_changes_move_missing(self):
1094
"""Test moving ids with no files around"""
1095
self.wt.set_root_id('toor_eert')
1096
# Need two steps because versioning a non-existant file is a conflict.
1097
transform, root = self.get_transform()
1098
transform.new_directory('floater', root, 'floater-id')
1100
transform, root = self.get_transform()
1101
transform.delete_contents(transform.trans_id_tree_path('floater'))
1103
transform, root = self.get_transform()
1104
floater = transform.trans_id_tree_path('floater')
1106
transform.adjust_path('flitter', root, floater)
1107
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1108
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1109
(None, None), (False, False))], list(transform.iter_changes()))
1111
transform.finalize()
1113
def test_iter_changes_pointless(self):
1114
"""Ensure that no-ops are not treated as modifications"""
1115
self.wt.set_root_id('eert_toor')
1116
transform, root = self.get_transform()
1117
transform.new_file('old', root, 'blah', 'id-1')
1118
transform.new_directory('subdir', root, 'subdir-id')
1120
transform, root = self.get_transform()
1122
old = transform.trans_id_tree_path('old')
1123
subdir = transform.trans_id_tree_file_id('subdir-id')
1124
self.assertEqual([], list(transform.iter_changes()))
1125
transform.delete_contents(subdir)
1126
transform.create_directory(subdir)
1127
transform.set_executability(False, old)
1128
transform.unversion_file(old)
1129
transform.version_file('id-1', old)
1130
transform.adjust_path('old', root, old)
1131
self.assertEqual([], list(transform.iter_changes()))
1133
transform.finalize()
1135
def test_rename_count(self):
1136
transform, root = self.get_transform()
1137
transform.new_file('name1', root, 'contents')
1138
self.assertEqual(transform.rename_count, 0)
1140
self.assertEqual(transform.rename_count, 1)
1141
transform2, root = self.get_transform()
1142
transform2.adjust_path('name2', root,
1143
transform2.trans_id_tree_path('name1'))
1144
self.assertEqual(transform2.rename_count, 0)
1146
self.assertEqual(transform2.rename_count, 2)
1148
def test_change_parent(self):
1149
"""Ensure that after we change a parent, the results are still right.
1151
Renames and parent changes on pending transforms can happen as part
1152
of conflict resolution, and are explicitly permitted by the
1155
This test ensures they work correctly with the rename-avoidance
1158
transform, root = self.get_transform()
1159
parent1 = transform.new_directory('parent1', root)
1160
child1 = transform.new_file('child1', parent1, 'contents')
1161
parent2 = transform.new_directory('parent2', root)
1162
transform.adjust_path('child1', parent2, child1)
1164
self.failIfExists(self.wt.abspath('parent1/child1'))
1165
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1166
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1167
# no rename for child1 (counting only renames during apply)
1168
self.failUnlessEqual(2, transform.rename_count)
1170
def test_cancel_parent(self):
1171
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1173
This is like the test_change_parent, except that we cancel the parent
1174
before adjusting the path. The transform must detect that the
1175
directory is non-empty, and move children to safe locations.
1177
transform, root = self.get_transform()
1178
parent1 = transform.new_directory('parent1', root)
1179
child1 = transform.new_file('child1', parent1, 'contents')
1180
child2 = transform.new_file('child2', parent1, 'contents')
1182
transform.cancel_creation(parent1)
1184
self.fail('Failed to move child1 before deleting parent1')
1185
transform.cancel_creation(child2)
1186
transform.create_directory(parent1)
1188
transform.cancel_creation(parent1)
1189
# If the transform incorrectly believes that child2 is still in
1190
# parent1's limbo directory, it will try to rename it and fail
1191
# because was already moved by the first cancel_creation.
1193
self.fail('Transform still thinks child2 is a child of parent1')
1194
parent2 = transform.new_directory('parent2', root)
1195
transform.adjust_path('child1', parent2, child1)
1197
self.failIfExists(self.wt.abspath('parent1'))
1198
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1199
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1200
self.failUnlessEqual(2, transform.rename_count)
1202
def test_adjust_and_cancel(self):
1203
"""Make sure adjust_path keeps track of limbo children properly"""
1204
transform, root = self.get_transform()
1205
parent1 = transform.new_directory('parent1', root)
1206
child1 = transform.new_file('child1', parent1, 'contents')
1207
parent2 = transform.new_directory('parent2', root)
1208
transform.adjust_path('child1', parent2, child1)
1209
transform.cancel_creation(child1)
1211
transform.cancel_creation(parent1)
1212
# if the transform thinks child1 is still in parent1's limbo
1213
# directory, it will attempt to move it and fail.
1215
self.fail('Transform still thinks child1 is a child of parent1')
1216
transform.finalize()
1218
def test_noname_contents(self):
1219
"""TreeTransform should permit deferring naming files."""
1220
transform, root = self.get_transform()
1221
parent = transform.trans_id_file_id('parent-id')
1223
transform.create_directory(parent)
1225
self.fail("Can't handle contents with no name")
1226
transform.finalize()
1228
def test_noname_contents_nested(self):
1229
"""TreeTransform should permit deferring naming files."""
1230
transform, root = self.get_transform()
1231
parent = transform.trans_id_file_id('parent-id')
1233
transform.create_directory(parent)
1235
self.fail("Can't handle contents with no name")
1236
child = transform.new_directory('child', parent)
1237
transform.adjust_path('parent', root, parent)
1239
self.failUnlessExists(self.wt.abspath('parent/child'))
1240
self.assertEqual(1, transform.rename_count)
1242
def test_reuse_name(self):
1243
"""Avoid reusing the same limbo name for different files"""
1244
transform, root = self.get_transform()
1245
parent = transform.new_directory('parent', root)
1246
child1 = transform.new_directory('child', parent)
1248
child2 = transform.new_directory('child', parent)
1250
self.fail('Tranform tried to use the same limbo name twice')
1251
transform.adjust_path('child2', parent, child2)
1253
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1254
# child2 is put into top-level limbo because child1 has already
1255
# claimed the direct limbo path when child2 is created. There is no
1256
# advantage in renaming files once they're in top-level limbo, except
1258
self.assertEqual(2, transform.rename_count)
1260
def test_reuse_when_first_moved(self):
1261
"""Don't avoid direct paths when it is safe to use them"""
1262
transform, root = self.get_transform()
1263
parent = transform.new_directory('parent', root)
1264
child1 = transform.new_directory('child', parent)
1265
transform.adjust_path('child1', parent, child1)
1266
child2 = transform.new_directory('child', parent)
1268
# limbo/new-1 => parent
1269
self.assertEqual(1, transform.rename_count)
1271
def test_reuse_after_cancel(self):
1272
"""Don't avoid direct paths when it is safe to use them"""
1273
transform, root = self.get_transform()
1274
parent2 = transform.new_directory('parent2', root)
1275
child1 = transform.new_directory('child1', parent2)
1276
transform.cancel_creation(parent2)
1277
transform.create_directory(parent2)
1278
child2 = transform.new_directory('child1', parent2)
1279
transform.adjust_path('child2', parent2, child1)
1281
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1282
self.assertEqual(2, transform.rename_count)
1284
def test_finalize_order(self):
1285
"""Finalize must be done in child-to-parent order"""
1286
transform, root = self.get_transform()
1287
parent = transform.new_directory('parent', root)
1288
child = transform.new_directory('child', parent)
1290
transform.finalize()
1292
self.fail('Tried to remove parent before child1')
1294
def test_cancel_with_cancelled_child_should_succeed(self):
1295
transform, root = self.get_transform()
1296
parent = transform.new_directory('parent', root)
1297
child = transform.new_directory('child', parent)
1298
transform.cancel_creation(child)
1299
transform.cancel_creation(parent)
1300
transform.finalize()
1302
def test_rollback_on_directory_clash(self):
1304
wt = self.make_branch_and_tree('.')
1305
tt = TreeTransform(wt) # TreeTransform obtains write lock
1307
foo = tt.new_directory('foo', tt.root)
1308
tt.new_file('bar', foo, 'foobar')
1309
baz = tt.new_directory('baz', tt.root)
1310
tt.new_file('qux', baz, 'quux')
1311
# Ask for a rename 'foo' -> 'baz'
1312
tt.adjust_path('baz', tt.root, foo)
1313
# Lie to tt that we've already resolved all conflicts.
1314
tt.apply(no_conflicts=True)
1318
# The rename will fail because the target directory is not empty (but
1319
# raises FileExists anyway).
1320
err = self.assertRaises(errors.FileExists, tt_helper)
1321
self.assertContainsRe(str(err),
1322
"^File exists: .+/baz")
1324
def test_two_directories_clash(self):
1326
wt = self.make_branch_and_tree('.')
1327
tt = TreeTransform(wt) # TreeTransform obtains write lock
1329
foo_1 = tt.new_directory('foo', tt.root)
1330
tt.new_directory('bar', foo_1)
1331
# Adding the same directory with a different content
1332
foo_2 = tt.new_directory('foo', tt.root)
1333
tt.new_directory('baz', foo_2)
1334
# Lie to tt that we've already resolved all conflicts.
1335
tt.apply(no_conflicts=True)
1339
err = self.assertRaises(errors.FileExists, tt_helper)
1340
self.assertContainsRe(str(err),
1341
"^File exists: .+/foo")
1343
def test_two_directories_clash_finalize(self):
1345
wt = self.make_branch_and_tree('.')
1346
tt = TreeTransform(wt) # TreeTransform obtains write lock
1348
foo_1 = tt.new_directory('foo', tt.root)
1349
tt.new_directory('bar', foo_1)
1350
# Adding the same directory with a different content
1351
foo_2 = tt.new_directory('foo', tt.root)
1352
tt.new_directory('baz', foo_2)
1353
# Lie to tt that we've already resolved all conflicts.
1354
tt.apply(no_conflicts=True)
1358
err = self.assertRaises(errors.FileExists, tt_helper)
1359
self.assertContainsRe(str(err),
1360
"^File exists: .+/foo")
1362
def test_file_to_directory(self):
1363
wt = self.make_branch_and_tree('.')
1364
self.build_tree(['foo'])
1367
tt = TreeTransform(wt)
1368
self.addCleanup(tt.finalize)
1369
foo_trans_id = tt.trans_id_tree_path("foo")
1370
tt.delete_contents(foo_trans_id)
1371
tt.create_directory(foo_trans_id)
1372
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1373
tt.create_file(["aa\n"], bar_trans_id)
1374
tt.version_file("bar-1", bar_trans_id)
1376
self.failUnlessExists("foo/bar")
1379
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1384
changes = wt.changes_from(wt.basis_tree())
1385
self.assertFalse(changes.has_changed(), changes)
1387
def test_file_to_symlink(self):
1388
self.requireFeature(SymlinkFeature)
1389
wt = self.make_branch_and_tree('.')
1390
self.build_tree(['foo'])
1393
tt = TreeTransform(wt)
1394
self.addCleanup(tt.finalize)
1395
foo_trans_id = tt.trans_id_tree_path("foo")
1396
tt.delete_contents(foo_trans_id)
1397
tt.create_symlink("bar", foo_trans_id)
1399
self.failUnlessExists("foo")
1401
self.addCleanup(wt.unlock)
1402
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1405
def test_dir_to_file(self):
1406
wt = self.make_branch_and_tree('.')
1407
self.build_tree(['foo/', 'foo/bar'])
1408
wt.add(['foo', 'foo/bar'])
1410
tt = TreeTransform(wt)
1411
self.addCleanup(tt.finalize)
1412
foo_trans_id = tt.trans_id_tree_path("foo")
1413
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1414
tt.delete_contents(foo_trans_id)
1415
tt.delete_versioned(bar_trans_id)
1416
tt.create_file(["aa\n"], foo_trans_id)
1418
self.failUnlessExists("foo")
1420
self.addCleanup(wt.unlock)
1421
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1424
def test_dir_to_hardlink(self):
1425
self.requireFeature(HardlinkFeature)
1426
wt = self.make_branch_and_tree('.')
1427
self.build_tree(['foo/', 'foo/bar'])
1428
wt.add(['foo', 'foo/bar'])
1430
tt = TreeTransform(wt)
1431
self.addCleanup(tt.finalize)
1432
foo_trans_id = tt.trans_id_tree_path("foo")
1433
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1434
tt.delete_contents(foo_trans_id)
1435
tt.delete_versioned(bar_trans_id)
1436
self.build_tree(['baz'])
1437
tt.create_hardlink("baz", foo_trans_id)
1439
self.failUnlessExists("foo")
1440
self.failUnlessExists("baz")
1442
self.addCleanup(wt.unlock)
1443
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1446
def test_no_final_path(self):
1447
transform, root = self.get_transform()
1448
trans_id = transform.trans_id_file_id('foo')
1449
transform.create_file('bar', trans_id)
1450
transform.cancel_creation(trans_id)
1453
def test_create_from_tree(self):
1454
tree1 = self.make_branch_and_tree('tree1')
1455
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1456
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1457
tree2 = self.make_branch_and_tree('tree2')
1458
tt = TreeTransform(tree2)
1459
foo_trans_id = tt.create_path('foo', tt.root)
1460
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1461
bar_trans_id = tt.create_path('bar', tt.root)
1462
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1464
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1465
self.assertFileEqual('baz', 'tree2/bar')
1467
def test_create_from_tree_bytes(self):
1468
"""Provided lines are used instead of tree content."""
1469
tree1 = self.make_branch_and_tree('tree1')
1470
self.build_tree_contents([('tree1/foo', 'bar'),])
1471
tree1.add('foo', 'foo-id')
1472
tree2 = self.make_branch_and_tree('tree2')
1473
tt = TreeTransform(tree2)
1474
foo_trans_id = tt.create_path('foo', tt.root)
1475
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1477
self.assertFileEqual('qux', 'tree2/foo')
1479
def test_create_from_tree_symlink(self):
1480
self.requireFeature(SymlinkFeature)
1481
tree1 = self.make_branch_and_tree('tree1')
1482
os.symlink('bar', 'tree1/foo')
1483
tree1.add('foo', 'foo-id')
1484
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1485
foo_trans_id = tt.create_path('foo', tt.root)
1486
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1488
self.assertEqual('bar', os.readlink('tree2/foo'))
1491
class TransformGroup(object):
1493
def __init__(self, dirname, root_id):
1496
self.wt = BzrDir.create_standalone_workingtree(dirname)
1497
self.wt.set_root_id(root_id)
1498
self.b = self.wt.branch
1499
self.tt = TreeTransform(self.wt)
1500
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1503
def conflict_text(tree, merge):
1504
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1505
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1508
class TestTransformMerge(TestCaseInTempDir):
1510
def test_text_merge(self):
1511
root_id = generate_ids.gen_root_id()
1512
base = TransformGroup("base", root_id)
1513
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1514
base.tt.new_file('b', base.root, 'b1', 'b')
1515
base.tt.new_file('c', base.root, 'c', 'c')
1516
base.tt.new_file('d', base.root, 'd', 'd')
1517
base.tt.new_file('e', base.root, 'e', 'e')
1518
base.tt.new_file('f', base.root, 'f', 'f')
1519
base.tt.new_directory('g', base.root, 'g')
1520
base.tt.new_directory('h', base.root, 'h')
1522
other = TransformGroup("other", root_id)
1523
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1524
other.tt.new_file('b', other.root, 'b2', 'b')
1525
other.tt.new_file('c', other.root, 'c2', 'c')
1526
other.tt.new_file('d', other.root, 'd', 'd')
1527
other.tt.new_file('e', other.root, 'e2', 'e')
1528
other.tt.new_file('f', other.root, 'f', 'f')
1529
other.tt.new_file('g', other.root, 'g', 'g')
1530
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1531
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1533
this = TransformGroup("this", root_id)
1534
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1535
this.tt.new_file('b', this.root, 'b', 'b')
1536
this.tt.new_file('c', this.root, 'c', 'c')
1537
this.tt.new_file('d', this.root, 'd2', 'd')
1538
this.tt.new_file('e', this.root, 'e2', 'e')
1539
this.tt.new_file('f', this.root, 'f', 'f')
1540
this.tt.new_file('g', this.root, 'g', 'g')
1541
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1542
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1544
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1547
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1548
# three-way text conflict
1549
self.assertEqual(this.wt.get_file('b').read(),
1550
conflict_text('b', 'b2'))
1552
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1554
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1555
# Ambigious clean merge
1556
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1558
self.assertEqual(this.wt.get_file('f').read(), 'f')
1559
# Correct correct results when THIS == OTHER
1560
self.assertEqual(this.wt.get_file('g').read(), 'g')
1561
# Text conflict when THIS & OTHER are text and BASE is dir
1562
self.assertEqual(this.wt.get_file('h').read(),
1563
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1564
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1566
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1568
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1569
self.assertEqual(this.wt.get_file('i').read(),
1570
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1571
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1573
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1575
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1576
modified = ['a', 'b', 'c', 'h', 'i']
1577
merge_modified = this.wt.merge_modified()
1578
self.assertSubset(merge_modified, modified)
1579
self.assertEqual(len(merge_modified), len(modified))
1580
file(this.wt.id2abspath('a'), 'wb').write('booga')
1582
merge_modified = this.wt.merge_modified()
1583
self.assertSubset(merge_modified, modified)
1584
self.assertEqual(len(merge_modified), len(modified))
1588
def test_file_merge(self):
1589
self.requireFeature(SymlinkFeature)
1590
root_id = generate_ids.gen_root_id()
1591
base = TransformGroup("BASE", root_id)
1592
this = TransformGroup("THIS", root_id)
1593
other = TransformGroup("OTHER", root_id)
1594
for tg in this, base, other:
1595
tg.tt.new_directory('a', tg.root, 'a')
1596
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1597
tg.tt.new_file('c', tg.root, 'c', 'c')
1598
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1599
targets = ((base, 'base-e', 'base-f', None, None),
1600
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1601
(other, 'other-e', None, 'other-g', 'other-h'))
1602
for tg, e_target, f_target, g_target, h_target in targets:
1603
for link, target in (('e', e_target), ('f', f_target),
1604
('g', g_target), ('h', h_target)):
1605
if target is not None:
1606
tg.tt.new_symlink(link, tg.root, target, link)
1608
for tg in this, base, other:
1610
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1611
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1612
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1613
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1614
for suffix in ('THIS', 'BASE', 'OTHER'):
1615
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1616
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1617
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1618
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1619
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1620
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1621
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1622
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1623
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1624
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1625
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1626
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1627
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1628
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1630
def test_filename_merge(self):
1631
root_id = generate_ids.gen_root_id()
1632
base = TransformGroup("BASE", root_id)
1633
this = TransformGroup("THIS", root_id)
1634
other = TransformGroup("OTHER", root_id)
1635
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1636
for t in [base, this, other]]
1637
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1638
for t in [base, this, other]]
1639
base.tt.new_directory('c', base_a, 'c')
1640
this.tt.new_directory('c1', this_a, 'c')
1641
other.tt.new_directory('c', other_b, 'c')
1643
base.tt.new_directory('d', base_a, 'd')
1644
this.tt.new_directory('d1', this_b, 'd')
1645
other.tt.new_directory('d', other_a, 'd')
1647
base.tt.new_directory('e', base_a, 'e')
1648
this.tt.new_directory('e', this_a, 'e')
1649
other.tt.new_directory('e1', other_b, 'e')
1651
base.tt.new_directory('f', base_a, 'f')
1652
this.tt.new_directory('f1', this_b, 'f')
1653
other.tt.new_directory('f1', other_b, 'f')
1655
for tg in [this, base, other]:
1657
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1658
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1659
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1660
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1661
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1663
def test_filename_merge_conflicts(self):
1664
root_id = generate_ids.gen_root_id()
1665
base = TransformGroup("BASE", root_id)
1666
this = TransformGroup("THIS", root_id)
1667
other = TransformGroup("OTHER", root_id)
1668
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1669
for t in [base, this, other]]
1670
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1671
for t in [base, this, other]]
1673
base.tt.new_file('g', base_a, 'g', 'g')
1674
other.tt.new_file('g1', other_b, 'g1', 'g')
1676
base.tt.new_file('h', base_a, 'h', 'h')
1677
this.tt.new_file('h1', this_b, 'h1', 'h')
1679
base.tt.new_file('i', base.root, 'i', 'i')
1680
other.tt.new_directory('i1', this_b, 'i')
1682
for tg in [this, base, other]:
1684
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1686
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1687
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1688
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1689
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1690
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1691
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1692
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1695
class TestBuildTree(tests.TestCaseWithTransport):
1697
def test_build_tree_with_symlinks(self):
1698
self.requireFeature(SymlinkFeature)
1700
a = BzrDir.create_standalone_workingtree('a')
1702
file('a/foo/bar', 'wb').write('contents')
1703
os.symlink('a/foo/bar', 'a/foo/baz')
1704
a.add(['foo', 'foo/bar', 'foo/baz'])
1705
a.commit('initial commit')
1706
b = BzrDir.create_standalone_workingtree('b')
1707
basis = a.basis_tree()
1709
self.addCleanup(basis.unlock)
1710
build_tree(basis, b)
1711
self.assertIs(os.path.isdir('b/foo'), True)
1712
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1713
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1715
def test_build_with_references(self):
1716
tree = self.make_branch_and_tree('source',
1717
format='dirstate-with-subtree')
1718
subtree = self.make_branch_and_tree('source/subtree',
1719
format='dirstate-with-subtree')
1720
tree.add_reference(subtree)
1721
tree.commit('a revision')
1722
tree.branch.create_checkout('target')
1723
self.failUnlessExists('target')
1724
self.failUnlessExists('target/subtree')
1726
def test_file_conflict_handling(self):
1727
"""Ensure that when building trees, conflict handling is done"""
1728
source = self.make_branch_and_tree('source')
1729
target = self.make_branch_and_tree('target')
1730
self.build_tree(['source/file', 'target/file'])
1731
source.add('file', 'new-file')
1732
source.commit('added file')
1733
build_tree(source.basis_tree(), target)
1734
self.assertEqual([DuplicateEntry('Moved existing file to',
1735
'file.moved', 'file', None, 'new-file')],
1737
target2 = self.make_branch_and_tree('target2')
1738
target_file = file('target2/file', 'wb')
1740
source_file = file('source/file', 'rb')
1742
target_file.write(source_file.read())
1747
build_tree(source.basis_tree(), target2)
1748
self.assertEqual([], target2.conflicts())
1750
def test_symlink_conflict_handling(self):
1751
"""Ensure that when building trees, conflict handling is done"""
1752
self.requireFeature(SymlinkFeature)
1753
source = self.make_branch_and_tree('source')
1754
os.symlink('foo', 'source/symlink')
1755
source.add('symlink', 'new-symlink')
1756
source.commit('added file')
1757
target = self.make_branch_and_tree('target')
1758
os.symlink('bar', 'target/symlink')
1759
build_tree(source.basis_tree(), target)
1760
self.assertEqual([DuplicateEntry('Moved existing file to',
1761
'symlink.moved', 'symlink', None, 'new-symlink')],
1763
target = self.make_branch_and_tree('target2')
1764
os.symlink('foo', 'target2/symlink')
1765
build_tree(source.basis_tree(), target)
1766
self.assertEqual([], target.conflicts())
1768
def test_directory_conflict_handling(self):
1769
"""Ensure that when building trees, conflict handling is done"""
1770
source = self.make_branch_and_tree('source')
1771
target = self.make_branch_and_tree('target')
1772
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1773
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1774
source.commit('added file')
1775
build_tree(source.basis_tree(), target)
1776
self.assertEqual([], target.conflicts())
1777
self.failUnlessExists('target/dir1/file')
1779
# Ensure contents are merged
1780
target = self.make_branch_and_tree('target2')
1781
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1782
build_tree(source.basis_tree(), target)
1783
self.assertEqual([], target.conflicts())
1784
self.failUnlessExists('target2/dir1/file2')
1785
self.failUnlessExists('target2/dir1/file')
1787
# Ensure new contents are suppressed for existing branches
1788
target = self.make_branch_and_tree('target3')
1789
self.make_branch('target3/dir1')
1790
self.build_tree(['target3/dir1/file2'])
1791
build_tree(source.basis_tree(), target)
1792
self.failIfExists('target3/dir1/file')
1793
self.failUnlessExists('target3/dir1/file2')
1794
self.failUnlessExists('target3/dir1.diverted/file')
1795
self.assertEqual([DuplicateEntry('Diverted to',
1796
'dir1.diverted', 'dir1', 'new-dir1', None)],
1799
target = self.make_branch_and_tree('target4')
1800
self.build_tree(['target4/dir1/'])
1801
self.make_branch('target4/dir1/file')
1802
build_tree(source.basis_tree(), target)
1803
self.failUnlessExists('target4/dir1/file')
1804
self.assertEqual('directory', file_kind('target4/dir1/file'))
1805
self.failUnlessExists('target4/dir1/file.diverted')
1806
self.assertEqual([DuplicateEntry('Diverted to',
1807
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1810
def test_mixed_conflict_handling(self):
1811
"""Ensure that when building trees, conflict handling is done"""
1812
source = self.make_branch_and_tree('source')
1813
target = self.make_branch_and_tree('target')
1814
self.build_tree(['source/name', 'target/name/'])
1815
source.add('name', 'new-name')
1816
source.commit('added file')
1817
build_tree(source.basis_tree(), target)
1818
self.assertEqual([DuplicateEntry('Moved existing file to',
1819
'name.moved', 'name', None, 'new-name')], target.conflicts())
1821
def test_raises_in_populated(self):
1822
source = self.make_branch_and_tree('source')
1823
self.build_tree(['source/name'])
1825
source.commit('added name')
1826
target = self.make_branch_and_tree('target')
1827
self.build_tree(['target/name'])
1829
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1830
build_tree, source.basis_tree(), target)
1832
def test_build_tree_rename_count(self):
1833
source = self.make_branch_and_tree('source')
1834
self.build_tree(['source/file1', 'source/dir1/'])
1835
source.add(['file1', 'dir1'])
1836
source.commit('add1')
1837
target1 = self.make_branch_and_tree('target1')
1838
transform_result = build_tree(source.basis_tree(), target1)
1839
self.assertEqual(2, transform_result.rename_count)
1841
self.build_tree(['source/dir1/file2'])
1842
source.add(['dir1/file2'])
1843
source.commit('add3')
1844
target2 = self.make_branch_and_tree('target2')
1845
transform_result = build_tree(source.basis_tree(), target2)
1846
# children of non-root directories should not be renamed
1847
self.assertEqual(2, transform_result.rename_count)
1849
def create_ab_tree(self):
1850
"""Create a committed test tree with two files"""
1851
source = self.make_branch_and_tree('source')
1852
self.build_tree_contents([('source/file1', 'A')])
1853
self.build_tree_contents([('source/file2', 'B')])
1854
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1855
source.commit('commit files')
1857
self.addCleanup(source.unlock)
1860
def test_build_tree_accelerator_tree(self):
1861
source = self.create_ab_tree()
1862
self.build_tree_contents([('source/file2', 'C')])
1864
real_source_get_file = source.get_file
1865
def get_file(file_id, path=None):
1866
calls.append(file_id)
1867
return real_source_get_file(file_id, path)
1868
source.get_file = get_file
1869
target = self.make_branch_and_tree('target')
1870
revision_tree = source.basis_tree()
1871
revision_tree.lock_read()
1872
self.addCleanup(revision_tree.unlock)
1873
build_tree(revision_tree, target, source)
1874
self.assertEqual(['file1-id'], calls)
1876
self.addCleanup(target.unlock)
1877
self.assertEqual([], list(target.iter_changes(revision_tree)))
1879
def test_build_tree_accelerator_tree_missing_file(self):
1880
source = self.create_ab_tree()
1881
os.unlink('source/file1')
1882
source.remove(['file2'])
1883
target = self.make_branch_and_tree('target')
1884
revision_tree = source.basis_tree()
1885
revision_tree.lock_read()
1886
self.addCleanup(revision_tree.unlock)
1887
build_tree(revision_tree, target, source)
1889
self.addCleanup(target.unlock)
1890
self.assertEqual([], list(target.iter_changes(revision_tree)))
1892
def test_build_tree_accelerator_wrong_kind(self):
1893
self.requireFeature(SymlinkFeature)
1894
source = self.make_branch_and_tree('source')
1895
self.build_tree_contents([('source/file1', '')])
1896
self.build_tree_contents([('source/file2', '')])
1897
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1898
source.commit('commit files')
1899
os.unlink('source/file2')
1900
self.build_tree_contents([('source/file2/', 'C')])
1901
os.unlink('source/file1')
1902
os.symlink('file2', 'source/file1')
1904
real_source_get_file = source.get_file
1905
def get_file(file_id, path=None):
1906
calls.append(file_id)
1907
return real_source_get_file(file_id, path)
1908
source.get_file = get_file
1909
target = self.make_branch_and_tree('target')
1910
revision_tree = source.basis_tree()
1911
revision_tree.lock_read()
1912
self.addCleanup(revision_tree.unlock)
1913
build_tree(revision_tree, target, source)
1914
self.assertEqual([], calls)
1916
self.addCleanup(target.unlock)
1917
self.assertEqual([], list(target.iter_changes(revision_tree)))
1919
def test_build_tree_hardlink(self):
1920
self.requireFeature(HardlinkFeature)
1921
source = self.create_ab_tree()
1922
target = self.make_branch_and_tree('target')
1923
revision_tree = source.basis_tree()
1924
revision_tree.lock_read()
1925
self.addCleanup(revision_tree.unlock)
1926
build_tree(revision_tree, target, source, hardlink=True)
1928
self.addCleanup(target.unlock)
1929
self.assertEqual([], list(target.iter_changes(revision_tree)))
1930
source_stat = os.stat('source/file1')
1931
target_stat = os.stat('target/file1')
1932
self.assertEqual(source_stat, target_stat)
1934
# Explicitly disallowing hardlinks should prevent them.
1935
target2 = self.make_branch_and_tree('target2')
1936
build_tree(revision_tree, target2, source, hardlink=False)
1938
self.addCleanup(target2.unlock)
1939
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1940
source_stat = os.stat('source/file1')
1941
target2_stat = os.stat('target2/file1')
1942
self.assertNotEqual(source_stat, target2_stat)
1944
def test_build_tree_accelerator_tree_moved(self):
1945
source = self.make_branch_and_tree('source')
1946
self.build_tree_contents([('source/file1', 'A')])
1947
source.add(['file1'], ['file1-id'])
1948
source.commit('commit files')
1949
source.rename_one('file1', 'file2')
1951
self.addCleanup(source.unlock)
1952
target = self.make_branch_and_tree('target')
1953
revision_tree = source.basis_tree()
1954
revision_tree.lock_read()
1955
self.addCleanup(revision_tree.unlock)
1956
build_tree(revision_tree, target, source)
1958
self.addCleanup(target.unlock)
1959
self.assertEqual([], list(target.iter_changes(revision_tree)))
1961
def test_build_tree_hardlinks_preserve_execute(self):
1962
self.requireFeature(HardlinkFeature)
1963
source = self.create_ab_tree()
1964
tt = TreeTransform(source)
1965
trans_id = tt.trans_id_tree_file_id('file1-id')
1966
tt.set_executability(True, trans_id)
1968
self.assertTrue(source.is_executable('file1-id'))
1969
target = self.make_branch_and_tree('target')
1970
revision_tree = source.basis_tree()
1971
revision_tree.lock_read()
1972
self.addCleanup(revision_tree.unlock)
1973
build_tree(revision_tree, target, source, hardlink=True)
1975
self.addCleanup(target.unlock)
1976
self.assertEqual([], list(target.iter_changes(revision_tree)))
1977
self.assertTrue(source.is_executable('file1-id'))
1979
def install_rot13_content_filter(self, pattern):
1981
# self.addCleanup(filters._reset_registry, filters._reset_registry())
1982
# below, but that looks a bit... hard to read even if it's exactly
1984
original_registry = filters._reset_registry()
1985
def restore_registry():
1986
filters._reset_registry(original_registry)
1987
self.addCleanup(restore_registry)
1988
def rot13(chunks, context=None):
1989
return [''.join(chunks).encode('rot13')]
1990
rot13filter = filters.ContentFilter(rot13, rot13)
1991
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1992
os.mkdir(self.test_home_dir + '/.bazaar')
1993
rules_filename = self.test_home_dir + '/.bazaar/rules'
1994
f = open(rules_filename, 'wb')
1995
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1997
def uninstall_rules():
1998
os.remove(rules_filename)
2000
self.addCleanup(uninstall_rules)
2003
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2004
"""build_tree will not hardlink files that have content filtering rules
2005
applied to them (but will still hardlink other files from the same tree
2008
self.requireFeature(HardlinkFeature)
2009
self.install_rot13_content_filter('file1')
2010
source = self.create_ab_tree()
2011
target = self.make_branch_and_tree('target')
2012
revision_tree = source.basis_tree()
2013
revision_tree.lock_read()
2014
self.addCleanup(revision_tree.unlock)
2015
build_tree(revision_tree, target, source, hardlink=True)
2017
self.addCleanup(target.unlock)
2018
self.assertEqual([], list(target.iter_changes(revision_tree)))
2019
source_stat = os.stat('source/file1')
2020
target_stat = os.stat('target/file1')
2021
self.assertNotEqual(source_stat, target_stat)
2022
source_stat = os.stat('source/file2')
2023
target_stat = os.stat('target/file2')
2024
self.assertEqualStat(source_stat, target_stat)
2026
def test_case_insensitive_build_tree_inventory(self):
2027
if (tests.CaseInsensitiveFilesystemFeature.available()
2028
or tests.CaseInsCasePresFilenameFeature.available()):
2029
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2030
source = self.make_branch_and_tree('source')
2031
self.build_tree(['source/file', 'source/FILE'])
2032
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2033
source.commit('added files')
2034
# Don't try this at home, kids!
2035
# Force the tree to report that it is case insensitive
2036
target = self.make_branch_and_tree('target')
2037
target.case_sensitive = False
2038
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2039
self.assertEqual('file.moved', target.id2path('lower-id'))
2040
self.assertEqual('FILE', target.id2path('upper-id'))
2043
class TestCommitTransform(tests.TestCaseWithTransport):
2045
def get_branch(self):
2046
tree = self.make_branch_and_tree('tree')
2048
self.addCleanup(tree.unlock)
2049
tree.commit('empty commit')
2052
def get_branch_and_transform(self):
2053
branch = self.get_branch()
2054
tt = TransformPreview(branch.basis_tree())
2055
self.addCleanup(tt.finalize)
2058
def test_commit_wrong_basis(self):
2059
branch = self.get_branch()
2060
basis = branch.repository.revision_tree(
2061
_mod_revision.NULL_REVISION)
2062
tt = TransformPreview(basis)
2063
self.addCleanup(tt.finalize)
2064
e = self.assertRaises(ValueError, tt.commit, branch, '')
2065
self.assertEqual('TreeTransform not based on branch basis: null:',
2068
def test_empy_commit(self):
2069
branch, tt = self.get_branch_and_transform()
2070
rev = tt.commit(branch, 'my message')
2071
self.assertEqual(2, branch.revno())
2072
repo = branch.repository
2073
self.assertEqual('my message', repo.get_revision(rev).message)
2075
def test_merge_parents(self):
2076
branch, tt = self.get_branch_and_transform()
2077
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2078
self.assertEqual(['rev1b', 'rev1c'],
2079
branch.basis_tree().get_parent_ids()[1:])
2081
def test_first_commit(self):
2082
branch = self.make_branch('branch')
2084
self.addCleanup(branch.unlock)
2085
tt = TransformPreview(branch.basis_tree())
2086
self.addCleanup(tt.finalize)
2087
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2088
rev = tt.commit(branch, 'my message')
2089
self.assertEqual([], branch.basis_tree().get_parent_ids())
2090
self.assertNotEqual(_mod_revision.NULL_REVISION,
2091
branch.last_revision())
2093
def test_first_commit_with_merge_parents(self):
2094
branch = self.make_branch('branch')
2096
self.addCleanup(branch.unlock)
2097
tt = TransformPreview(branch.basis_tree())
2098
self.addCleanup(tt.finalize)
2099
e = self.assertRaises(ValueError, tt.commit, branch,
2100
'my message', ['rev1b-id'])
2101
self.assertEqual('Cannot supply merge parents for first commit.',
2103
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2105
def test_add_files(self):
2106
branch, tt = self.get_branch_and_transform()
2107
tt.new_file('file', tt.root, 'contents', 'file-id')
2108
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2109
if SymlinkFeature.available():
2110
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2111
rev = tt.commit(branch, 'message')
2112
tree = branch.basis_tree()
2113
self.assertEqual('file', tree.id2path('file-id'))
2114
self.assertEqual('contents', tree.get_file_text('file-id'))
2115
self.assertEqual('dir', tree.id2path('dir-id'))
2116
if SymlinkFeature.available():
2117
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2118
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2120
def test_add_unversioned(self):
2121
branch, tt = self.get_branch_and_transform()
2122
tt.new_file('file', tt.root, 'contents')
2123
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2124
'message', strict=True)
2126
def test_modify_strict(self):
2127
branch, tt = self.get_branch_and_transform()
2128
tt.new_file('file', tt.root, 'contents', 'file-id')
2129
tt.commit(branch, 'message', strict=True)
2130
tt = TransformPreview(branch.basis_tree())
2131
self.addCleanup(tt.finalize)
2132
trans_id = tt.trans_id_file_id('file-id')
2133
tt.delete_contents(trans_id)
2134
tt.create_file('contents', trans_id)
2135
tt.commit(branch, 'message', strict=True)
2137
def test_commit_malformed(self):
2138
"""Committing a malformed transform should raise an exception.
2140
In this case, we are adding a file without adding its parent.
2142
branch, tt = self.get_branch_and_transform()
2143
parent_id = tt.trans_id_file_id('parent-id')
2144
tt.new_file('file', parent_id, 'contents', 'file-id')
2145
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2148
def test_commit_rich_revision_data(self):
2149
branch, tt = self.get_branch_and_transform()
2150
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2151
committer='me <me@example.com>',
2152
revprops={'foo': 'bar'}, revision_id='revid-1',
2153
authors=['Author1 <author1@example.com>',
2154
'Author2 <author2@example.com>',
2156
self.assertEqual('revid-1', rev_id)
2157
revision = branch.repository.get_revision(rev_id)
2158
self.assertEqual(1, revision.timestamp)
2159
self.assertEqual(43201, revision.timezone)
2160
self.assertEqual('me <me@example.com>', revision.committer)
2161
self.assertEqual(['Author1 <author1@example.com>',
2162
'Author2 <author2@example.com>'],
2163
revision.get_apparent_authors())
2164
del revision.properties['authors']
2165
self.assertEqual({'foo': 'bar',
2166
'branch-nick': 'tree'},
2167
revision.properties)
2169
def test_no_explicit_revprops(self):
2170
branch, tt = self.get_branch_and_transform()
2171
rev_id = tt.commit(branch, 'message', authors=[
2172
'Author1 <author1@example.com>',
2173
'Author2 <author2@example.com>', ])
2174
revision = branch.repository.get_revision(rev_id)
2175
self.assertEqual(['Author1 <author1@example.com>',
2176
'Author2 <author2@example.com>'],
2177
revision.get_apparent_authors())
2178
self.assertEqual('tree', revision.properties['branch-nick'])
2181
class MockTransform(object):
2183
def has_named_child(self, by_parent, parent_id, name):
2184
for child_id in by_parent[parent_id]:
2188
elif name == "name.~%s~" % child_id:
2193
class MockEntry(object):
2195
object.__init__(self)
2199
class TestGetBackupName(TestCase):
2200
def test_get_backup_name(self):
2201
tt = MockTransform()
2202
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2203
self.assertEqual(name, 'name.~1~')
2204
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2205
self.assertEqual(name, 'name.~2~')
2206
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2207
self.assertEqual(name, 'name.~1~')
2208
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2209
self.assertEqual(name, 'name.~1~')
2210
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2211
self.assertEqual(name, 'name.~4~')
2214
class TestFileMover(tests.TestCaseWithTransport):
2216
def test_file_mover(self):
2217
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2218
mover = _FileMover()
2219
mover.rename('a', 'q')
2220
self.failUnlessExists('q')
2221
self.failIfExists('a')
2222
self.failUnlessExists('q/b')
2223
self.failUnlessExists('c')
2224
self.failUnlessExists('c/d')
2226
def test_pre_delete_rollback(self):
2227
self.build_tree(['a/'])
2228
mover = _FileMover()
2229
mover.pre_delete('a', 'q')
2230
self.failUnlessExists('q')
2231
self.failIfExists('a')
2233
self.failIfExists('q')
2234
self.failUnlessExists('a')
2236
def test_apply_deletions(self):
2237
self.build_tree(['a/', 'b/'])
2238
mover = _FileMover()
2239
mover.pre_delete('a', 'q')
2240
mover.pre_delete('b', 'r')
2241
self.failUnlessExists('q')
2242
self.failUnlessExists('r')
2243
self.failIfExists('a')
2244
self.failIfExists('b')
2245
mover.apply_deletions()
2246
self.failIfExists('q')
2247
self.failIfExists('r')
2248
self.failIfExists('a')
2249
self.failIfExists('b')
2251
def test_file_mover_rollback(self):
2252
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2253
mover = _FileMover()
2254
mover.rename('c/d', 'c/f')
2255
mover.rename('c/e', 'c/d')
2257
mover.rename('a', 'c')
2258
except errors.FileExists, e:
2260
self.failUnlessExists('a')
2261
self.failUnlessExists('c/d')
2264
class Bogus(Exception):
2268
class TestTransformRollback(tests.TestCaseWithTransport):
2270
class ExceptionFileMover(_FileMover):
2272
def __init__(self, bad_source=None, bad_target=None):
2273
_FileMover.__init__(self)
2274
self.bad_source = bad_source
2275
self.bad_target = bad_target
2277
def rename(self, source, target):
2278
if (self.bad_source is not None and
2279
source.endswith(self.bad_source)):
2281
elif (self.bad_target is not None and
2282
target.endswith(self.bad_target)):
2285
_FileMover.rename(self, source, target)
2287
def test_rollback_rename(self):
2288
tree = self.make_branch_and_tree('.')
2289
self.build_tree(['a/', 'a/b'])
2290
tt = TreeTransform(tree)
2291
self.addCleanup(tt.finalize)
2292
a_id = tt.trans_id_tree_path('a')
2293
tt.adjust_path('c', tt.root, a_id)
2294
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2295
self.assertRaises(Bogus, tt.apply,
2296
_mover=self.ExceptionFileMover(bad_source='a'))
2297
self.failUnlessExists('a')
2298
self.failUnlessExists('a/b')
2300
self.failUnlessExists('c')
2301
self.failUnlessExists('c/d')
2303
def test_rollback_rename_into_place(self):
2304
tree = self.make_branch_and_tree('.')
2305
self.build_tree(['a/', 'a/b'])
2306
tt = TreeTransform(tree)
2307
self.addCleanup(tt.finalize)
2308
a_id = tt.trans_id_tree_path('a')
2309
tt.adjust_path('c', tt.root, a_id)
2310
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2311
self.assertRaises(Bogus, tt.apply,
2312
_mover=self.ExceptionFileMover(bad_target='c/d'))
2313
self.failUnlessExists('a')
2314
self.failUnlessExists('a/b')
2316
self.failUnlessExists('c')
2317
self.failUnlessExists('c/d')
2319
def test_rollback_deletion(self):
2320
tree = self.make_branch_and_tree('.')
2321
self.build_tree(['a/', 'a/b'])
2322
tt = TreeTransform(tree)
2323
self.addCleanup(tt.finalize)
2324
a_id = tt.trans_id_tree_path('a')
2325
tt.delete_contents(a_id)
2326
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2327
self.assertRaises(Bogus, tt.apply,
2328
_mover=self.ExceptionFileMover(bad_target='d'))
2329
self.failUnlessExists('a')
2330
self.failUnlessExists('a/b')
2332
def test_resolve_no_parent(self):
2333
wt = self.make_branch_and_tree('.')
2334
tt = TreeTransform(wt)
2335
self.addCleanup(tt.finalize)
2336
parent = tt.trans_id_file_id('parent-id')
2337
tt.new_file('file', parent, 'Contents')
2338
resolve_conflicts(tt)
2341
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2342
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2344
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2345
('', ''), ('directory', 'directory'), (False, None))
2348
class TestTransformPreview(tests.TestCaseWithTransport):
2350
def create_tree(self):
2351
tree = self.make_branch_and_tree('.')
2352
self.build_tree_contents([('a', 'content 1')])
2353
tree.set_root_id('TREE_ROOT')
2354
tree.add('a', 'a-id')
2355
tree.commit('rev1', rev_id='rev1')
2356
return tree.branch.repository.revision_tree('rev1')
2358
def get_empty_preview(self):
2359
repository = self.make_repository('repo')
2360
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2361
preview = TransformPreview(tree)
2362
self.addCleanup(preview.finalize)
2365
def test_transform_preview(self):
2366
revision_tree = self.create_tree()
2367
preview = TransformPreview(revision_tree)
2368
self.addCleanup(preview.finalize)
2370
def test_transform_preview_tree(self):
2371
revision_tree = self.create_tree()
2372
preview = TransformPreview(revision_tree)
2373
self.addCleanup(preview.finalize)
2374
preview.get_preview_tree()
2376
def test_transform_new_file(self):
2377
revision_tree = self.create_tree()
2378
preview = TransformPreview(revision_tree)
2379
self.addCleanup(preview.finalize)
2380
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2381
preview_tree = preview.get_preview_tree()
2382
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2384
preview_tree.get_file('file2-id').read(), 'content B\n')
2386
def test_diff_preview_tree(self):
2387
revision_tree = self.create_tree()
2388
preview = TransformPreview(revision_tree)
2389
self.addCleanup(preview.finalize)
2390
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2391
preview_tree = preview.get_preview_tree()
2393
show_diff_trees(revision_tree, preview_tree, out)
2394
lines = out.getvalue().splitlines()
2395
self.assertEqual(lines[0], "=== added file 'file2'")
2396
# 3 lines of diff administrivia
2397
self.assertEqual(lines[4], "+content B")
2399
def test_transform_conflicts(self):
2400
revision_tree = self.create_tree()
2401
preview = TransformPreview(revision_tree)
2402
self.addCleanup(preview.finalize)
2403
preview.new_file('a', preview.root, 'content 2')
2404
resolve_conflicts(preview)
2405
trans_id = preview.trans_id_file_id('a-id')
2406
self.assertEqual('a.moved', preview.final_name(trans_id))
2408
def get_tree_and_preview_tree(self):
2409
revision_tree = self.create_tree()
2410
preview = TransformPreview(revision_tree)
2411
self.addCleanup(preview.finalize)
2412
a_trans_id = preview.trans_id_file_id('a-id')
2413
preview.delete_contents(a_trans_id)
2414
preview.create_file('b content', a_trans_id)
2415
preview_tree = preview.get_preview_tree()
2416
return revision_tree, preview_tree
2418
def test_iter_changes(self):
2419
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2420
root = revision_tree.inventory.root.file_id
2421
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2422
(root, root), ('a', 'a'), ('file', 'file'),
2424
list(preview_tree.iter_changes(revision_tree)))
2426
def test_include_unchanged_succeeds(self):
2427
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2428
changes = preview_tree.iter_changes(revision_tree,
2429
include_unchanged=True)
2430
root = revision_tree.inventory.root.file_id
2432
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2434
def test_specific_files(self):
2435
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2436
changes = preview_tree.iter_changes(revision_tree,
2437
specific_files=[''])
2438
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2440
def test_want_unversioned(self):
2441
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2442
changes = preview_tree.iter_changes(revision_tree,
2443
want_unversioned=True)
2444
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2446
def test_ignore_extra_trees_no_specific_files(self):
2447
# extra_trees is harmless without specific_files, so we'll silently
2448
# accept it, even though we won't use it.
2449
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2450
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2452
def test_ignore_require_versioned_no_specific_files(self):
2453
# require_versioned is meaningless without specific_files.
2454
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2455
preview_tree.iter_changes(revision_tree, require_versioned=False)
2457
def test_ignore_pb(self):
2458
# pb could be supported, but TT.iter_changes doesn't support it.
2459
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2460
preview_tree.iter_changes(revision_tree)
2462
def test_kind(self):
2463
revision_tree = self.create_tree()
2464
preview = TransformPreview(revision_tree)
2465
self.addCleanup(preview.finalize)
2466
preview.new_file('file', preview.root, 'contents', 'file-id')
2467
preview.new_directory('directory', preview.root, 'dir-id')
2468
preview_tree = preview.get_preview_tree()
2469
self.assertEqual('file', preview_tree.kind('file-id'))
2470
self.assertEqual('directory', preview_tree.kind('dir-id'))
2472
def test_get_file_mtime(self):
2473
preview = self.get_empty_preview()
2474
file_trans_id = preview.new_file('file', preview.root, 'contents',
2476
limbo_path = preview._limbo_name(file_trans_id)
2477
preview_tree = preview.get_preview_tree()
2478
self.assertEqual(os.stat(limbo_path).st_mtime,
2479
preview_tree.get_file_mtime('file-id'))
2481
def test_get_file_mtime_renamed(self):
2482
work_tree = self.make_branch_and_tree('tree')
2483
self.build_tree(['tree/file'])
2484
work_tree.add('file', 'file-id')
2485
preview = TransformPreview(work_tree)
2486
self.addCleanup(preview.finalize)
2487
file_trans_id = preview.trans_id_tree_file_id('file-id')
2488
preview.adjust_path('renamed', preview.root, file_trans_id)
2489
preview_tree = preview.get_preview_tree()
2490
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2491
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2493
def test_get_file(self):
2494
preview = self.get_empty_preview()
2495
preview.new_file('file', preview.root, 'contents', 'file-id')
2496
preview_tree = preview.get_preview_tree()
2497
tree_file = preview_tree.get_file('file-id')
2499
self.assertEqual('contents', tree_file.read())
2503
def test_get_symlink_target(self):
2504
self.requireFeature(SymlinkFeature)
2505
preview = self.get_empty_preview()
2506
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2507
preview_tree = preview.get_preview_tree()
2508
self.assertEqual('target',
2509
preview_tree.get_symlink_target('symlink-id'))
2511
def test_all_file_ids(self):
2512
tree = self.make_branch_and_tree('tree')
2513
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2514
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2515
preview = TransformPreview(tree)
2516
self.addCleanup(preview.finalize)
2517
preview.unversion_file(preview.trans_id_file_id('b-id'))
2518
c_trans_id = preview.trans_id_file_id('c-id')
2519
preview.unversion_file(c_trans_id)
2520
preview.version_file('c-id', c_trans_id)
2521
preview_tree = preview.get_preview_tree()
2522
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2523
preview_tree.all_file_ids())
2525
def test_path2id_deleted_unchanged(self):
2526
tree = self.make_branch_and_tree('tree')
2527
self.build_tree(['tree/unchanged', 'tree/deleted'])
2528
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2529
preview = TransformPreview(tree)
2530
self.addCleanup(preview.finalize)
2531
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2532
preview_tree = preview.get_preview_tree()
2533
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2534
self.assertIs(None, preview_tree.path2id('deleted'))
2536
def test_path2id_created(self):
2537
tree = self.make_branch_and_tree('tree')
2538
self.build_tree(['tree/unchanged'])
2539
tree.add(['unchanged'], ['unchanged-id'])
2540
preview = TransformPreview(tree)
2541
self.addCleanup(preview.finalize)
2542
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2543
'contents', 'new-id')
2544
preview_tree = preview.get_preview_tree()
2545
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2547
def test_path2id_moved(self):
2548
tree = self.make_branch_and_tree('tree')
2549
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2550
tree.add(['old_parent', 'old_parent/child'],
2551
['old_parent-id', 'child-id'])
2552
preview = TransformPreview(tree)
2553
self.addCleanup(preview.finalize)
2554
new_parent = preview.new_directory('new_parent', preview.root,
2556
preview.adjust_path('child', new_parent,
2557
preview.trans_id_file_id('child-id'))
2558
preview_tree = preview.get_preview_tree()
2559
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2560
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2562
def test_path2id_renamed_parent(self):
2563
tree = self.make_branch_and_tree('tree')
2564
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2565
tree.add(['old_name', 'old_name/child'],
2566
['parent-id', 'child-id'])
2567
preview = TransformPreview(tree)
2568
self.addCleanup(preview.finalize)
2569
preview.adjust_path('new_name', preview.root,
2570
preview.trans_id_file_id('parent-id'))
2571
preview_tree = preview.get_preview_tree()
2572
self.assertIs(None, preview_tree.path2id('old_name/child'))
2573
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2575
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2576
preview_tree = tt.get_preview_tree()
2577
preview_result = list(preview_tree.iter_entries_by_dir(
2581
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2582
self.assertEqual(actual_result, preview_result)
2584
def test_iter_entries_by_dir_new(self):
2585
tree = self.make_branch_and_tree('tree')
2586
tt = TreeTransform(tree)
2587
tt.new_file('new', tt.root, 'contents', 'new-id')
2588
self.assertMatchingIterEntries(tt)
2590
def test_iter_entries_by_dir_deleted(self):
2591
tree = self.make_branch_and_tree('tree')
2592
self.build_tree(['tree/deleted'])
2593
tree.add('deleted', 'deleted-id')
2594
tt = TreeTransform(tree)
2595
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2596
self.assertMatchingIterEntries(tt)
2598
def test_iter_entries_by_dir_unversioned(self):
2599
tree = self.make_branch_and_tree('tree')
2600
self.build_tree(['tree/removed'])
2601
tree.add('removed', 'removed-id')
2602
tt = TreeTransform(tree)
2603
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2604
self.assertMatchingIterEntries(tt)
2606
def test_iter_entries_by_dir_moved(self):
2607
tree = self.make_branch_and_tree('tree')
2608
self.build_tree(['tree/moved', 'tree/new_parent/'])
2609
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2610
tt = TreeTransform(tree)
2611
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2612
tt.trans_id_file_id('moved-id'))
2613
self.assertMatchingIterEntries(tt)
2615
def test_iter_entries_by_dir_specific_file_ids(self):
2616
tree = self.make_branch_and_tree('tree')
2617
tree.set_root_id('tree-root-id')
2618
self.build_tree(['tree/parent/', 'tree/parent/child'])
2619
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2620
tt = TreeTransform(tree)
2621
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2623
def test_symlink_content_summary(self):
2624
self.requireFeature(SymlinkFeature)
2625
preview = self.get_empty_preview()
2626
preview.new_symlink('path', preview.root, 'target', 'path-id')
2627
summary = preview.get_preview_tree().path_content_summary('path')
2628
self.assertEqual(('symlink', None, None, 'target'), summary)
2630
def test_missing_content_summary(self):
2631
preview = self.get_empty_preview()
2632
summary = preview.get_preview_tree().path_content_summary('path')
2633
self.assertEqual(('missing', None, None, None), summary)
2635
def test_deleted_content_summary(self):
2636
tree = self.make_branch_and_tree('tree')
2637
self.build_tree(['tree/path/'])
2639
preview = TransformPreview(tree)
2640
self.addCleanup(preview.finalize)
2641
preview.delete_contents(preview.trans_id_tree_path('path'))
2642
summary = preview.get_preview_tree().path_content_summary('path')
2643
self.assertEqual(('missing', None, None, None), summary)
2645
def test_file_content_summary_executable(self):
2646
preview = self.get_empty_preview()
2647
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2648
preview.set_executability(True, path_id)
2649
summary = preview.get_preview_tree().path_content_summary('path')
2650
self.assertEqual(4, len(summary))
2651
self.assertEqual('file', summary[0])
2652
# size must be known
2653
self.assertEqual(len('contents'), summary[1])
2655
self.assertEqual(True, summary[2])
2656
# will not have hash (not cheap to determine)
2657
self.assertIs(None, summary[3])
2659
def test_change_executability(self):
2660
tree = self.make_branch_and_tree('tree')
2661
self.build_tree(['tree/path'])
2663
preview = TransformPreview(tree)
2664
self.addCleanup(preview.finalize)
2665
path_id = preview.trans_id_tree_path('path')
2666
preview.set_executability(True, path_id)
2667
summary = preview.get_preview_tree().path_content_summary('path')
2668
self.assertEqual(True, summary[2])
2670
def test_file_content_summary_non_exec(self):
2671
preview = self.get_empty_preview()
2672
preview.new_file('path', preview.root, 'contents', 'path-id')
2673
summary = preview.get_preview_tree().path_content_summary('path')
2674
self.assertEqual(4, len(summary))
2675
self.assertEqual('file', summary[0])
2676
# size must be known
2677
self.assertEqual(len('contents'), summary[1])
2679
self.assertEqual(False, summary[2])
2680
# will not have hash (not cheap to determine)
2681
self.assertIs(None, summary[3])
2683
def test_dir_content_summary(self):
2684
preview = self.get_empty_preview()
2685
preview.new_directory('path', preview.root, 'path-id')
2686
summary = preview.get_preview_tree().path_content_summary('path')
2687
self.assertEqual(('directory', None, None, None), summary)
2689
def test_tree_content_summary(self):
2690
preview = self.get_empty_preview()
2691
path = preview.new_directory('path', preview.root, 'path-id')
2692
preview.set_tree_reference('rev-1', path)
2693
summary = preview.get_preview_tree().path_content_summary('path')
2694
self.assertEqual(4, len(summary))
2695
self.assertEqual('tree-reference', summary[0])
2697
def test_annotate(self):
2698
tree = self.make_branch_and_tree('tree')
2699
self.build_tree_contents([('tree/file', 'a\n')])
2700
tree.add('file', 'file-id')
2701
tree.commit('a', rev_id='one')
2702
self.build_tree_contents([('tree/file', 'a\nb\n')])
2703
preview = TransformPreview(tree)
2704
self.addCleanup(preview.finalize)
2705
file_trans_id = preview.trans_id_file_id('file-id')
2706
preview.delete_contents(file_trans_id)
2707
preview.create_file('a\nb\nc\n', file_trans_id)
2708
preview_tree = preview.get_preview_tree()
2714
annotation = preview_tree.annotate_iter('file-id', 'me:')
2715
self.assertEqual(expected, annotation)
2717
def test_annotate_missing(self):
2718
preview = self.get_empty_preview()
2719
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2720
preview_tree = preview.get_preview_tree()
2726
annotation = preview_tree.annotate_iter('file-id', 'me:')
2727
self.assertEqual(expected, annotation)
2729
def test_annotate_rename(self):
2730
tree = self.make_branch_and_tree('tree')
2731
self.build_tree_contents([('tree/file', 'a\n')])
2732
tree.add('file', 'file-id')
2733
tree.commit('a', rev_id='one')
2734
preview = TransformPreview(tree)
2735
self.addCleanup(preview.finalize)
2736
file_trans_id = preview.trans_id_file_id('file-id')
2737
preview.adjust_path('newname', preview.root, file_trans_id)
2738
preview_tree = preview.get_preview_tree()
2742
annotation = preview_tree.annotate_iter('file-id', 'me:')
2743
self.assertEqual(expected, annotation)
2745
def test_annotate_deleted(self):
2746
tree = self.make_branch_and_tree('tree')
2747
self.build_tree_contents([('tree/file', 'a\n')])
2748
tree.add('file', 'file-id')
2749
tree.commit('a', rev_id='one')
2750
self.build_tree_contents([('tree/file', 'a\nb\n')])
2751
preview = TransformPreview(tree)
2752
self.addCleanup(preview.finalize)
2753
file_trans_id = preview.trans_id_file_id('file-id')
2754
preview.delete_contents(file_trans_id)
2755
preview_tree = preview.get_preview_tree()
2756
annotation = preview_tree.annotate_iter('file-id', 'me:')
2757
self.assertIs(None, annotation)
2759
def test_stored_kind(self):
2760
preview = self.get_empty_preview()
2761
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2762
preview_tree = preview.get_preview_tree()
2763
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2765
def test_is_executable(self):
2766
preview = self.get_empty_preview()
2767
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2768
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2769
preview_tree = preview.get_preview_tree()
2770
self.assertEqual(True, preview_tree.is_executable('file-id'))
2772
def test_get_set_parent_ids(self):
2773
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2774
self.assertEqual([], preview_tree.get_parent_ids())
2775
preview_tree.set_parent_ids(['rev-1'])
2776
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2778
def test_plan_file_merge(self):
2779
work_a = self.make_branch_and_tree('wta')
2780
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2781
work_a.add('file', 'file-id')
2782
base_id = work_a.commit('base version')
2783
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2784
preview = TransformPreview(work_a)
2785
self.addCleanup(preview.finalize)
2786
trans_id = preview.trans_id_file_id('file-id')
2787
preview.delete_contents(trans_id)
2788
preview.create_file('b\nc\nd\ne\n', trans_id)
2789
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2790
tree_a = preview.get_preview_tree()
2791
tree_a.set_parent_ids([base_id])
2793
('killed-a', 'a\n'),
2794
('killed-b', 'b\n'),
2795
('unchanged', 'c\n'),
2796
('unchanged', 'd\n'),
2799
], list(tree_a.plan_file_merge('file-id', tree_b)))
2801
def test_plan_file_merge_revision_tree(self):
2802
work_a = self.make_branch_and_tree('wta')
2803
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2804
work_a.add('file', 'file-id')
2805
base_id = work_a.commit('base version')
2806
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2807
preview = TransformPreview(work_a.basis_tree())
2808
self.addCleanup(preview.finalize)
2809
trans_id = preview.trans_id_file_id('file-id')
2810
preview.delete_contents(trans_id)
2811
preview.create_file('b\nc\nd\ne\n', trans_id)
2812
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2813
tree_a = preview.get_preview_tree()
2814
tree_a.set_parent_ids([base_id])
2816
('killed-a', 'a\n'),
2817
('killed-b', 'b\n'),
2818
('unchanged', 'c\n'),
2819
('unchanged', 'd\n'),
2822
], list(tree_a.plan_file_merge('file-id', tree_b)))
2824
def test_walkdirs(self):
2825
preview = self.get_empty_preview()
2826
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2827
# FIXME: new_directory should mark root.
2828
preview.fixup_new_roots()
2829
preview_tree = preview.get_preview_tree()
2830
file_trans_id = preview.new_file('a', preview.root, 'contents',
2832
expected = [(('', 'tree-root'),
2833
[('a', 'a', 'file', None, 'a-id', 'file')])]
2834
self.assertEqual(expected, list(preview_tree.walkdirs()))
2836
def test_extras(self):
2837
work_tree = self.make_branch_and_tree('tree')
2838
self.build_tree(['tree/removed-file', 'tree/existing-file',
2839
'tree/not-removed-file'])
2840
work_tree.add(['removed-file', 'not-removed-file'])
2841
preview = TransformPreview(work_tree)
2842
self.addCleanup(preview.finalize)
2843
preview.new_file('new-file', preview.root, 'contents')
2844
preview.new_file('new-versioned-file', preview.root, 'contents',
2846
tree = preview.get_preview_tree()
2847
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2848
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2851
def test_merge_into_preview(self):
2852
work_tree = self.make_branch_and_tree('tree')
2853
self.build_tree_contents([('tree/file','b\n')])
2854
work_tree.add('file', 'file-id')
2855
work_tree.commit('first commit')
2856
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2857
self.build_tree_contents([('child/file','b\nc\n')])
2858
child_tree.commit('child commit')
2859
child_tree.lock_write()
2860
self.addCleanup(child_tree.unlock)
2861
work_tree.lock_write()
2862
self.addCleanup(work_tree.unlock)
2863
preview = TransformPreview(work_tree)
2864
self.addCleanup(preview.finalize)
2865
file_trans_id = preview.trans_id_file_id('file-id')
2866
preview.delete_contents(file_trans_id)
2867
preview.create_file('a\nb\n', file_trans_id)
2868
preview_tree = preview.get_preview_tree()
2869
merger = Merger.from_revision_ids(None, preview_tree,
2870
child_tree.branch.last_revision(),
2871
other_branch=child_tree.branch,
2872
tree_branch=work_tree.branch)
2873
merger.merge_type = Merge3Merger
2874
tt = merger.make_merger().make_preview_transform()
2875
self.addCleanup(tt.finalize)
2876
final_tree = tt.get_preview_tree()
2877
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2879
def test_merge_preview_into_workingtree(self):
2880
tree = self.make_branch_and_tree('tree')
2881
tree.set_root_id('TREE_ROOT')
2882
tt = TransformPreview(tree)
2883
self.addCleanup(tt.finalize)
2884
tt.new_file('name', tt.root, 'content', 'file-id')
2885
tree2 = self.make_branch_and_tree('tree2')
2886
tree2.set_root_id('TREE_ROOT')
2887
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2888
None, tree.basis_tree())
2889
merger.merge_type = Merge3Merger
2892
def test_merge_preview_into_workingtree_handles_conflicts(self):
2893
tree = self.make_branch_and_tree('tree')
2894
self.build_tree_contents([('tree/foo', 'bar')])
2895
tree.add('foo', 'foo-id')
2897
tt = TransformPreview(tree)
2898
self.addCleanup(tt.finalize)
2899
trans_id = tt.trans_id_file_id('foo-id')
2900
tt.delete_contents(trans_id)
2901
tt.create_file('baz', trans_id)
2902
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2903
self.build_tree_contents([('tree2/foo', 'qux')])
2905
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2906
pb, tree.basis_tree())
2907
merger.merge_type = Merge3Merger
2910
def test_is_executable(self):
2911
tree = self.make_branch_and_tree('tree')
2912
preview = TransformPreview(tree)
2913
self.addCleanup(preview.finalize)
2914
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2915
preview_tree = preview.get_preview_tree()
2916
self.assertEqual(False, preview_tree.is_executable('baz-id',
2918
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2920
def test_commit_preview_tree(self):
2921
tree = self.make_branch_and_tree('tree')
2922
rev_id = tree.commit('rev1')
2923
tree.branch.lock_write()
2924
self.addCleanup(tree.branch.unlock)
2925
tt = TransformPreview(tree)
2926
tt.new_file('file', tt.root, 'contents', 'file_id')
2927
self.addCleanup(tt.finalize)
2928
preview = tt.get_preview_tree()
2929
preview.set_parent_ids([rev_id])
2930
builder = tree.branch.get_commit_builder([rev_id])
2931
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2932
builder.finish_inventory()
2933
rev2_id = builder.commit('rev2')
2934
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2935
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2937
def test_ascii_limbo_paths(self):
2938
self.requireFeature(tests.UnicodeFilenameFeature)
2939
branch = self.make_branch('any')
2940
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2941
tt = TransformPreview(tree)
2942
self.addCleanup(tt.finalize)
2943
foo_id = tt.new_directory('', ROOT_PARENT)
2944
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2945
limbo_path = tt._limbo_name(bar_id)
2946
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2949
class FakeSerializer(object):
2950
"""Serializer implementation that simply returns the input.
2952
The input is returned in the order used by pack.ContainerPushParser.
2955
def bytes_record(bytes, names):
2959
class TestSerializeTransform(tests.TestCaseWithTransport):
2961
_test_needs_features = [tests.UnicodeFilenameFeature]
2963
def get_preview(self, tree=None):
2965
tree = self.make_branch_and_tree('tree')
2966
tt = TransformPreview(tree)
2967
self.addCleanup(tt.finalize)
2970
def assertSerializesTo(self, expected, tt):
2971
records = list(tt.serialize(FakeSerializer()))
2972
self.assertEqual(expected, records)
2975
def default_attribs():
2980
'_new_executability': {},
2982
'_tree_path_ids': {'': 'new-0'},
2984
'_removed_contents': [],
2985
'_non_present_ids': {},
2988
def make_records(self, attribs, contents):
2990
(((('attribs'),),), bencode.bencode(attribs))]
2991
records.extend([(((n, k),), c) for n, k, c in contents])
2994
def creation_records(self):
2995
attribs = self.default_attribs()
2996
attribs['_id_number'] = 3
2997
attribs['_new_name'] = {
2998
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2999
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3000
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3001
attribs['_new_executability'] = {'new-1': 1}
3003
('new-1', 'file', 'i 1\nbar\n'),
3004
('new-2', 'directory', ''),
3006
return self.make_records(attribs, contents)
3008
def test_serialize_creation(self):
3009
tt = self.get_preview()
3010
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3011
tt.new_directory('qux', tt.root, 'quxx')
3012
self.assertSerializesTo(self.creation_records(), tt)
3014
def test_deserialize_creation(self):
3015
tt = self.get_preview()
3016
tt.deserialize(iter(self.creation_records()))
3017
self.assertEqual(3, tt._id_number)
3018
self.assertEqual({'new-1': u'foo\u1234',
3019
'new-2': 'qux'}, tt._new_name)
3020
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3021
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3022
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3023
self.assertEqual({'new-1': True}, tt._new_executability)
3024
self.assertEqual({'new-1': 'file',
3025
'new-2': 'directory'}, tt._new_contents)
3026
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3028
foo_content = foo_limbo.read()
3031
self.assertEqual('bar', foo_content)
3033
def symlink_creation_records(self):
3034
attribs = self.default_attribs()
3035
attribs['_id_number'] = 2
3036
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3037
attribs['_new_parent'] = {'new-1': 'new-0'}
3038
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3039
return self.make_records(attribs, contents)
3041
def test_serialize_symlink_creation(self):
3042
self.requireFeature(tests.SymlinkFeature)
3043
tt = self.get_preview()
3044
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3045
self.assertSerializesTo(self.symlink_creation_records(), tt)
3047
def test_deserialize_symlink_creation(self):
3048
self.requireFeature(tests.SymlinkFeature)
3049
tt = self.get_preview()
3050
tt.deserialize(iter(self.symlink_creation_records()))
3051
abspath = tt._limbo_name('new-1')
3052
foo_content = osutils.readlink(abspath)
3053
self.assertEqual(u'bar\u1234', foo_content)
3055
def make_destruction_preview(self):
3056
tree = self.make_branch_and_tree('.')
3057
self.build_tree([u'foo\u1234', 'bar'])
3058
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3059
return self.get_preview(tree)
3061
def destruction_records(self):
3062
attribs = self.default_attribs()
3063
attribs['_id_number'] = 3
3064
attribs['_removed_id'] = ['new-1']
3065
attribs['_removed_contents'] = ['new-2']
3066
attribs['_tree_path_ids'] = {
3068
u'foo\u1234'.encode('utf-8'): 'new-1',
3071
return self.make_records(attribs, [])
3073
def test_serialize_destruction(self):
3074
tt = self.make_destruction_preview()
3075
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3076
tt.unversion_file(foo_trans_id)
3077
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3078
tt.delete_contents(bar_trans_id)
3079
self.assertSerializesTo(self.destruction_records(), tt)
3081
def test_deserialize_destruction(self):
3082
tt = self.make_destruction_preview()
3083
tt.deserialize(iter(self.destruction_records()))
3084
self.assertEqual({u'foo\u1234': 'new-1',
3086
'': tt.root}, tt._tree_path_ids)
3087
self.assertEqual({'new-1': u'foo\u1234',
3089
tt.root: ''}, tt._tree_id_paths)
3090
self.assertEqual(set(['new-1']), tt._removed_id)
3091
self.assertEqual(set(['new-2']), tt._removed_contents)
3093
def missing_records(self):
3094
attribs = self.default_attribs()
3095
attribs['_id_number'] = 2
3096
attribs['_non_present_ids'] = {
3098
return self.make_records(attribs, [])
3100
def test_serialize_missing(self):
3101
tt = self.get_preview()
3102
boo_trans_id = tt.trans_id_file_id('boo')
3103
self.assertSerializesTo(self.missing_records(), tt)
3105
def test_deserialize_missing(self):
3106
tt = self.get_preview()
3107
tt.deserialize(iter(self.missing_records()))
3108
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3110
def make_modification_preview(self):
3111
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3112
LINES_TWO = 'z\nbb\nx\ndd\n'
3113
tree = self.make_branch_and_tree('tree')
3114
self.build_tree_contents([('tree/file', LINES_ONE)])
3115
tree.add('file', 'file-id')
3116
return self.get_preview(tree), LINES_TWO
3118
def modification_records(self):
3119
attribs = self.default_attribs()
3120
attribs['_id_number'] = 2
3121
attribs['_tree_path_ids'] = {
3124
attribs['_removed_contents'] = ['new-1']
3125
contents = [('new-1', 'file',
3126
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3127
return self.make_records(attribs, contents)
3129
def test_serialize_modification(self):
3130
tt, LINES = self.make_modification_preview()
3131
trans_id = tt.trans_id_file_id('file-id')
3132
tt.delete_contents(trans_id)
3133
tt.create_file(LINES, trans_id)
3134
self.assertSerializesTo(self.modification_records(), tt)
3136
def test_deserialize_modification(self):
3137
tt, LINES = self.make_modification_preview()
3138
tt.deserialize(iter(self.modification_records()))
3139
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3141
def make_kind_change_preview(self):
3142
LINES = 'a\nb\nc\nd\n'
3143
tree = self.make_branch_and_tree('tree')
3144
self.build_tree(['tree/foo/'])
3145
tree.add('foo', 'foo-id')
3146
return self.get_preview(tree), LINES
3148
def kind_change_records(self):
3149
attribs = self.default_attribs()
3150
attribs['_id_number'] = 2
3151
attribs['_tree_path_ids'] = {
3154
attribs['_removed_contents'] = ['new-1']
3155
contents = [('new-1', 'file',
3156
'i 4\na\nb\nc\nd\n\n')]
3157
return self.make_records(attribs, contents)
3159
def test_serialize_kind_change(self):
3160
tt, LINES = self.make_kind_change_preview()
3161
trans_id = tt.trans_id_file_id('foo-id')
3162
tt.delete_contents(trans_id)
3163
tt.create_file(LINES, trans_id)
3164
self.assertSerializesTo(self.kind_change_records(), tt)
3166
def test_deserialize_kind_change(self):
3167
tt, LINES = self.make_kind_change_preview()
3168
tt.deserialize(iter(self.kind_change_records()))
3169
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3171
def make_add_contents_preview(self):
3172
LINES = 'a\nb\nc\nd\n'
3173
tree = self.make_branch_and_tree('tree')
3174
self.build_tree(['tree/foo'])
3176
os.unlink('tree/foo')
3177
return self.get_preview(tree), LINES
3179
def add_contents_records(self):
3180
attribs = self.default_attribs()
3181
attribs['_id_number'] = 2
3182
attribs['_tree_path_ids'] = {
3185
contents = [('new-1', 'file',
3186
'i 4\na\nb\nc\nd\n\n')]
3187
return self.make_records(attribs, contents)
3189
def test_serialize_add_contents(self):
3190
tt, LINES = self.make_add_contents_preview()
3191
trans_id = tt.trans_id_tree_path('foo')
3192
tt.create_file(LINES, trans_id)
3193
self.assertSerializesTo(self.add_contents_records(), tt)
3195
def test_deserialize_add_contents(self):
3196
tt, LINES = self.make_add_contents_preview()
3197
tt.deserialize(iter(self.add_contents_records()))
3198
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3200
def test_get_parents_lines(self):
3201
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3202
LINES_TWO = 'z\nbb\nx\ndd\n'
3203
tree = self.make_branch_and_tree('tree')
3204
self.build_tree_contents([('tree/file', LINES_ONE)])
3205
tree.add('file', 'file-id')
3206
tt = self.get_preview(tree)
3207
trans_id = tt.trans_id_tree_path('file')
3208
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3209
tt._get_parents_lines(trans_id))
3211
def test_get_parents_texts(self):
3212
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3213
LINES_TWO = 'z\nbb\nx\ndd\n'
3214
tree = self.make_branch_and_tree('tree')
3215
self.build_tree_contents([('tree/file', LINES_ONE)])
3216
tree.add('file', 'file-id')
3217
tt = self.get_preview(tree)
3218
trans_id = tt.trans_id_tree_path('file')
3219
self.assertEqual((LINES_ONE,),
3220
tt._get_parents_texts(trans_id))