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
# make the file and directory readonly in the hope this will prevent
864
osutils.make_readonly(self.wt.abspath('first-dir'))
865
osutils.make_readonly(self.wt.abspath('myfile'))
866
# now transform to rename
867
rename_transform, root_id = self.get_transform()
868
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
869
dir_id = rename_transform.trans_id_file_id('first-id')
870
rename_transform.adjust_path('newname', dir_id, file_trans_id)
871
e = self.assertRaises(errors.TransformRenameFailed,
872
rename_transform.apply)
874
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
875
# to .../first-dir/newname: [Errno 13] Permission denied"
876
# so the first filename is not visible in it; we expect a strerror but
877
# it may vary per OS and language so it's not checked here
878
self.assertContainsRe(str(e),
879
"Failed to rename .*first-dir.newname:")
881
def test_set_executability_order(self):
882
"""Ensure that executability behaves the same, no matter what order.
884
- create file and set executability simultaneously
885
- create file and set executability afterward
886
- unsetting the executability of a file whose executability has not been
887
declared should throw an exception (this may happen when a
888
merge attempts to create a file with a duplicate ID)
890
transform, root = self.get_transform()
893
self.addCleanup(wt.unlock)
894
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
896
sac = transform.new_file('set_after_creation', root,
897
'Set after creation', 'sac')
898
transform.set_executability(True, sac)
899
uws = transform.new_file('unset_without_set', root, 'Unset badly',
901
self.assertRaises(KeyError, transform.set_executability, None, uws)
903
self.assertTrue(wt.is_executable('soc'))
904
self.assertTrue(wt.is_executable('sac'))
906
def test_preserve_mode(self):
907
"""File mode is preserved when replacing content"""
908
if sys.platform == 'win32':
909
raise TestSkipped('chmod has no effect on win32')
910
transform, root = self.get_transform()
911
transform.new_file('file1', root, 'contents', 'file1-id', True)
914
self.addCleanup(self.wt.unlock)
915
self.assertTrue(self.wt.is_executable('file1-id'))
916
transform, root = self.get_transform()
917
file1_id = transform.trans_id_tree_file_id('file1-id')
918
transform.delete_contents(file1_id)
919
transform.create_file('contents2', file1_id)
921
self.assertTrue(self.wt.is_executable('file1-id'))
923
def test__set_mode_stats_correctly(self):
924
"""_set_mode stats to determine file mode."""
925
if sys.platform == 'win32':
926
raise TestSkipped('chmod has no effect on win32')
930
def instrumented_stat(path):
931
stat_paths.append(path)
932
return real_stat(path)
934
transform, root = self.get_transform()
936
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
937
file_id='bar-id-1', executable=False)
940
transform, root = self.get_transform()
941
bar1_id = transform.trans_id_tree_path('bar')
942
bar2_id = transform.trans_id_tree_path('bar2')
944
os.stat = instrumented_stat
945
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
950
bar1_abspath = self.wt.abspath('bar')
951
self.assertEqual([bar1_abspath], stat_paths)
953
def test_iter_changes(self):
954
self.wt.set_root_id('eert_toor')
955
transform, root = self.get_transform()
956
transform.new_file('old', root, 'blah', 'id-1', True)
958
transform, root = self.get_transform()
960
self.assertEqual([], list(transform.iter_changes()))
961
old = transform.trans_id_tree_file_id('id-1')
962
transform.unversion_file(old)
963
self.assertEqual([('id-1', ('old', None), False, (True, False),
964
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
965
(True, True))], list(transform.iter_changes()))
966
transform.new_directory('new', root, 'id-1')
967
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
968
('eert_toor', 'eert_toor'), ('old', 'new'),
969
('file', 'directory'),
970
(True, False))], list(transform.iter_changes()))
974
def test_iter_changes_new(self):
975
self.wt.set_root_id('eert_toor')
976
transform, root = self.get_transform()
977
transform.new_file('old', root, 'blah')
979
transform, root = self.get_transform()
981
old = transform.trans_id_tree_path('old')
982
transform.version_file('id-1', old)
983
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
984
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
985
(False, False))], list(transform.iter_changes()))
989
def test_iter_changes_modifications(self):
990
self.wt.set_root_id('eert_toor')
991
transform, root = self.get_transform()
992
transform.new_file('old', root, 'blah', 'id-1')
993
transform.new_file('new', root, 'blah')
994
transform.new_directory('subdir', root, 'subdir-id')
996
transform, root = self.get_transform()
998
old = transform.trans_id_tree_path('old')
999
subdir = transform.trans_id_tree_file_id('subdir-id')
1000
new = transform.trans_id_tree_path('new')
1001
self.assertEqual([], list(transform.iter_changes()))
1004
transform.delete_contents(old)
1005
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1006
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1007
(False, False))], list(transform.iter_changes()))
1010
transform.create_file('blah', old)
1011
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1012
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1013
(False, False))], list(transform.iter_changes()))
1014
transform.cancel_deletion(old)
1015
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1016
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1017
(False, False))], list(transform.iter_changes()))
1018
transform.cancel_creation(old)
1020
# move file_id to a different file
1021
self.assertEqual([], list(transform.iter_changes()))
1022
transform.unversion_file(old)
1023
transform.version_file('id-1', new)
1024
transform.adjust_path('old', root, new)
1025
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1026
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1027
(False, False))], list(transform.iter_changes()))
1028
transform.cancel_versioning(new)
1029
transform._removed_id = set()
1032
self.assertEqual([], list(transform.iter_changes()))
1033
transform.set_executability(True, old)
1034
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1035
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1036
(False, True))], list(transform.iter_changes()))
1037
transform.set_executability(None, old)
1040
self.assertEqual([], list(transform.iter_changes()))
1041
transform.adjust_path('new', root, old)
1042
transform._new_parent = {}
1043
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1044
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1045
(False, False))], list(transform.iter_changes()))
1046
transform._new_name = {}
1049
self.assertEqual([], list(transform.iter_changes()))
1050
transform.adjust_path('new', subdir, old)
1051
transform._new_name = {}
1052
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1053
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1054
('file', 'file'), (False, False))],
1055
list(transform.iter_changes()))
1056
transform._new_path = {}
1059
transform.finalize()
1061
def test_iter_changes_modified_bleed(self):
1062
self.wt.set_root_id('eert_toor')
1063
"""Modified flag should not bleed from one change to another"""
1064
# unfortunately, we have no guarantee that file1 (which is modified)
1065
# will be applied before file2. And if it's applied after file2, it
1066
# obviously can't bleed into file2's change output. But for now, it
1068
transform, root = self.get_transform()
1069
transform.new_file('file1', root, 'blah', 'id-1')
1070
transform.new_file('file2', root, 'blah', 'id-2')
1072
transform, root = self.get_transform()
1074
transform.delete_contents(transform.trans_id_file_id('id-1'))
1075
transform.set_executability(True,
1076
transform.trans_id_file_id('id-2'))
1077
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1078
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1079
('file', None), (False, False)),
1080
('id-2', (u'file2', u'file2'), False, (True, True),
1081
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1082
('file', 'file'), (False, True))],
1083
list(transform.iter_changes()))
1085
transform.finalize()
1087
def test_iter_changes_move_missing(self):
1088
"""Test moving ids with no files around"""
1089
self.wt.set_root_id('toor_eert')
1090
# Need two steps because versioning a non-existant file is a conflict.
1091
transform, root = self.get_transform()
1092
transform.new_directory('floater', root, 'floater-id')
1094
transform, root = self.get_transform()
1095
transform.delete_contents(transform.trans_id_tree_path('floater'))
1097
transform, root = self.get_transform()
1098
floater = transform.trans_id_tree_path('floater')
1100
transform.adjust_path('flitter', root, floater)
1101
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1102
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1103
(None, None), (False, False))], list(transform.iter_changes()))
1105
transform.finalize()
1107
def test_iter_changes_pointless(self):
1108
"""Ensure that no-ops are not treated as modifications"""
1109
self.wt.set_root_id('eert_toor')
1110
transform, root = self.get_transform()
1111
transform.new_file('old', root, 'blah', 'id-1')
1112
transform.new_directory('subdir', root, 'subdir-id')
1114
transform, root = self.get_transform()
1116
old = transform.trans_id_tree_path('old')
1117
subdir = transform.trans_id_tree_file_id('subdir-id')
1118
self.assertEqual([], list(transform.iter_changes()))
1119
transform.delete_contents(subdir)
1120
transform.create_directory(subdir)
1121
transform.set_executability(False, old)
1122
transform.unversion_file(old)
1123
transform.version_file('id-1', old)
1124
transform.adjust_path('old', root, old)
1125
self.assertEqual([], list(transform.iter_changes()))
1127
transform.finalize()
1129
def test_rename_count(self):
1130
transform, root = self.get_transform()
1131
transform.new_file('name1', root, 'contents')
1132
self.assertEqual(transform.rename_count, 0)
1134
self.assertEqual(transform.rename_count, 1)
1135
transform2, root = self.get_transform()
1136
transform2.adjust_path('name2', root,
1137
transform2.trans_id_tree_path('name1'))
1138
self.assertEqual(transform2.rename_count, 0)
1140
self.assertEqual(transform2.rename_count, 2)
1142
def test_change_parent(self):
1143
"""Ensure that after we change a parent, the results are still right.
1145
Renames and parent changes on pending transforms can happen as part
1146
of conflict resolution, and are explicitly permitted by the
1149
This test ensures they work correctly with the rename-avoidance
1152
transform, root = self.get_transform()
1153
parent1 = transform.new_directory('parent1', root)
1154
child1 = transform.new_file('child1', parent1, 'contents')
1155
parent2 = transform.new_directory('parent2', root)
1156
transform.adjust_path('child1', parent2, child1)
1158
self.failIfExists(self.wt.abspath('parent1/child1'))
1159
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1160
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1161
# no rename for child1 (counting only renames during apply)
1162
self.failUnlessEqual(2, transform.rename_count)
1164
def test_cancel_parent(self):
1165
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1167
This is like the test_change_parent, except that we cancel the parent
1168
before adjusting the path. The transform must detect that the
1169
directory is non-empty, and move children to safe locations.
1171
transform, root = self.get_transform()
1172
parent1 = transform.new_directory('parent1', root)
1173
child1 = transform.new_file('child1', parent1, 'contents')
1174
child2 = transform.new_file('child2', parent1, 'contents')
1176
transform.cancel_creation(parent1)
1178
self.fail('Failed to move child1 before deleting parent1')
1179
transform.cancel_creation(child2)
1180
transform.create_directory(parent1)
1182
transform.cancel_creation(parent1)
1183
# If the transform incorrectly believes that child2 is still in
1184
# parent1's limbo directory, it will try to rename it and fail
1185
# because was already moved by the first cancel_creation.
1187
self.fail('Transform still thinks child2 is a child of parent1')
1188
parent2 = transform.new_directory('parent2', root)
1189
transform.adjust_path('child1', parent2, child1)
1191
self.failIfExists(self.wt.abspath('parent1'))
1192
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1193
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1194
self.failUnlessEqual(2, transform.rename_count)
1196
def test_adjust_and_cancel(self):
1197
"""Make sure adjust_path keeps track of limbo children properly"""
1198
transform, root = self.get_transform()
1199
parent1 = transform.new_directory('parent1', root)
1200
child1 = transform.new_file('child1', parent1, 'contents')
1201
parent2 = transform.new_directory('parent2', root)
1202
transform.adjust_path('child1', parent2, child1)
1203
transform.cancel_creation(child1)
1205
transform.cancel_creation(parent1)
1206
# if the transform thinks child1 is still in parent1's limbo
1207
# directory, it will attempt to move it and fail.
1209
self.fail('Transform still thinks child1 is a child of parent1')
1210
transform.finalize()
1212
def test_noname_contents(self):
1213
"""TreeTransform should permit deferring naming files."""
1214
transform, root = self.get_transform()
1215
parent = transform.trans_id_file_id('parent-id')
1217
transform.create_directory(parent)
1219
self.fail("Can't handle contents with no name")
1220
transform.finalize()
1222
def test_noname_contents_nested(self):
1223
"""TreeTransform should permit deferring naming files."""
1224
transform, root = self.get_transform()
1225
parent = transform.trans_id_file_id('parent-id')
1227
transform.create_directory(parent)
1229
self.fail("Can't handle contents with no name")
1230
child = transform.new_directory('child', parent)
1231
transform.adjust_path('parent', root, parent)
1233
self.failUnlessExists(self.wt.abspath('parent/child'))
1234
self.assertEqual(1, transform.rename_count)
1236
def test_reuse_name(self):
1237
"""Avoid reusing the same limbo name for different files"""
1238
transform, root = self.get_transform()
1239
parent = transform.new_directory('parent', root)
1240
child1 = transform.new_directory('child', parent)
1242
child2 = transform.new_directory('child', parent)
1244
self.fail('Tranform tried to use the same limbo name twice')
1245
transform.adjust_path('child2', parent, child2)
1247
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1248
# child2 is put into top-level limbo because child1 has already
1249
# claimed the direct limbo path when child2 is created. There is no
1250
# advantage in renaming files once they're in top-level limbo, except
1252
self.assertEqual(2, transform.rename_count)
1254
def test_reuse_when_first_moved(self):
1255
"""Don't avoid direct paths when it is safe to use them"""
1256
transform, root = self.get_transform()
1257
parent = transform.new_directory('parent', root)
1258
child1 = transform.new_directory('child', parent)
1259
transform.adjust_path('child1', parent, child1)
1260
child2 = transform.new_directory('child', parent)
1262
# limbo/new-1 => parent
1263
self.assertEqual(1, transform.rename_count)
1265
def test_reuse_after_cancel(self):
1266
"""Don't avoid direct paths when it is safe to use them"""
1267
transform, root = self.get_transform()
1268
parent2 = transform.new_directory('parent2', root)
1269
child1 = transform.new_directory('child1', parent2)
1270
transform.cancel_creation(parent2)
1271
transform.create_directory(parent2)
1272
child2 = transform.new_directory('child1', parent2)
1273
transform.adjust_path('child2', parent2, child1)
1275
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1276
self.assertEqual(2, transform.rename_count)
1278
def test_finalize_order(self):
1279
"""Finalize must be done in child-to-parent order"""
1280
transform, root = self.get_transform()
1281
parent = transform.new_directory('parent', root)
1282
child = transform.new_directory('child', parent)
1284
transform.finalize()
1286
self.fail('Tried to remove parent before child1')
1288
def test_cancel_with_cancelled_child_should_succeed(self):
1289
transform, root = self.get_transform()
1290
parent = transform.new_directory('parent', root)
1291
child = transform.new_directory('child', parent)
1292
transform.cancel_creation(child)
1293
transform.cancel_creation(parent)
1294
transform.finalize()
1296
def test_rollback_on_directory_clash(self):
1298
wt = self.make_branch_and_tree('.')
1299
tt = TreeTransform(wt) # TreeTransform obtains write lock
1301
foo = tt.new_directory('foo', tt.root)
1302
tt.new_file('bar', foo, 'foobar')
1303
baz = tt.new_directory('baz', tt.root)
1304
tt.new_file('qux', baz, 'quux')
1305
# Ask for a rename 'foo' -> 'baz'
1306
tt.adjust_path('baz', tt.root, foo)
1307
# Lie to tt that we've already resolved all conflicts.
1308
tt.apply(no_conflicts=True)
1312
# The rename will fail because the target directory is not empty (but
1313
# raises FileExists anyway).
1314
err = self.assertRaises(errors.FileExists, tt_helper)
1315
self.assertContainsRe(str(err),
1316
"^File exists: .+/baz")
1318
def test_two_directories_clash(self):
1320
wt = self.make_branch_and_tree('.')
1321
tt = TreeTransform(wt) # TreeTransform obtains write lock
1323
foo_1 = tt.new_directory('foo', tt.root)
1324
tt.new_directory('bar', foo_1)
1325
# Adding the same directory with a different content
1326
foo_2 = tt.new_directory('foo', tt.root)
1327
tt.new_directory('baz', foo_2)
1328
# Lie to tt that we've already resolved all conflicts.
1329
tt.apply(no_conflicts=True)
1333
err = self.assertRaises(errors.FileExists, tt_helper)
1334
self.assertContainsRe(str(err),
1335
"^File exists: .+/foo")
1337
def test_two_directories_clash_finalize(self):
1339
wt = self.make_branch_and_tree('.')
1340
tt = TreeTransform(wt) # TreeTransform obtains write lock
1342
foo_1 = tt.new_directory('foo', tt.root)
1343
tt.new_directory('bar', foo_1)
1344
# Adding the same directory with a different content
1345
foo_2 = tt.new_directory('foo', tt.root)
1346
tt.new_directory('baz', foo_2)
1347
# Lie to tt that we've already resolved all conflicts.
1348
tt.apply(no_conflicts=True)
1352
err = self.assertRaises(errors.FileExists, tt_helper)
1353
self.assertContainsRe(str(err),
1354
"^File exists: .+/foo")
1356
def test_file_to_directory(self):
1357
wt = self.make_branch_and_tree('.')
1358
self.build_tree(['foo'])
1361
tt = TreeTransform(wt)
1362
self.addCleanup(tt.finalize)
1363
foo_trans_id = tt.trans_id_tree_path("foo")
1364
tt.delete_contents(foo_trans_id)
1365
tt.create_directory(foo_trans_id)
1366
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1367
tt.create_file(["aa\n"], bar_trans_id)
1368
tt.version_file("bar-1", bar_trans_id)
1370
self.failUnlessExists("foo/bar")
1373
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1378
changes = wt.changes_from(wt.basis_tree())
1379
self.assertFalse(changes.has_changed(), changes)
1381
def test_file_to_symlink(self):
1382
self.requireFeature(SymlinkFeature)
1383
wt = self.make_branch_and_tree('.')
1384
self.build_tree(['foo'])
1387
tt = TreeTransform(wt)
1388
self.addCleanup(tt.finalize)
1389
foo_trans_id = tt.trans_id_tree_path("foo")
1390
tt.delete_contents(foo_trans_id)
1391
tt.create_symlink("bar", foo_trans_id)
1393
self.failUnlessExists("foo")
1395
self.addCleanup(wt.unlock)
1396
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1399
def test_dir_to_file(self):
1400
wt = self.make_branch_and_tree('.')
1401
self.build_tree(['foo/', 'foo/bar'])
1402
wt.add(['foo', 'foo/bar'])
1404
tt = TreeTransform(wt)
1405
self.addCleanup(tt.finalize)
1406
foo_trans_id = tt.trans_id_tree_path("foo")
1407
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1408
tt.delete_contents(foo_trans_id)
1409
tt.delete_versioned(bar_trans_id)
1410
tt.create_file(["aa\n"], foo_trans_id)
1412
self.failUnlessExists("foo")
1414
self.addCleanup(wt.unlock)
1415
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1418
def test_dir_to_hardlink(self):
1419
self.requireFeature(HardlinkFeature)
1420
wt = self.make_branch_and_tree('.')
1421
self.build_tree(['foo/', 'foo/bar'])
1422
wt.add(['foo', 'foo/bar'])
1424
tt = TreeTransform(wt)
1425
self.addCleanup(tt.finalize)
1426
foo_trans_id = tt.trans_id_tree_path("foo")
1427
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1428
tt.delete_contents(foo_trans_id)
1429
tt.delete_versioned(bar_trans_id)
1430
self.build_tree(['baz'])
1431
tt.create_hardlink("baz", foo_trans_id)
1433
self.failUnlessExists("foo")
1434
self.failUnlessExists("baz")
1436
self.addCleanup(wt.unlock)
1437
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1440
def test_no_final_path(self):
1441
transform, root = self.get_transform()
1442
trans_id = transform.trans_id_file_id('foo')
1443
transform.create_file('bar', trans_id)
1444
transform.cancel_creation(trans_id)
1447
def test_create_from_tree(self):
1448
tree1 = self.make_branch_and_tree('tree1')
1449
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1450
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1451
tree2 = self.make_branch_and_tree('tree2')
1452
tt = TreeTransform(tree2)
1453
foo_trans_id = tt.create_path('foo', tt.root)
1454
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1455
bar_trans_id = tt.create_path('bar', tt.root)
1456
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1458
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1459
self.assertFileEqual('baz', 'tree2/bar')
1461
def test_create_from_tree_bytes(self):
1462
"""Provided lines are used instead of tree content."""
1463
tree1 = self.make_branch_and_tree('tree1')
1464
self.build_tree_contents([('tree1/foo', 'bar'),])
1465
tree1.add('foo', 'foo-id')
1466
tree2 = self.make_branch_and_tree('tree2')
1467
tt = TreeTransform(tree2)
1468
foo_trans_id = tt.create_path('foo', tt.root)
1469
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1471
self.assertFileEqual('qux', 'tree2/foo')
1473
def test_create_from_tree_symlink(self):
1474
self.requireFeature(SymlinkFeature)
1475
tree1 = self.make_branch_and_tree('tree1')
1476
os.symlink('bar', 'tree1/foo')
1477
tree1.add('foo', 'foo-id')
1478
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1479
foo_trans_id = tt.create_path('foo', tt.root)
1480
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1482
self.assertEqual('bar', os.readlink('tree2/foo'))
1485
class TransformGroup(object):
1487
def __init__(self, dirname, root_id):
1490
self.wt = BzrDir.create_standalone_workingtree(dirname)
1491
self.wt.set_root_id(root_id)
1492
self.b = self.wt.branch
1493
self.tt = TreeTransform(self.wt)
1494
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1497
def conflict_text(tree, merge):
1498
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1499
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1502
class TestTransformMerge(TestCaseInTempDir):
1504
def test_text_merge(self):
1505
root_id = generate_ids.gen_root_id()
1506
base = TransformGroup("base", root_id)
1507
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1508
base.tt.new_file('b', base.root, 'b1', 'b')
1509
base.tt.new_file('c', base.root, 'c', 'c')
1510
base.tt.new_file('d', base.root, 'd', 'd')
1511
base.tt.new_file('e', base.root, 'e', 'e')
1512
base.tt.new_file('f', base.root, 'f', 'f')
1513
base.tt.new_directory('g', base.root, 'g')
1514
base.tt.new_directory('h', base.root, 'h')
1516
other = TransformGroup("other", root_id)
1517
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1518
other.tt.new_file('b', other.root, 'b2', 'b')
1519
other.tt.new_file('c', other.root, 'c2', 'c')
1520
other.tt.new_file('d', other.root, 'd', 'd')
1521
other.tt.new_file('e', other.root, 'e2', 'e')
1522
other.tt.new_file('f', other.root, 'f', 'f')
1523
other.tt.new_file('g', other.root, 'g', 'g')
1524
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1525
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1527
this = TransformGroup("this", root_id)
1528
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1529
this.tt.new_file('b', this.root, 'b', 'b')
1530
this.tt.new_file('c', this.root, 'c', 'c')
1531
this.tt.new_file('d', this.root, 'd2', 'd')
1532
this.tt.new_file('e', this.root, 'e2', 'e')
1533
this.tt.new_file('f', this.root, 'f', 'f')
1534
this.tt.new_file('g', this.root, 'g', 'g')
1535
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1536
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1538
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1541
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1542
# three-way text conflict
1543
self.assertEqual(this.wt.get_file('b').read(),
1544
conflict_text('b', 'b2'))
1546
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1548
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1549
# Ambigious clean merge
1550
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1552
self.assertEqual(this.wt.get_file('f').read(), 'f')
1553
# Correct correct results when THIS == OTHER
1554
self.assertEqual(this.wt.get_file('g').read(), 'g')
1555
# Text conflict when THIS & OTHER are text and BASE is dir
1556
self.assertEqual(this.wt.get_file('h').read(),
1557
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1558
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1560
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1562
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1563
self.assertEqual(this.wt.get_file('i').read(),
1564
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1565
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1567
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1569
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1570
modified = ['a', 'b', 'c', 'h', 'i']
1571
merge_modified = this.wt.merge_modified()
1572
self.assertSubset(merge_modified, modified)
1573
self.assertEqual(len(merge_modified), len(modified))
1574
file(this.wt.id2abspath('a'), 'wb').write('booga')
1576
merge_modified = this.wt.merge_modified()
1577
self.assertSubset(merge_modified, modified)
1578
self.assertEqual(len(merge_modified), len(modified))
1582
def test_file_merge(self):
1583
self.requireFeature(SymlinkFeature)
1584
root_id = generate_ids.gen_root_id()
1585
base = TransformGroup("BASE", root_id)
1586
this = TransformGroup("THIS", root_id)
1587
other = TransformGroup("OTHER", root_id)
1588
for tg in this, base, other:
1589
tg.tt.new_directory('a', tg.root, 'a')
1590
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1591
tg.tt.new_file('c', tg.root, 'c', 'c')
1592
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1593
targets = ((base, 'base-e', 'base-f', None, None),
1594
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1595
(other, 'other-e', None, 'other-g', 'other-h'))
1596
for tg, e_target, f_target, g_target, h_target in targets:
1597
for link, target in (('e', e_target), ('f', f_target),
1598
('g', g_target), ('h', h_target)):
1599
if target is not None:
1600
tg.tt.new_symlink(link, tg.root, target, link)
1602
for tg in this, base, other:
1604
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1605
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1606
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1607
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1608
for suffix in ('THIS', 'BASE', 'OTHER'):
1609
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1610
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1611
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1612
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1613
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1614
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1615
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1616
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1617
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1618
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1619
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1620
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1621
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1622
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1624
def test_filename_merge(self):
1625
root_id = generate_ids.gen_root_id()
1626
base = TransformGroup("BASE", root_id)
1627
this = TransformGroup("THIS", root_id)
1628
other = TransformGroup("OTHER", root_id)
1629
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1630
for t in [base, this, other]]
1631
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1632
for t in [base, this, other]]
1633
base.tt.new_directory('c', base_a, 'c')
1634
this.tt.new_directory('c1', this_a, 'c')
1635
other.tt.new_directory('c', other_b, 'c')
1637
base.tt.new_directory('d', base_a, 'd')
1638
this.tt.new_directory('d1', this_b, 'd')
1639
other.tt.new_directory('d', other_a, 'd')
1641
base.tt.new_directory('e', base_a, 'e')
1642
this.tt.new_directory('e', this_a, 'e')
1643
other.tt.new_directory('e1', other_b, 'e')
1645
base.tt.new_directory('f', base_a, 'f')
1646
this.tt.new_directory('f1', this_b, 'f')
1647
other.tt.new_directory('f1', other_b, 'f')
1649
for tg in [this, base, other]:
1651
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1652
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1653
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1654
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1655
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1657
def test_filename_merge_conflicts(self):
1658
root_id = generate_ids.gen_root_id()
1659
base = TransformGroup("BASE", root_id)
1660
this = TransformGroup("THIS", root_id)
1661
other = TransformGroup("OTHER", root_id)
1662
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1663
for t in [base, this, other]]
1664
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1665
for t in [base, this, other]]
1667
base.tt.new_file('g', base_a, 'g', 'g')
1668
other.tt.new_file('g1', other_b, 'g1', 'g')
1670
base.tt.new_file('h', base_a, 'h', 'h')
1671
this.tt.new_file('h1', this_b, 'h1', 'h')
1673
base.tt.new_file('i', base.root, 'i', 'i')
1674
other.tt.new_directory('i1', this_b, 'i')
1676
for tg in [this, base, other]:
1678
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1680
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1681
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1682
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1683
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1684
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1685
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1686
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1689
class TestBuildTree(tests.TestCaseWithTransport):
1691
def test_build_tree_with_symlinks(self):
1692
self.requireFeature(SymlinkFeature)
1694
a = BzrDir.create_standalone_workingtree('a')
1696
file('a/foo/bar', 'wb').write('contents')
1697
os.symlink('a/foo/bar', 'a/foo/baz')
1698
a.add(['foo', 'foo/bar', 'foo/baz'])
1699
a.commit('initial commit')
1700
b = BzrDir.create_standalone_workingtree('b')
1701
basis = a.basis_tree()
1703
self.addCleanup(basis.unlock)
1704
build_tree(basis, b)
1705
self.assertIs(os.path.isdir('b/foo'), True)
1706
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1707
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1709
def test_build_with_references(self):
1710
tree = self.make_branch_and_tree('source',
1711
format='dirstate-with-subtree')
1712
subtree = self.make_branch_and_tree('source/subtree',
1713
format='dirstate-with-subtree')
1714
tree.add_reference(subtree)
1715
tree.commit('a revision')
1716
tree.branch.create_checkout('target')
1717
self.failUnlessExists('target')
1718
self.failUnlessExists('target/subtree')
1720
def test_file_conflict_handling(self):
1721
"""Ensure that when building trees, conflict handling is done"""
1722
source = self.make_branch_and_tree('source')
1723
target = self.make_branch_and_tree('target')
1724
self.build_tree(['source/file', 'target/file'])
1725
source.add('file', 'new-file')
1726
source.commit('added file')
1727
build_tree(source.basis_tree(), target)
1728
self.assertEqual([DuplicateEntry('Moved existing file to',
1729
'file.moved', 'file', None, 'new-file')],
1731
target2 = self.make_branch_and_tree('target2')
1732
target_file = file('target2/file', 'wb')
1734
source_file = file('source/file', 'rb')
1736
target_file.write(source_file.read())
1741
build_tree(source.basis_tree(), target2)
1742
self.assertEqual([], target2.conflicts())
1744
def test_symlink_conflict_handling(self):
1745
"""Ensure that when building trees, conflict handling is done"""
1746
self.requireFeature(SymlinkFeature)
1747
source = self.make_branch_and_tree('source')
1748
os.symlink('foo', 'source/symlink')
1749
source.add('symlink', 'new-symlink')
1750
source.commit('added file')
1751
target = self.make_branch_and_tree('target')
1752
os.symlink('bar', 'target/symlink')
1753
build_tree(source.basis_tree(), target)
1754
self.assertEqual([DuplicateEntry('Moved existing file to',
1755
'symlink.moved', 'symlink', None, 'new-symlink')],
1757
target = self.make_branch_and_tree('target2')
1758
os.symlink('foo', 'target2/symlink')
1759
build_tree(source.basis_tree(), target)
1760
self.assertEqual([], target.conflicts())
1762
def test_directory_conflict_handling(self):
1763
"""Ensure that when building trees, conflict handling is done"""
1764
source = self.make_branch_and_tree('source')
1765
target = self.make_branch_and_tree('target')
1766
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1767
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1768
source.commit('added file')
1769
build_tree(source.basis_tree(), target)
1770
self.assertEqual([], target.conflicts())
1771
self.failUnlessExists('target/dir1/file')
1773
# Ensure contents are merged
1774
target = self.make_branch_and_tree('target2')
1775
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1776
build_tree(source.basis_tree(), target)
1777
self.assertEqual([], target.conflicts())
1778
self.failUnlessExists('target2/dir1/file2')
1779
self.failUnlessExists('target2/dir1/file')
1781
# Ensure new contents are suppressed for existing branches
1782
target = self.make_branch_and_tree('target3')
1783
self.make_branch('target3/dir1')
1784
self.build_tree(['target3/dir1/file2'])
1785
build_tree(source.basis_tree(), target)
1786
self.failIfExists('target3/dir1/file')
1787
self.failUnlessExists('target3/dir1/file2')
1788
self.failUnlessExists('target3/dir1.diverted/file')
1789
self.assertEqual([DuplicateEntry('Diverted to',
1790
'dir1.diverted', 'dir1', 'new-dir1', None)],
1793
target = self.make_branch_and_tree('target4')
1794
self.build_tree(['target4/dir1/'])
1795
self.make_branch('target4/dir1/file')
1796
build_tree(source.basis_tree(), target)
1797
self.failUnlessExists('target4/dir1/file')
1798
self.assertEqual('directory', file_kind('target4/dir1/file'))
1799
self.failUnlessExists('target4/dir1/file.diverted')
1800
self.assertEqual([DuplicateEntry('Diverted to',
1801
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1804
def test_mixed_conflict_handling(self):
1805
"""Ensure that when building trees, conflict handling is done"""
1806
source = self.make_branch_and_tree('source')
1807
target = self.make_branch_and_tree('target')
1808
self.build_tree(['source/name', 'target/name/'])
1809
source.add('name', 'new-name')
1810
source.commit('added file')
1811
build_tree(source.basis_tree(), target)
1812
self.assertEqual([DuplicateEntry('Moved existing file to',
1813
'name.moved', 'name', None, 'new-name')], target.conflicts())
1815
def test_raises_in_populated(self):
1816
source = self.make_branch_and_tree('source')
1817
self.build_tree(['source/name'])
1819
source.commit('added name')
1820
target = self.make_branch_and_tree('target')
1821
self.build_tree(['target/name'])
1823
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1824
build_tree, source.basis_tree(), target)
1826
def test_build_tree_rename_count(self):
1827
source = self.make_branch_and_tree('source')
1828
self.build_tree(['source/file1', 'source/dir1/'])
1829
source.add(['file1', 'dir1'])
1830
source.commit('add1')
1831
target1 = self.make_branch_and_tree('target1')
1832
transform_result = build_tree(source.basis_tree(), target1)
1833
self.assertEqual(2, transform_result.rename_count)
1835
self.build_tree(['source/dir1/file2'])
1836
source.add(['dir1/file2'])
1837
source.commit('add3')
1838
target2 = self.make_branch_and_tree('target2')
1839
transform_result = build_tree(source.basis_tree(), target2)
1840
# children of non-root directories should not be renamed
1841
self.assertEqual(2, transform_result.rename_count)
1843
def create_ab_tree(self):
1844
"""Create a committed test tree with two files"""
1845
source = self.make_branch_and_tree('source')
1846
self.build_tree_contents([('source/file1', 'A')])
1847
self.build_tree_contents([('source/file2', 'B')])
1848
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1849
source.commit('commit files')
1851
self.addCleanup(source.unlock)
1854
def test_build_tree_accelerator_tree(self):
1855
source = self.create_ab_tree()
1856
self.build_tree_contents([('source/file2', 'C')])
1858
real_source_get_file = source.get_file
1859
def get_file(file_id, path=None):
1860
calls.append(file_id)
1861
return real_source_get_file(file_id, path)
1862
source.get_file = get_file
1863
target = self.make_branch_and_tree('target')
1864
revision_tree = source.basis_tree()
1865
revision_tree.lock_read()
1866
self.addCleanup(revision_tree.unlock)
1867
build_tree(revision_tree, target, source)
1868
self.assertEqual(['file1-id'], calls)
1870
self.addCleanup(target.unlock)
1871
self.assertEqual([], list(target.iter_changes(revision_tree)))
1873
def test_build_tree_accelerator_tree_missing_file(self):
1874
source = self.create_ab_tree()
1875
os.unlink('source/file1')
1876
source.remove(['file2'])
1877
target = self.make_branch_and_tree('target')
1878
revision_tree = source.basis_tree()
1879
revision_tree.lock_read()
1880
self.addCleanup(revision_tree.unlock)
1881
build_tree(revision_tree, target, source)
1883
self.addCleanup(target.unlock)
1884
self.assertEqual([], list(target.iter_changes(revision_tree)))
1886
def test_build_tree_accelerator_wrong_kind(self):
1887
self.requireFeature(SymlinkFeature)
1888
source = self.make_branch_and_tree('source')
1889
self.build_tree_contents([('source/file1', '')])
1890
self.build_tree_contents([('source/file2', '')])
1891
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1892
source.commit('commit files')
1893
os.unlink('source/file2')
1894
self.build_tree_contents([('source/file2/', 'C')])
1895
os.unlink('source/file1')
1896
os.symlink('file2', 'source/file1')
1898
real_source_get_file = source.get_file
1899
def get_file(file_id, path=None):
1900
calls.append(file_id)
1901
return real_source_get_file(file_id, path)
1902
source.get_file = get_file
1903
target = self.make_branch_and_tree('target')
1904
revision_tree = source.basis_tree()
1905
revision_tree.lock_read()
1906
self.addCleanup(revision_tree.unlock)
1907
build_tree(revision_tree, target, source)
1908
self.assertEqual([], calls)
1910
self.addCleanup(target.unlock)
1911
self.assertEqual([], list(target.iter_changes(revision_tree)))
1913
def test_build_tree_hardlink(self):
1914
self.requireFeature(HardlinkFeature)
1915
source = self.create_ab_tree()
1916
target = self.make_branch_and_tree('target')
1917
revision_tree = source.basis_tree()
1918
revision_tree.lock_read()
1919
self.addCleanup(revision_tree.unlock)
1920
build_tree(revision_tree, target, source, hardlink=True)
1922
self.addCleanup(target.unlock)
1923
self.assertEqual([], list(target.iter_changes(revision_tree)))
1924
source_stat = os.stat('source/file1')
1925
target_stat = os.stat('target/file1')
1926
self.assertEqual(source_stat, target_stat)
1928
# Explicitly disallowing hardlinks should prevent them.
1929
target2 = self.make_branch_and_tree('target2')
1930
build_tree(revision_tree, target2, source, hardlink=False)
1932
self.addCleanup(target2.unlock)
1933
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1934
source_stat = os.stat('source/file1')
1935
target2_stat = os.stat('target2/file1')
1936
self.assertNotEqual(source_stat, target2_stat)
1938
def test_build_tree_accelerator_tree_moved(self):
1939
source = self.make_branch_and_tree('source')
1940
self.build_tree_contents([('source/file1', 'A')])
1941
source.add(['file1'], ['file1-id'])
1942
source.commit('commit files')
1943
source.rename_one('file1', 'file2')
1945
self.addCleanup(source.unlock)
1946
target = self.make_branch_and_tree('target')
1947
revision_tree = source.basis_tree()
1948
revision_tree.lock_read()
1949
self.addCleanup(revision_tree.unlock)
1950
build_tree(revision_tree, target, source)
1952
self.addCleanup(target.unlock)
1953
self.assertEqual([], list(target.iter_changes(revision_tree)))
1955
def test_build_tree_hardlinks_preserve_execute(self):
1956
self.requireFeature(HardlinkFeature)
1957
source = self.create_ab_tree()
1958
tt = TreeTransform(source)
1959
trans_id = tt.trans_id_tree_file_id('file1-id')
1960
tt.set_executability(True, trans_id)
1962
self.assertTrue(source.is_executable('file1-id'))
1963
target = self.make_branch_and_tree('target')
1964
revision_tree = source.basis_tree()
1965
revision_tree.lock_read()
1966
self.addCleanup(revision_tree.unlock)
1967
build_tree(revision_tree, target, source, hardlink=True)
1969
self.addCleanup(target.unlock)
1970
self.assertEqual([], list(target.iter_changes(revision_tree)))
1971
self.assertTrue(source.is_executable('file1-id'))
1973
def install_rot13_content_filter(self, pattern):
1975
# self.addCleanup(filters._reset_registry, filters._reset_registry())
1976
# below, but that looks a bit... hard to read even if it's exactly
1978
original_registry = filters._reset_registry()
1979
def restore_registry():
1980
filters._reset_registry(original_registry)
1981
self.addCleanup(restore_registry)
1982
def rot13(chunks, context=None):
1983
return [''.join(chunks).encode('rot13')]
1984
rot13filter = filters.ContentFilter(rot13, rot13)
1985
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1986
os.mkdir(self.test_home_dir + '/.bazaar')
1987
rules_filename = self.test_home_dir + '/.bazaar/rules'
1988
f = open(rules_filename, 'wb')
1989
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1991
def uninstall_rules():
1992
os.remove(rules_filename)
1994
self.addCleanup(uninstall_rules)
1997
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1998
"""build_tree will not hardlink files that have content filtering rules
1999
applied to them (but will still hardlink other files from the same tree
2002
self.requireFeature(HardlinkFeature)
2003
self.install_rot13_content_filter('file1')
2004
source = self.create_ab_tree()
2005
target = self.make_branch_and_tree('target')
2006
revision_tree = source.basis_tree()
2007
revision_tree.lock_read()
2008
self.addCleanup(revision_tree.unlock)
2009
build_tree(revision_tree, target, source, hardlink=True)
2011
self.addCleanup(target.unlock)
2012
self.assertEqual([], list(target.iter_changes(revision_tree)))
2013
source_stat = os.stat('source/file1')
2014
target_stat = os.stat('target/file1')
2015
self.assertNotEqual(source_stat, target_stat)
2016
source_stat = os.stat('source/file2')
2017
target_stat = os.stat('target/file2')
2018
self.assertEqualStat(source_stat, target_stat)
2020
def test_case_insensitive_build_tree_inventory(self):
2021
if (tests.CaseInsensitiveFilesystemFeature.available()
2022
or tests.CaseInsCasePresFilenameFeature.available()):
2023
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2024
source = self.make_branch_and_tree('source')
2025
self.build_tree(['source/file', 'source/FILE'])
2026
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2027
source.commit('added files')
2028
# Don't try this at home, kids!
2029
# Force the tree to report that it is case insensitive
2030
target = self.make_branch_and_tree('target')
2031
target.case_sensitive = False
2032
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2033
self.assertEqual('file.moved', target.id2path('lower-id'))
2034
self.assertEqual('FILE', target.id2path('upper-id'))
2037
class TestCommitTransform(tests.TestCaseWithTransport):
2039
def get_branch(self):
2040
tree = self.make_branch_and_tree('tree')
2042
self.addCleanup(tree.unlock)
2043
tree.commit('empty commit')
2046
def get_branch_and_transform(self):
2047
branch = self.get_branch()
2048
tt = TransformPreview(branch.basis_tree())
2049
self.addCleanup(tt.finalize)
2052
def test_commit_wrong_basis(self):
2053
branch = self.get_branch()
2054
basis = branch.repository.revision_tree(
2055
_mod_revision.NULL_REVISION)
2056
tt = TransformPreview(basis)
2057
self.addCleanup(tt.finalize)
2058
e = self.assertRaises(ValueError, tt.commit, branch, '')
2059
self.assertEqual('TreeTransform not based on branch basis: null:',
2062
def test_empy_commit(self):
2063
branch, tt = self.get_branch_and_transform()
2064
rev = tt.commit(branch, 'my message')
2065
self.assertEqual(2, branch.revno())
2066
repo = branch.repository
2067
self.assertEqual('my message', repo.get_revision(rev).message)
2069
def test_merge_parents(self):
2070
branch, tt = self.get_branch_and_transform()
2071
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2072
self.assertEqual(['rev1b', 'rev1c'],
2073
branch.basis_tree().get_parent_ids()[1:])
2075
def test_first_commit(self):
2076
branch = self.make_branch('branch')
2078
self.addCleanup(branch.unlock)
2079
tt = TransformPreview(branch.basis_tree())
2080
self.addCleanup(tt.finalize)
2081
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2082
rev = tt.commit(branch, 'my message')
2083
self.assertEqual([], branch.basis_tree().get_parent_ids())
2084
self.assertNotEqual(_mod_revision.NULL_REVISION,
2085
branch.last_revision())
2087
def test_first_commit_with_merge_parents(self):
2088
branch = self.make_branch('branch')
2090
self.addCleanup(branch.unlock)
2091
tt = TransformPreview(branch.basis_tree())
2092
self.addCleanup(tt.finalize)
2093
e = self.assertRaises(ValueError, tt.commit, branch,
2094
'my message', ['rev1b-id'])
2095
self.assertEqual('Cannot supply merge parents for first commit.',
2097
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2099
def test_add_files(self):
2100
branch, tt = self.get_branch_and_transform()
2101
tt.new_file('file', tt.root, 'contents', 'file-id')
2102
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2103
if SymlinkFeature.available():
2104
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2105
rev = tt.commit(branch, 'message')
2106
tree = branch.basis_tree()
2107
self.assertEqual('file', tree.id2path('file-id'))
2108
self.assertEqual('contents', tree.get_file_text('file-id'))
2109
self.assertEqual('dir', tree.id2path('dir-id'))
2110
if SymlinkFeature.available():
2111
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2112
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2114
def test_add_unversioned(self):
2115
branch, tt = self.get_branch_and_transform()
2116
tt.new_file('file', tt.root, 'contents')
2117
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2118
'message', strict=True)
2120
def test_modify_strict(self):
2121
branch, tt = self.get_branch_and_transform()
2122
tt.new_file('file', tt.root, 'contents', 'file-id')
2123
tt.commit(branch, 'message', strict=True)
2124
tt = TransformPreview(branch.basis_tree())
2125
self.addCleanup(tt.finalize)
2126
trans_id = tt.trans_id_file_id('file-id')
2127
tt.delete_contents(trans_id)
2128
tt.create_file('contents', trans_id)
2129
tt.commit(branch, 'message', strict=True)
2131
def test_commit_malformed(self):
2132
"""Committing a malformed transform should raise an exception.
2134
In this case, we are adding a file without adding its parent.
2136
branch, tt = self.get_branch_and_transform()
2137
parent_id = tt.trans_id_file_id('parent-id')
2138
tt.new_file('file', parent_id, 'contents', 'file-id')
2139
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2142
def test_commit_rich_revision_data(self):
2143
branch, tt = self.get_branch_and_transform()
2144
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2145
committer='me <me@example.com>',
2146
revprops={'foo': 'bar'}, revision_id='revid-1',
2147
authors=['Author1 <author1@example.com>',
2148
'Author2 <author2@example.com>',
2150
self.assertEqual('revid-1', rev_id)
2151
revision = branch.repository.get_revision(rev_id)
2152
self.assertEqual(1, revision.timestamp)
2153
self.assertEqual(43201, revision.timezone)
2154
self.assertEqual('me <me@example.com>', revision.committer)
2155
self.assertEqual(['Author1 <author1@example.com>',
2156
'Author2 <author2@example.com>'],
2157
revision.get_apparent_authors())
2158
del revision.properties['authors']
2159
self.assertEqual({'foo': 'bar',
2160
'branch-nick': 'tree'},
2161
revision.properties)
2163
def test_no_explicit_revprops(self):
2164
branch, tt = self.get_branch_and_transform()
2165
rev_id = tt.commit(branch, 'message', authors=[
2166
'Author1 <author1@example.com>',
2167
'Author2 <author2@example.com>', ])
2168
revision = branch.repository.get_revision(rev_id)
2169
self.assertEqual(['Author1 <author1@example.com>',
2170
'Author2 <author2@example.com>'],
2171
revision.get_apparent_authors())
2172
self.assertEqual('tree', revision.properties['branch-nick'])
2175
class MockTransform(object):
2177
def has_named_child(self, by_parent, parent_id, name):
2178
for child_id in by_parent[parent_id]:
2182
elif name == "name.~%s~" % child_id:
2187
class MockEntry(object):
2189
object.__init__(self)
2193
class TestGetBackupName(TestCase):
2194
def test_get_backup_name(self):
2195
tt = MockTransform()
2196
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2197
self.assertEqual(name, 'name.~1~')
2198
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2199
self.assertEqual(name, 'name.~2~')
2200
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2201
self.assertEqual(name, 'name.~1~')
2202
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2203
self.assertEqual(name, 'name.~1~')
2204
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2205
self.assertEqual(name, 'name.~4~')
2208
class TestFileMover(tests.TestCaseWithTransport):
2210
def test_file_mover(self):
2211
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2212
mover = _FileMover()
2213
mover.rename('a', 'q')
2214
self.failUnlessExists('q')
2215
self.failIfExists('a')
2216
self.failUnlessExists('q/b')
2217
self.failUnlessExists('c')
2218
self.failUnlessExists('c/d')
2220
def test_pre_delete_rollback(self):
2221
self.build_tree(['a/'])
2222
mover = _FileMover()
2223
mover.pre_delete('a', 'q')
2224
self.failUnlessExists('q')
2225
self.failIfExists('a')
2227
self.failIfExists('q')
2228
self.failUnlessExists('a')
2230
def test_apply_deletions(self):
2231
self.build_tree(['a/', 'b/'])
2232
mover = _FileMover()
2233
mover.pre_delete('a', 'q')
2234
mover.pre_delete('b', 'r')
2235
self.failUnlessExists('q')
2236
self.failUnlessExists('r')
2237
self.failIfExists('a')
2238
self.failIfExists('b')
2239
mover.apply_deletions()
2240
self.failIfExists('q')
2241
self.failIfExists('r')
2242
self.failIfExists('a')
2243
self.failIfExists('b')
2245
def test_file_mover_rollback(self):
2246
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2247
mover = _FileMover()
2248
mover.rename('c/d', 'c/f')
2249
mover.rename('c/e', 'c/d')
2251
mover.rename('a', 'c')
2252
except errors.FileExists, e:
2254
self.failUnlessExists('a')
2255
self.failUnlessExists('c/d')
2258
class Bogus(Exception):
2262
class TestTransformRollback(tests.TestCaseWithTransport):
2264
class ExceptionFileMover(_FileMover):
2266
def __init__(self, bad_source=None, bad_target=None):
2267
_FileMover.__init__(self)
2268
self.bad_source = bad_source
2269
self.bad_target = bad_target
2271
def rename(self, source, target):
2272
if (self.bad_source is not None and
2273
source.endswith(self.bad_source)):
2275
elif (self.bad_target is not None and
2276
target.endswith(self.bad_target)):
2279
_FileMover.rename(self, source, target)
2281
def test_rollback_rename(self):
2282
tree = self.make_branch_and_tree('.')
2283
self.build_tree(['a/', 'a/b'])
2284
tt = TreeTransform(tree)
2285
self.addCleanup(tt.finalize)
2286
a_id = tt.trans_id_tree_path('a')
2287
tt.adjust_path('c', tt.root, a_id)
2288
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2289
self.assertRaises(Bogus, tt.apply,
2290
_mover=self.ExceptionFileMover(bad_source='a'))
2291
self.failUnlessExists('a')
2292
self.failUnlessExists('a/b')
2294
self.failUnlessExists('c')
2295
self.failUnlessExists('c/d')
2297
def test_rollback_rename_into_place(self):
2298
tree = self.make_branch_and_tree('.')
2299
self.build_tree(['a/', 'a/b'])
2300
tt = TreeTransform(tree)
2301
self.addCleanup(tt.finalize)
2302
a_id = tt.trans_id_tree_path('a')
2303
tt.adjust_path('c', tt.root, a_id)
2304
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2305
self.assertRaises(Bogus, tt.apply,
2306
_mover=self.ExceptionFileMover(bad_target='c/d'))
2307
self.failUnlessExists('a')
2308
self.failUnlessExists('a/b')
2310
self.failUnlessExists('c')
2311
self.failUnlessExists('c/d')
2313
def test_rollback_deletion(self):
2314
tree = self.make_branch_and_tree('.')
2315
self.build_tree(['a/', 'a/b'])
2316
tt = TreeTransform(tree)
2317
self.addCleanup(tt.finalize)
2318
a_id = tt.trans_id_tree_path('a')
2319
tt.delete_contents(a_id)
2320
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2321
self.assertRaises(Bogus, tt.apply,
2322
_mover=self.ExceptionFileMover(bad_target='d'))
2323
self.failUnlessExists('a')
2324
self.failUnlessExists('a/b')
2326
def test_resolve_no_parent(self):
2327
wt = self.make_branch_and_tree('.')
2328
tt = TreeTransform(wt)
2329
self.addCleanup(tt.finalize)
2330
parent = tt.trans_id_file_id('parent-id')
2331
tt.new_file('file', parent, 'Contents')
2332
resolve_conflicts(tt)
2335
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2336
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2338
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2339
('', ''), ('directory', 'directory'), (False, None))
2342
class TestTransformPreview(tests.TestCaseWithTransport):
2344
def create_tree(self):
2345
tree = self.make_branch_and_tree('.')
2346
self.build_tree_contents([('a', 'content 1')])
2347
tree.set_root_id('TREE_ROOT')
2348
tree.add('a', 'a-id')
2349
tree.commit('rev1', rev_id='rev1')
2350
return tree.branch.repository.revision_tree('rev1')
2352
def get_empty_preview(self):
2353
repository = self.make_repository('repo')
2354
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2355
preview = TransformPreview(tree)
2356
self.addCleanup(preview.finalize)
2359
def test_transform_preview(self):
2360
revision_tree = self.create_tree()
2361
preview = TransformPreview(revision_tree)
2362
self.addCleanup(preview.finalize)
2364
def test_transform_preview_tree(self):
2365
revision_tree = self.create_tree()
2366
preview = TransformPreview(revision_tree)
2367
self.addCleanup(preview.finalize)
2368
preview.get_preview_tree()
2370
def test_transform_new_file(self):
2371
revision_tree = self.create_tree()
2372
preview = TransformPreview(revision_tree)
2373
self.addCleanup(preview.finalize)
2374
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2375
preview_tree = preview.get_preview_tree()
2376
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2378
preview_tree.get_file('file2-id').read(), 'content B\n')
2380
def test_diff_preview_tree(self):
2381
revision_tree = self.create_tree()
2382
preview = TransformPreview(revision_tree)
2383
self.addCleanup(preview.finalize)
2384
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2385
preview_tree = preview.get_preview_tree()
2387
show_diff_trees(revision_tree, preview_tree, out)
2388
lines = out.getvalue().splitlines()
2389
self.assertEqual(lines[0], "=== added file 'file2'")
2390
# 3 lines of diff administrivia
2391
self.assertEqual(lines[4], "+content B")
2393
def test_transform_conflicts(self):
2394
revision_tree = self.create_tree()
2395
preview = TransformPreview(revision_tree)
2396
self.addCleanup(preview.finalize)
2397
preview.new_file('a', preview.root, 'content 2')
2398
resolve_conflicts(preview)
2399
trans_id = preview.trans_id_file_id('a-id')
2400
self.assertEqual('a.moved', preview.final_name(trans_id))
2402
def get_tree_and_preview_tree(self):
2403
revision_tree = self.create_tree()
2404
preview = TransformPreview(revision_tree)
2405
self.addCleanup(preview.finalize)
2406
a_trans_id = preview.trans_id_file_id('a-id')
2407
preview.delete_contents(a_trans_id)
2408
preview.create_file('b content', a_trans_id)
2409
preview_tree = preview.get_preview_tree()
2410
return revision_tree, preview_tree
2412
def test_iter_changes(self):
2413
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2414
root = revision_tree.inventory.root.file_id
2415
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2416
(root, root), ('a', 'a'), ('file', 'file'),
2418
list(preview_tree.iter_changes(revision_tree)))
2420
def test_include_unchanged_succeeds(self):
2421
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2422
changes = preview_tree.iter_changes(revision_tree,
2423
include_unchanged=True)
2424
root = revision_tree.inventory.root.file_id
2426
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2428
def test_specific_files(self):
2429
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2430
changes = preview_tree.iter_changes(revision_tree,
2431
specific_files=[''])
2432
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2434
def test_want_unversioned(self):
2435
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2436
changes = preview_tree.iter_changes(revision_tree,
2437
want_unversioned=True)
2438
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2440
def test_ignore_extra_trees_no_specific_files(self):
2441
# extra_trees is harmless without specific_files, so we'll silently
2442
# accept it, even though we won't use it.
2443
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2444
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2446
def test_ignore_require_versioned_no_specific_files(self):
2447
# require_versioned is meaningless without specific_files.
2448
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2449
preview_tree.iter_changes(revision_tree, require_versioned=False)
2451
def test_ignore_pb(self):
2452
# pb could be supported, but TT.iter_changes doesn't support it.
2453
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2454
preview_tree.iter_changes(revision_tree)
2456
def test_kind(self):
2457
revision_tree = self.create_tree()
2458
preview = TransformPreview(revision_tree)
2459
self.addCleanup(preview.finalize)
2460
preview.new_file('file', preview.root, 'contents', 'file-id')
2461
preview.new_directory('directory', preview.root, 'dir-id')
2462
preview_tree = preview.get_preview_tree()
2463
self.assertEqual('file', preview_tree.kind('file-id'))
2464
self.assertEqual('directory', preview_tree.kind('dir-id'))
2466
def test_get_file_mtime(self):
2467
preview = self.get_empty_preview()
2468
file_trans_id = preview.new_file('file', preview.root, 'contents',
2470
limbo_path = preview._limbo_name(file_trans_id)
2471
preview_tree = preview.get_preview_tree()
2472
self.assertEqual(os.stat(limbo_path).st_mtime,
2473
preview_tree.get_file_mtime('file-id'))
2475
def test_get_file_mtime_renamed(self):
2476
work_tree = self.make_branch_and_tree('tree')
2477
self.build_tree(['tree/file'])
2478
work_tree.add('file', 'file-id')
2479
preview = TransformPreview(work_tree)
2480
self.addCleanup(preview.finalize)
2481
file_trans_id = preview.trans_id_tree_file_id('file-id')
2482
preview.adjust_path('renamed', preview.root, file_trans_id)
2483
preview_tree = preview.get_preview_tree()
2484
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2485
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2487
def test_get_file(self):
2488
preview = self.get_empty_preview()
2489
preview.new_file('file', preview.root, 'contents', 'file-id')
2490
preview_tree = preview.get_preview_tree()
2491
tree_file = preview_tree.get_file('file-id')
2493
self.assertEqual('contents', tree_file.read())
2497
def test_get_symlink_target(self):
2498
self.requireFeature(SymlinkFeature)
2499
preview = self.get_empty_preview()
2500
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2501
preview_tree = preview.get_preview_tree()
2502
self.assertEqual('target',
2503
preview_tree.get_symlink_target('symlink-id'))
2505
def test_all_file_ids(self):
2506
tree = self.make_branch_and_tree('tree')
2507
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2508
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2509
preview = TransformPreview(tree)
2510
self.addCleanup(preview.finalize)
2511
preview.unversion_file(preview.trans_id_file_id('b-id'))
2512
c_trans_id = preview.trans_id_file_id('c-id')
2513
preview.unversion_file(c_trans_id)
2514
preview.version_file('c-id', c_trans_id)
2515
preview_tree = preview.get_preview_tree()
2516
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2517
preview_tree.all_file_ids())
2519
def test_path2id_deleted_unchanged(self):
2520
tree = self.make_branch_and_tree('tree')
2521
self.build_tree(['tree/unchanged', 'tree/deleted'])
2522
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2523
preview = TransformPreview(tree)
2524
self.addCleanup(preview.finalize)
2525
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2526
preview_tree = preview.get_preview_tree()
2527
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2528
self.assertIs(None, preview_tree.path2id('deleted'))
2530
def test_path2id_created(self):
2531
tree = self.make_branch_and_tree('tree')
2532
self.build_tree(['tree/unchanged'])
2533
tree.add(['unchanged'], ['unchanged-id'])
2534
preview = TransformPreview(tree)
2535
self.addCleanup(preview.finalize)
2536
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2537
'contents', 'new-id')
2538
preview_tree = preview.get_preview_tree()
2539
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2541
def test_path2id_moved(self):
2542
tree = self.make_branch_and_tree('tree')
2543
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2544
tree.add(['old_parent', 'old_parent/child'],
2545
['old_parent-id', 'child-id'])
2546
preview = TransformPreview(tree)
2547
self.addCleanup(preview.finalize)
2548
new_parent = preview.new_directory('new_parent', preview.root,
2550
preview.adjust_path('child', new_parent,
2551
preview.trans_id_file_id('child-id'))
2552
preview_tree = preview.get_preview_tree()
2553
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2554
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2556
def test_path2id_renamed_parent(self):
2557
tree = self.make_branch_and_tree('tree')
2558
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2559
tree.add(['old_name', 'old_name/child'],
2560
['parent-id', 'child-id'])
2561
preview = TransformPreview(tree)
2562
self.addCleanup(preview.finalize)
2563
preview.adjust_path('new_name', preview.root,
2564
preview.trans_id_file_id('parent-id'))
2565
preview_tree = preview.get_preview_tree()
2566
self.assertIs(None, preview_tree.path2id('old_name/child'))
2567
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2569
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2570
preview_tree = tt.get_preview_tree()
2571
preview_result = list(preview_tree.iter_entries_by_dir(
2575
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2576
self.assertEqual(actual_result, preview_result)
2578
def test_iter_entries_by_dir_new(self):
2579
tree = self.make_branch_and_tree('tree')
2580
tt = TreeTransform(tree)
2581
tt.new_file('new', tt.root, 'contents', 'new-id')
2582
self.assertMatchingIterEntries(tt)
2584
def test_iter_entries_by_dir_deleted(self):
2585
tree = self.make_branch_and_tree('tree')
2586
self.build_tree(['tree/deleted'])
2587
tree.add('deleted', 'deleted-id')
2588
tt = TreeTransform(tree)
2589
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2590
self.assertMatchingIterEntries(tt)
2592
def test_iter_entries_by_dir_unversioned(self):
2593
tree = self.make_branch_and_tree('tree')
2594
self.build_tree(['tree/removed'])
2595
tree.add('removed', 'removed-id')
2596
tt = TreeTransform(tree)
2597
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2598
self.assertMatchingIterEntries(tt)
2600
def test_iter_entries_by_dir_moved(self):
2601
tree = self.make_branch_and_tree('tree')
2602
self.build_tree(['tree/moved', 'tree/new_parent/'])
2603
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2604
tt = TreeTransform(tree)
2605
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2606
tt.trans_id_file_id('moved-id'))
2607
self.assertMatchingIterEntries(tt)
2609
def test_iter_entries_by_dir_specific_file_ids(self):
2610
tree = self.make_branch_and_tree('tree')
2611
tree.set_root_id('tree-root-id')
2612
self.build_tree(['tree/parent/', 'tree/parent/child'])
2613
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2614
tt = TreeTransform(tree)
2615
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2617
def test_symlink_content_summary(self):
2618
self.requireFeature(SymlinkFeature)
2619
preview = self.get_empty_preview()
2620
preview.new_symlink('path', preview.root, 'target', 'path-id')
2621
summary = preview.get_preview_tree().path_content_summary('path')
2622
self.assertEqual(('symlink', None, None, 'target'), summary)
2624
def test_missing_content_summary(self):
2625
preview = self.get_empty_preview()
2626
summary = preview.get_preview_tree().path_content_summary('path')
2627
self.assertEqual(('missing', None, None, None), summary)
2629
def test_deleted_content_summary(self):
2630
tree = self.make_branch_and_tree('tree')
2631
self.build_tree(['tree/path/'])
2633
preview = TransformPreview(tree)
2634
self.addCleanup(preview.finalize)
2635
preview.delete_contents(preview.trans_id_tree_path('path'))
2636
summary = preview.get_preview_tree().path_content_summary('path')
2637
self.assertEqual(('missing', None, None, None), summary)
2639
def test_file_content_summary_executable(self):
2640
preview = self.get_empty_preview()
2641
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2642
preview.set_executability(True, path_id)
2643
summary = preview.get_preview_tree().path_content_summary('path')
2644
self.assertEqual(4, len(summary))
2645
self.assertEqual('file', summary[0])
2646
# size must be known
2647
self.assertEqual(len('contents'), summary[1])
2649
self.assertEqual(True, summary[2])
2650
# will not have hash (not cheap to determine)
2651
self.assertIs(None, summary[3])
2653
def test_change_executability(self):
2654
tree = self.make_branch_and_tree('tree')
2655
self.build_tree(['tree/path'])
2657
preview = TransformPreview(tree)
2658
self.addCleanup(preview.finalize)
2659
path_id = preview.trans_id_tree_path('path')
2660
preview.set_executability(True, path_id)
2661
summary = preview.get_preview_tree().path_content_summary('path')
2662
self.assertEqual(True, summary[2])
2664
def test_file_content_summary_non_exec(self):
2665
preview = self.get_empty_preview()
2666
preview.new_file('path', preview.root, 'contents', 'path-id')
2667
summary = preview.get_preview_tree().path_content_summary('path')
2668
self.assertEqual(4, len(summary))
2669
self.assertEqual('file', summary[0])
2670
# size must be known
2671
self.assertEqual(len('contents'), summary[1])
2673
self.assertEqual(False, summary[2])
2674
# will not have hash (not cheap to determine)
2675
self.assertIs(None, summary[3])
2677
def test_dir_content_summary(self):
2678
preview = self.get_empty_preview()
2679
preview.new_directory('path', preview.root, 'path-id')
2680
summary = preview.get_preview_tree().path_content_summary('path')
2681
self.assertEqual(('directory', None, None, None), summary)
2683
def test_tree_content_summary(self):
2684
preview = self.get_empty_preview()
2685
path = preview.new_directory('path', preview.root, 'path-id')
2686
preview.set_tree_reference('rev-1', path)
2687
summary = preview.get_preview_tree().path_content_summary('path')
2688
self.assertEqual(4, len(summary))
2689
self.assertEqual('tree-reference', summary[0])
2691
def test_annotate(self):
2692
tree = self.make_branch_and_tree('tree')
2693
self.build_tree_contents([('tree/file', 'a\n')])
2694
tree.add('file', 'file-id')
2695
tree.commit('a', rev_id='one')
2696
self.build_tree_contents([('tree/file', 'a\nb\n')])
2697
preview = TransformPreview(tree)
2698
self.addCleanup(preview.finalize)
2699
file_trans_id = preview.trans_id_file_id('file-id')
2700
preview.delete_contents(file_trans_id)
2701
preview.create_file('a\nb\nc\n', file_trans_id)
2702
preview_tree = preview.get_preview_tree()
2708
annotation = preview_tree.annotate_iter('file-id', 'me:')
2709
self.assertEqual(expected, annotation)
2711
def test_annotate_missing(self):
2712
preview = self.get_empty_preview()
2713
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2714
preview_tree = preview.get_preview_tree()
2720
annotation = preview_tree.annotate_iter('file-id', 'me:')
2721
self.assertEqual(expected, annotation)
2723
def test_annotate_rename(self):
2724
tree = self.make_branch_and_tree('tree')
2725
self.build_tree_contents([('tree/file', 'a\n')])
2726
tree.add('file', 'file-id')
2727
tree.commit('a', rev_id='one')
2728
preview = TransformPreview(tree)
2729
self.addCleanup(preview.finalize)
2730
file_trans_id = preview.trans_id_file_id('file-id')
2731
preview.adjust_path('newname', preview.root, file_trans_id)
2732
preview_tree = preview.get_preview_tree()
2736
annotation = preview_tree.annotate_iter('file-id', 'me:')
2737
self.assertEqual(expected, annotation)
2739
def test_annotate_deleted(self):
2740
tree = self.make_branch_and_tree('tree')
2741
self.build_tree_contents([('tree/file', 'a\n')])
2742
tree.add('file', 'file-id')
2743
tree.commit('a', rev_id='one')
2744
self.build_tree_contents([('tree/file', 'a\nb\n')])
2745
preview = TransformPreview(tree)
2746
self.addCleanup(preview.finalize)
2747
file_trans_id = preview.trans_id_file_id('file-id')
2748
preview.delete_contents(file_trans_id)
2749
preview_tree = preview.get_preview_tree()
2750
annotation = preview_tree.annotate_iter('file-id', 'me:')
2751
self.assertIs(None, annotation)
2753
def test_stored_kind(self):
2754
preview = self.get_empty_preview()
2755
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2756
preview_tree = preview.get_preview_tree()
2757
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2759
def test_is_executable(self):
2760
preview = self.get_empty_preview()
2761
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2762
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2763
preview_tree = preview.get_preview_tree()
2764
self.assertEqual(True, preview_tree.is_executable('file-id'))
2766
def test_get_set_parent_ids(self):
2767
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2768
self.assertEqual([], preview_tree.get_parent_ids())
2769
preview_tree.set_parent_ids(['rev-1'])
2770
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2772
def test_plan_file_merge(self):
2773
work_a = self.make_branch_and_tree('wta')
2774
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2775
work_a.add('file', 'file-id')
2776
base_id = work_a.commit('base version')
2777
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2778
preview = TransformPreview(work_a)
2779
self.addCleanup(preview.finalize)
2780
trans_id = preview.trans_id_file_id('file-id')
2781
preview.delete_contents(trans_id)
2782
preview.create_file('b\nc\nd\ne\n', trans_id)
2783
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2784
tree_a = preview.get_preview_tree()
2785
tree_a.set_parent_ids([base_id])
2787
('killed-a', 'a\n'),
2788
('killed-b', 'b\n'),
2789
('unchanged', 'c\n'),
2790
('unchanged', 'd\n'),
2793
], list(tree_a.plan_file_merge('file-id', tree_b)))
2795
def test_plan_file_merge_revision_tree(self):
2796
work_a = self.make_branch_and_tree('wta')
2797
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2798
work_a.add('file', 'file-id')
2799
base_id = work_a.commit('base version')
2800
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2801
preview = TransformPreview(work_a.basis_tree())
2802
self.addCleanup(preview.finalize)
2803
trans_id = preview.trans_id_file_id('file-id')
2804
preview.delete_contents(trans_id)
2805
preview.create_file('b\nc\nd\ne\n', trans_id)
2806
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2807
tree_a = preview.get_preview_tree()
2808
tree_a.set_parent_ids([base_id])
2810
('killed-a', 'a\n'),
2811
('killed-b', 'b\n'),
2812
('unchanged', 'c\n'),
2813
('unchanged', 'd\n'),
2816
], list(tree_a.plan_file_merge('file-id', tree_b)))
2818
def test_walkdirs(self):
2819
preview = self.get_empty_preview()
2820
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2821
# FIXME: new_directory should mark root.
2822
preview.fixup_new_roots()
2823
preview_tree = preview.get_preview_tree()
2824
file_trans_id = preview.new_file('a', preview.root, 'contents',
2826
expected = [(('', 'tree-root'),
2827
[('a', 'a', 'file', None, 'a-id', 'file')])]
2828
self.assertEqual(expected, list(preview_tree.walkdirs()))
2830
def test_extras(self):
2831
work_tree = self.make_branch_and_tree('tree')
2832
self.build_tree(['tree/removed-file', 'tree/existing-file',
2833
'tree/not-removed-file'])
2834
work_tree.add(['removed-file', 'not-removed-file'])
2835
preview = TransformPreview(work_tree)
2836
self.addCleanup(preview.finalize)
2837
preview.new_file('new-file', preview.root, 'contents')
2838
preview.new_file('new-versioned-file', preview.root, 'contents',
2840
tree = preview.get_preview_tree()
2841
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2842
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2845
def test_merge_into_preview(self):
2846
work_tree = self.make_branch_and_tree('tree')
2847
self.build_tree_contents([('tree/file','b\n')])
2848
work_tree.add('file', 'file-id')
2849
work_tree.commit('first commit')
2850
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2851
self.build_tree_contents([('child/file','b\nc\n')])
2852
child_tree.commit('child commit')
2853
child_tree.lock_write()
2854
self.addCleanup(child_tree.unlock)
2855
work_tree.lock_write()
2856
self.addCleanup(work_tree.unlock)
2857
preview = TransformPreview(work_tree)
2858
self.addCleanup(preview.finalize)
2859
file_trans_id = preview.trans_id_file_id('file-id')
2860
preview.delete_contents(file_trans_id)
2861
preview.create_file('a\nb\n', file_trans_id)
2862
preview_tree = preview.get_preview_tree()
2863
merger = Merger.from_revision_ids(None, preview_tree,
2864
child_tree.branch.last_revision(),
2865
other_branch=child_tree.branch,
2866
tree_branch=work_tree.branch)
2867
merger.merge_type = Merge3Merger
2868
tt = merger.make_merger().make_preview_transform()
2869
self.addCleanup(tt.finalize)
2870
final_tree = tt.get_preview_tree()
2871
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2873
def test_merge_preview_into_workingtree(self):
2874
tree = self.make_branch_and_tree('tree')
2875
tree.set_root_id('TREE_ROOT')
2876
tt = TransformPreview(tree)
2877
self.addCleanup(tt.finalize)
2878
tt.new_file('name', tt.root, 'content', 'file-id')
2879
tree2 = self.make_branch_and_tree('tree2')
2880
tree2.set_root_id('TREE_ROOT')
2881
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2882
None, tree.basis_tree())
2883
merger.merge_type = Merge3Merger
2886
def test_merge_preview_into_workingtree_handles_conflicts(self):
2887
tree = self.make_branch_and_tree('tree')
2888
self.build_tree_contents([('tree/foo', 'bar')])
2889
tree.add('foo', 'foo-id')
2891
tt = TransformPreview(tree)
2892
self.addCleanup(tt.finalize)
2893
trans_id = tt.trans_id_file_id('foo-id')
2894
tt.delete_contents(trans_id)
2895
tt.create_file('baz', trans_id)
2896
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2897
self.build_tree_contents([('tree2/foo', 'qux')])
2899
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2900
pb, tree.basis_tree())
2901
merger.merge_type = Merge3Merger
2904
def test_is_executable(self):
2905
tree = self.make_branch_and_tree('tree')
2906
preview = TransformPreview(tree)
2907
self.addCleanup(preview.finalize)
2908
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2909
preview_tree = preview.get_preview_tree()
2910
self.assertEqual(False, preview_tree.is_executable('baz-id',
2912
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2914
def test_commit_preview_tree(self):
2915
tree = self.make_branch_and_tree('tree')
2916
rev_id = tree.commit('rev1')
2917
tree.branch.lock_write()
2918
self.addCleanup(tree.branch.unlock)
2919
tt = TransformPreview(tree)
2920
tt.new_file('file', tt.root, 'contents', 'file_id')
2921
self.addCleanup(tt.finalize)
2922
preview = tt.get_preview_tree()
2923
preview.set_parent_ids([rev_id])
2924
builder = tree.branch.get_commit_builder([rev_id])
2925
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2926
builder.finish_inventory()
2927
rev2_id = builder.commit('rev2')
2928
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2929
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2931
def test_ascii_limbo_paths(self):
2932
self.requireFeature(tests.UnicodeFilenameFeature)
2933
branch = self.make_branch('any')
2934
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2935
tt = TransformPreview(tree)
2936
self.addCleanup(tt.finalize)
2937
foo_id = tt.new_directory('', ROOT_PARENT)
2938
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2939
limbo_path = tt._limbo_name(bar_id)
2940
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2943
class FakeSerializer(object):
2944
"""Serializer implementation that simply returns the input.
2946
The input is returned in the order used by pack.ContainerPushParser.
2949
def bytes_record(bytes, names):
2953
class TestSerializeTransform(tests.TestCaseWithTransport):
2955
_test_needs_features = [tests.UnicodeFilenameFeature]
2957
def get_preview(self, tree=None):
2959
tree = self.make_branch_and_tree('tree')
2960
tt = TransformPreview(tree)
2961
self.addCleanup(tt.finalize)
2964
def assertSerializesTo(self, expected, tt):
2965
records = list(tt.serialize(FakeSerializer()))
2966
self.assertEqual(expected, records)
2969
def default_attribs():
2974
'_new_executability': {},
2976
'_tree_path_ids': {'': 'new-0'},
2978
'_removed_contents': [],
2979
'_non_present_ids': {},
2982
def make_records(self, attribs, contents):
2984
(((('attribs'),),), bencode.bencode(attribs))]
2985
records.extend([(((n, k),), c) for n, k, c in contents])
2988
def creation_records(self):
2989
attribs = self.default_attribs()
2990
attribs['_id_number'] = 3
2991
attribs['_new_name'] = {
2992
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2993
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2994
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2995
attribs['_new_executability'] = {'new-1': 1}
2997
('new-1', 'file', 'i 1\nbar\n'),
2998
('new-2', 'directory', ''),
3000
return self.make_records(attribs, contents)
3002
def test_serialize_creation(self):
3003
tt = self.get_preview()
3004
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3005
tt.new_directory('qux', tt.root, 'quxx')
3006
self.assertSerializesTo(self.creation_records(), tt)
3008
def test_deserialize_creation(self):
3009
tt = self.get_preview()
3010
tt.deserialize(iter(self.creation_records()))
3011
self.assertEqual(3, tt._id_number)
3012
self.assertEqual({'new-1': u'foo\u1234',
3013
'new-2': 'qux'}, tt._new_name)
3014
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3015
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3016
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3017
self.assertEqual({'new-1': True}, tt._new_executability)
3018
self.assertEqual({'new-1': 'file',
3019
'new-2': 'directory'}, tt._new_contents)
3020
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3022
foo_content = foo_limbo.read()
3025
self.assertEqual('bar', foo_content)
3027
def symlink_creation_records(self):
3028
attribs = self.default_attribs()
3029
attribs['_id_number'] = 2
3030
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3031
attribs['_new_parent'] = {'new-1': 'new-0'}
3032
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3033
return self.make_records(attribs, contents)
3035
def test_serialize_symlink_creation(self):
3036
self.requireFeature(tests.SymlinkFeature)
3037
tt = self.get_preview()
3038
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3039
self.assertSerializesTo(self.symlink_creation_records(), tt)
3041
def test_deserialize_symlink_creation(self):
3042
self.requireFeature(tests.SymlinkFeature)
3043
tt = self.get_preview()
3044
tt.deserialize(iter(self.symlink_creation_records()))
3045
abspath = tt._limbo_name('new-1')
3046
foo_content = osutils.readlink(abspath)
3047
self.assertEqual(u'bar\u1234', foo_content)
3049
def make_destruction_preview(self):
3050
tree = self.make_branch_and_tree('.')
3051
self.build_tree([u'foo\u1234', 'bar'])
3052
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3053
return self.get_preview(tree)
3055
def destruction_records(self):
3056
attribs = self.default_attribs()
3057
attribs['_id_number'] = 3
3058
attribs['_removed_id'] = ['new-1']
3059
attribs['_removed_contents'] = ['new-2']
3060
attribs['_tree_path_ids'] = {
3062
u'foo\u1234'.encode('utf-8'): 'new-1',
3065
return self.make_records(attribs, [])
3067
def test_serialize_destruction(self):
3068
tt = self.make_destruction_preview()
3069
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3070
tt.unversion_file(foo_trans_id)
3071
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3072
tt.delete_contents(bar_trans_id)
3073
self.assertSerializesTo(self.destruction_records(), tt)
3075
def test_deserialize_destruction(self):
3076
tt = self.make_destruction_preview()
3077
tt.deserialize(iter(self.destruction_records()))
3078
self.assertEqual({u'foo\u1234': 'new-1',
3080
'': tt.root}, tt._tree_path_ids)
3081
self.assertEqual({'new-1': u'foo\u1234',
3083
tt.root: ''}, tt._tree_id_paths)
3084
self.assertEqual(set(['new-1']), tt._removed_id)
3085
self.assertEqual(set(['new-2']), tt._removed_contents)
3087
def missing_records(self):
3088
attribs = self.default_attribs()
3089
attribs['_id_number'] = 2
3090
attribs['_non_present_ids'] = {
3092
return self.make_records(attribs, [])
3094
def test_serialize_missing(self):
3095
tt = self.get_preview()
3096
boo_trans_id = tt.trans_id_file_id('boo')
3097
self.assertSerializesTo(self.missing_records(), tt)
3099
def test_deserialize_missing(self):
3100
tt = self.get_preview()
3101
tt.deserialize(iter(self.missing_records()))
3102
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3104
def make_modification_preview(self):
3105
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3106
LINES_TWO = 'z\nbb\nx\ndd\n'
3107
tree = self.make_branch_and_tree('tree')
3108
self.build_tree_contents([('tree/file', LINES_ONE)])
3109
tree.add('file', 'file-id')
3110
return self.get_preview(tree), LINES_TWO
3112
def modification_records(self):
3113
attribs = self.default_attribs()
3114
attribs['_id_number'] = 2
3115
attribs['_tree_path_ids'] = {
3118
attribs['_removed_contents'] = ['new-1']
3119
contents = [('new-1', 'file',
3120
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3121
return self.make_records(attribs, contents)
3123
def test_serialize_modification(self):
3124
tt, LINES = self.make_modification_preview()
3125
trans_id = tt.trans_id_file_id('file-id')
3126
tt.delete_contents(trans_id)
3127
tt.create_file(LINES, trans_id)
3128
self.assertSerializesTo(self.modification_records(), tt)
3130
def test_deserialize_modification(self):
3131
tt, LINES = self.make_modification_preview()
3132
tt.deserialize(iter(self.modification_records()))
3133
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3135
def make_kind_change_preview(self):
3136
LINES = 'a\nb\nc\nd\n'
3137
tree = self.make_branch_and_tree('tree')
3138
self.build_tree(['tree/foo/'])
3139
tree.add('foo', 'foo-id')
3140
return self.get_preview(tree), LINES
3142
def kind_change_records(self):
3143
attribs = self.default_attribs()
3144
attribs['_id_number'] = 2
3145
attribs['_tree_path_ids'] = {
3148
attribs['_removed_contents'] = ['new-1']
3149
contents = [('new-1', 'file',
3150
'i 4\na\nb\nc\nd\n\n')]
3151
return self.make_records(attribs, contents)
3153
def test_serialize_kind_change(self):
3154
tt, LINES = self.make_kind_change_preview()
3155
trans_id = tt.trans_id_file_id('foo-id')
3156
tt.delete_contents(trans_id)
3157
tt.create_file(LINES, trans_id)
3158
self.assertSerializesTo(self.kind_change_records(), tt)
3160
def test_deserialize_kind_change(self):
3161
tt, LINES = self.make_kind_change_preview()
3162
tt.deserialize(iter(self.kind_change_records()))
3163
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3165
def make_add_contents_preview(self):
3166
LINES = 'a\nb\nc\nd\n'
3167
tree = self.make_branch_and_tree('tree')
3168
self.build_tree(['tree/foo'])
3170
os.unlink('tree/foo')
3171
return self.get_preview(tree), LINES
3173
def add_contents_records(self):
3174
attribs = self.default_attribs()
3175
attribs['_id_number'] = 2
3176
attribs['_tree_path_ids'] = {
3179
contents = [('new-1', 'file',
3180
'i 4\na\nb\nc\nd\n\n')]
3181
return self.make_records(attribs, contents)
3183
def test_serialize_add_contents(self):
3184
tt, LINES = self.make_add_contents_preview()
3185
trans_id = tt.trans_id_tree_path('foo')
3186
tt.create_file(LINES, trans_id)
3187
self.assertSerializesTo(self.add_contents_records(), tt)
3189
def test_deserialize_add_contents(self):
3190
tt, LINES = self.make_add_contents_preview()
3191
tt.deserialize(iter(self.add_contents_records()))
3192
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3194
def test_get_parents_lines(self):
3195
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3196
LINES_TWO = 'z\nbb\nx\ndd\n'
3197
tree = self.make_branch_and_tree('tree')
3198
self.build_tree_contents([('tree/file', LINES_ONE)])
3199
tree.add('file', 'file-id')
3200
tt = self.get_preview(tree)
3201
trans_id = tt.trans_id_tree_path('file')
3202
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3203
tt._get_parents_lines(trans_id))
3205
def test_get_parents_texts(self):
3206
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3207
LINES_TWO = 'z\nbb\nx\ndd\n'
3208
tree = self.make_branch_and_tree('tree')
3209
self.build_tree_contents([('tree/file', LINES_ONE)])
3210
tree.add('file', 'file-id')
3211
tt = self.get_preview(tree)
3212
trans_id = tt.trans_id_tree_path('file')
3213
self.assertEqual((LINES_ONE,),
3214
tt._get_parents_texts(trans_id))