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
29
revision as _mod_revision,
34
from bzrlib.bzrdir import BzrDir
35
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
36
UnversionedParent, ParentLoop, DeletingParent,
38
from bzrlib.diff import show_diff_trees
39
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
40
ReusingTransform, CantMoveRoot,
41
PathsNotVersionedError, ExistingLimbo,
42
ExistingPendingDeletion, ImmortalLimbo,
43
ImmortalPendingDeletion, LockError)
44
from bzrlib.osutils import file_kind, pathjoin
45
from bzrlib.merge import Merge3Merger, Merger
46
from bzrlib.tests import (
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
54
resolve_conflicts, cook_conflicts,
55
build_tree, get_backup_name,
56
_FileMover, resolve_checkout,
57
TransformPreview, create_from_tree)
60
class TestTreeTransform(tests.TestCaseWithTransport):
63
super(TestTreeTransform, self).setUp()
64
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
67
def get_transform(self):
68
transform = TreeTransform(self.wt)
69
self.addCleanup(transform.finalize)
70
return transform, transform.root
72
def test_existing_limbo(self):
73
transform, root = self.get_transform()
74
limbo_name = transform._limbodir
75
deletion_path = transform._deletiondir
76
os.mkdir(pathjoin(limbo_name, 'hehe'))
77
self.assertRaises(ImmortalLimbo, transform.apply)
78
self.assertRaises(LockError, self.wt.unlock)
79
self.assertRaises(ExistingLimbo, self.get_transform)
80
self.assertRaises(LockError, self.wt.unlock)
81
os.rmdir(pathjoin(limbo_name, 'hehe'))
83
os.rmdir(deletion_path)
84
transform, root = self.get_transform()
87
def test_existing_pending_deletion(self):
88
transform, root = self.get_transform()
89
deletion_path = self._limbodir = urlutils.local_path_from_url(
90
transform._tree._transport.abspath('pending-deletion'))
91
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
92
self.assertRaises(ImmortalPendingDeletion, transform.apply)
93
self.assertRaises(LockError, self.wt.unlock)
94
self.assertRaises(ExistingPendingDeletion, self.get_transform)
97
transform, root = self.get_transform()
98
self.wt.lock_tree_write()
99
self.addCleanup(self.wt.unlock)
100
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
101
imaginary_id = transform.trans_id_tree_path('imaginary')
102
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
103
self.assertEqual(imaginary_id, imaginary_id2)
104
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
105
self.assertEqual(transform.final_kind(root), 'directory')
106
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
107
trans_id = transform.create_path('name', root)
108
self.assertIs(transform.final_file_id(trans_id), None)
109
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
110
transform.create_file('contents', trans_id)
111
transform.set_executability(True, trans_id)
112
transform.version_file('my_pretties', trans_id)
113
self.assertRaises(DuplicateKey, transform.version_file,
114
'my_pretties', trans_id)
115
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
116
self.assertEqual(transform.final_parent(trans_id), root)
117
self.assertIs(transform.final_parent(root), ROOT_PARENT)
118
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
119
oz_id = transform.create_path('oz', root)
120
transform.create_directory(oz_id)
121
transform.version_file('ozzie', oz_id)
122
trans_id2 = transform.create_path('name2', root)
123
transform.create_file('contents', trans_id2)
124
transform.set_executability(False, trans_id2)
125
transform.version_file('my_pretties2', trans_id2)
126
modified_paths = transform.apply().modified_paths
127
self.assertEqual('contents', self.wt.get_file_byname('name').read())
128
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
129
self.assertIs(self.wt.is_executable('my_pretties'), True)
130
self.assertIs(self.wt.is_executable('my_pretties2'), False)
131
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
132
self.assertEqual(len(modified_paths), 3)
133
tree_mod_paths = [self.wt.id2abspath(f) for f in
134
('ozzie', 'my_pretties', 'my_pretties2')]
135
self.assertSubset(tree_mod_paths, modified_paths)
136
# is it safe to finalize repeatedly?
140
def test_create_files_same_timestamp(self):
141
transform, root = self.get_transform()
142
self.wt.lock_tree_write()
143
self.addCleanup(self.wt.unlock)
144
# Roll back the clock, so that we know everything is being set to the
146
transform._creation_mtime = creation_mtime = time.time() - 20.0
147
transform.create_file('content-one',
148
transform.create_path('one', root))
149
time.sleep(1) # *ugly*
150
transform.create_file('content-two',
151
transform.create_path('two', root))
153
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
155
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
157
# We only guarantee 2s resolution
158
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
159
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
160
# But if we have more than that, all files should get the same result
161
self.assertEqual(st1.st_mtime, st2.st_mtime)
163
def test_change_root_id(self):
164
transform, root = self.get_transform()
165
self.assertNotEqual('new-root-id', self.wt.get_root_id())
166
transform.new_directory('', ROOT_PARENT, 'new-root-id')
167
transform.delete_contents(root)
168
transform.unversion_file(root)
169
transform.fixup_new_roots()
171
self.assertEqual('new-root-id', self.wt.get_root_id())
173
def test_change_root_id_add_files(self):
174
transform, root = self.get_transform()
175
self.assertNotEqual('new-root-id', self.wt.get_root_id())
176
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
177
transform.new_file('file', new_trans_id, ['new-contents\n'],
179
transform.delete_contents(root)
180
transform.unversion_file(root)
181
transform.fixup_new_roots()
183
self.assertEqual('new-root-id', self.wt.get_root_id())
184
self.assertEqual('new-file-id', self.wt.path2id('file'))
185
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
187
def test_add_two_roots(self):
188
transform, root = self.get_transform()
189
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
190
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
191
self.assertRaises(ValueError, transform.fixup_new_roots)
193
def test_hardlink(self):
194
self.requireFeature(HardlinkFeature)
195
transform, root = self.get_transform()
196
transform.new_file('file1', root, 'contents')
198
target = self.make_branch_and_tree('target')
199
target_transform = TreeTransform(target)
200
trans_id = target_transform.create_path('file1', target_transform.root)
201
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
202
target_transform.apply()
203
self.failUnlessExists('target/file1')
204
source_stat = os.stat(self.wt.abspath('file1'))
205
target_stat = os.stat('target/file1')
206
self.assertEqual(source_stat, target_stat)
208
def test_convenience(self):
209
transform, root = self.get_transform()
210
self.wt.lock_tree_write()
211
self.addCleanup(self.wt.unlock)
212
trans_id = transform.new_file('name', root, 'contents',
214
oz = transform.new_directory('oz', root, 'oz-id')
215
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
216
toto = transform.new_file('toto', dorothy, 'toto-contents',
219
self.assertEqual(len(transform.find_conflicts()), 0)
221
self.assertRaises(ReusingTransform, transform.find_conflicts)
222
self.assertEqual('contents', file(self.wt.abspath('name')).read())
223
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
224
self.assertIs(self.wt.is_executable('my_pretties'), True)
225
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
226
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
227
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
229
self.assertEqual('toto-contents',
230
self.wt.get_file_byname('oz/dorothy/toto').read())
231
self.assertIs(self.wt.is_executable('toto-id'), False)
233
def test_tree_reference(self):
234
transform, root = self.get_transform()
235
tree = transform._tree
236
trans_id = transform.new_directory('reference', root, 'subtree-id')
237
transform.set_tree_reference('subtree-revision', trans_id)
240
self.addCleanup(tree.unlock)
241
self.assertEqual('subtree-revision',
242
tree.inventory['subtree-id'].reference_revision)
244
def test_conflicts(self):
245
transform, root = self.get_transform()
246
trans_id = transform.new_file('name', root, 'contents',
248
self.assertEqual(len(transform.find_conflicts()), 0)
249
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
250
self.assertEqual(transform.find_conflicts(),
251
[('duplicate', trans_id, trans_id2, 'name')])
252
self.assertRaises(MalformedTransform, transform.apply)
253
transform.adjust_path('name', trans_id, trans_id2)
254
self.assertEqual(transform.find_conflicts(),
255
[('non-directory parent', trans_id)])
256
tinman_id = transform.trans_id_tree_path('tinman')
257
transform.adjust_path('name', tinman_id, trans_id2)
258
self.assertEqual(transform.find_conflicts(),
259
[('unversioned parent', tinman_id),
260
('missing parent', tinman_id)])
261
lion_id = transform.create_path('lion', root)
262
self.assertEqual(transform.find_conflicts(),
263
[('unversioned parent', tinman_id),
264
('missing parent', tinman_id)])
265
transform.adjust_path('name', lion_id, trans_id2)
266
self.assertEqual(transform.find_conflicts(),
267
[('unversioned parent', lion_id),
268
('missing parent', lion_id)])
269
transform.version_file("Courage", lion_id)
270
self.assertEqual(transform.find_conflicts(),
271
[('missing parent', lion_id),
272
('versioning no contents', lion_id)])
273
transform.adjust_path('name2', root, trans_id2)
274
self.assertEqual(transform.find_conflicts(),
275
[('versioning no contents', lion_id)])
276
transform.create_file('Contents, okay?', lion_id)
277
transform.adjust_path('name2', trans_id2, trans_id2)
278
self.assertEqual(transform.find_conflicts(),
279
[('parent loop', trans_id2),
280
('non-directory parent', trans_id2)])
281
transform.adjust_path('name2', root, trans_id2)
282
oz_id = transform.new_directory('oz', root)
283
transform.set_executability(True, oz_id)
284
self.assertEqual(transform.find_conflicts(),
285
[('unversioned executability', oz_id)])
286
transform.version_file('oz-id', oz_id)
287
self.assertEqual(transform.find_conflicts(),
288
[('non-file executability', oz_id)])
289
transform.set_executability(None, oz_id)
290
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
292
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
293
self.assertEqual('contents', file(self.wt.abspath('name')).read())
294
transform2, root = self.get_transform()
295
oz_id = transform2.trans_id_tree_file_id('oz-id')
296
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
297
result = transform2.find_conflicts()
298
fp = FinalPaths(transform2)
299
self.assert_('oz/tip' in transform2._tree_path_ids)
300
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
301
self.assertEqual(len(result), 2)
302
self.assertEqual((result[0][0], result[0][1]),
303
('duplicate', newtip))
304
self.assertEqual((result[1][0], result[1][2]),
305
('duplicate id', newtip))
306
transform2.finalize()
307
transform3 = TreeTransform(self.wt)
308
self.addCleanup(transform3.finalize)
309
oz_id = transform3.trans_id_tree_file_id('oz-id')
310
transform3.delete_contents(oz_id)
311
self.assertEqual(transform3.find_conflicts(),
312
[('missing parent', oz_id)])
313
root_id = transform3.root
314
tip_id = transform3.trans_id_tree_file_id('tip-id')
315
transform3.adjust_path('tip', root_id, tip_id)
318
def test_conflict_on_case_insensitive(self):
319
tree = self.make_branch_and_tree('tree')
320
# Don't try this at home, kids!
321
# Force the tree to report that it is case sensitive, for conflict
323
tree.case_sensitive = True
324
transform = TreeTransform(tree)
325
self.addCleanup(transform.finalize)
326
transform.new_file('file', transform.root, 'content')
327
transform.new_file('FiLe', transform.root, 'content')
328
result = transform.find_conflicts()
329
self.assertEqual([], result)
331
# Force the tree to report that it is case insensitive, for conflict
333
tree.case_sensitive = False
334
transform = TreeTransform(tree)
335
self.addCleanup(transform.finalize)
336
transform.new_file('file', transform.root, 'content')
337
transform.new_file('FiLe', transform.root, 'content')
338
result = transform.find_conflicts()
339
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
341
def test_conflict_on_case_insensitive_existing(self):
342
tree = self.make_branch_and_tree('tree')
343
self.build_tree(['tree/FiLe'])
344
# Don't try this at home, kids!
345
# Force the tree to report that it is case sensitive, for conflict
347
tree.case_sensitive = True
348
transform = TreeTransform(tree)
349
self.addCleanup(transform.finalize)
350
transform.new_file('file', transform.root, 'content')
351
result = transform.find_conflicts()
352
self.assertEqual([], result)
354
# Force the tree to report that it is case insensitive, for conflict
356
tree.case_sensitive = False
357
transform = TreeTransform(tree)
358
self.addCleanup(transform.finalize)
359
transform.new_file('file', transform.root, 'content')
360
result = transform.find_conflicts()
361
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
363
def test_resolve_case_insensitive_conflict(self):
364
tree = self.make_branch_and_tree('tree')
365
# Don't try this at home, kids!
366
# Force the tree to report that it is case insensitive, for conflict
368
tree.case_sensitive = False
369
transform = TreeTransform(tree)
370
self.addCleanup(transform.finalize)
371
transform.new_file('file', transform.root, 'content')
372
transform.new_file('FiLe', transform.root, 'content')
373
resolve_conflicts(transform)
375
self.failUnlessExists('tree/file')
376
self.failUnlessExists('tree/FiLe.moved')
378
def test_resolve_checkout_case_conflict(self):
379
tree = self.make_branch_and_tree('tree')
380
# Don't try this at home, kids!
381
# Force the tree to report that it is case insensitive, for conflict
383
tree.case_sensitive = False
384
transform = TreeTransform(tree)
385
self.addCleanup(transform.finalize)
386
transform.new_file('file', transform.root, 'content')
387
transform.new_file('FiLe', transform.root, 'content')
388
resolve_conflicts(transform,
389
pass_func=lambda t, c: resolve_checkout(t, c, []))
391
self.failUnlessExists('tree/file')
392
self.failUnlessExists('tree/FiLe.moved')
394
def test_apply_case_conflict(self):
395
"""Ensure that a transform with case conflicts can always be applied"""
396
tree = self.make_branch_and_tree('tree')
397
transform = TreeTransform(tree)
398
self.addCleanup(transform.finalize)
399
transform.new_file('file', transform.root, 'content')
400
transform.new_file('FiLe', transform.root, 'content')
401
dir = transform.new_directory('dir', transform.root)
402
transform.new_file('dirfile', dir, 'content')
403
transform.new_file('dirFiLe', dir, 'content')
404
resolve_conflicts(transform)
406
self.failUnlessExists('tree/file')
407
if not os.path.exists('tree/FiLe.moved'):
408
self.failUnlessExists('tree/FiLe')
409
self.failUnlessExists('tree/dir/dirfile')
410
if not os.path.exists('tree/dir/dirFiLe.moved'):
411
self.failUnlessExists('tree/dir/dirFiLe')
413
def test_case_insensitive_limbo(self):
414
tree = self.make_branch_and_tree('tree')
415
# Don't try this at home, kids!
416
# Force the tree to report that it is case insensitive
417
tree.case_sensitive = False
418
transform = TreeTransform(tree)
419
self.addCleanup(transform.finalize)
420
dir = transform.new_directory('dir', transform.root)
421
first = transform.new_file('file', dir, 'content')
422
second = transform.new_file('FiLe', dir, 'content')
423
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
424
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
426
def test_adjust_path_updates_child_limbo_names(self):
427
tree = self.make_branch_and_tree('tree')
428
transform = TreeTransform(tree)
429
self.addCleanup(transform.finalize)
430
foo_id = transform.new_directory('foo', transform.root)
431
bar_id = transform.new_directory('bar', foo_id)
432
baz_id = transform.new_directory('baz', bar_id)
433
qux_id = transform.new_directory('qux', baz_id)
434
transform.adjust_path('quxx', foo_id, bar_id)
435
self.assertStartsWith(transform._limbo_name(qux_id),
436
transform._limbo_name(bar_id))
438
def test_add_del(self):
439
start, root = self.get_transform()
440
start.new_directory('a', root, 'a')
442
transform, root = self.get_transform()
443
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
444
transform.new_directory('a', root, 'a')
447
def test_unversioning(self):
448
create_tree, root = self.get_transform()
449
parent_id = create_tree.new_directory('parent', root, 'parent-id')
450
create_tree.new_file('child', parent_id, 'child', 'child-id')
452
unversion = TreeTransform(self.wt)
453
self.addCleanup(unversion.finalize)
454
parent = unversion.trans_id_tree_path('parent')
455
unversion.unversion_file(parent)
456
self.assertEqual(unversion.find_conflicts(),
457
[('unversioned parent', parent_id)])
458
file_id = unversion.trans_id_tree_file_id('child-id')
459
unversion.unversion_file(file_id)
462
def test_name_invariants(self):
463
create_tree, root = self.get_transform()
465
root = create_tree.root
466
create_tree.new_file('name1', root, 'hello1', 'name1')
467
create_tree.new_file('name2', root, 'hello2', 'name2')
468
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
469
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
470
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
471
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
474
mangle_tree,root = self.get_transform()
475
root = mangle_tree.root
477
name1 = mangle_tree.trans_id_tree_file_id('name1')
478
name2 = mangle_tree.trans_id_tree_file_id('name2')
479
mangle_tree.adjust_path('name2', root, name1)
480
mangle_tree.adjust_path('name1', root, name2)
482
#tests for deleting parent directories
483
ddir = mangle_tree.trans_id_tree_file_id('ddir')
484
mangle_tree.delete_contents(ddir)
485
dfile = mangle_tree.trans_id_tree_file_id('dfile')
486
mangle_tree.delete_versioned(dfile)
487
mangle_tree.unversion_file(dfile)
488
mfile = mangle_tree.trans_id_tree_file_id('mfile')
489
mangle_tree.adjust_path('mfile', root, mfile)
491
#tests for adding parent directories
492
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
493
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
494
mangle_tree.adjust_path('mfile2', newdir, mfile2)
495
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
496
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
497
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
498
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
500
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
501
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
502
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
503
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
504
self.assertEqual(file(mfile2_path).read(), 'later2')
505
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
506
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
507
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
508
self.assertEqual(file(newfile_path).read(), 'hello3')
509
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
510
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
511
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
513
def test_both_rename(self):
514
create_tree,root = self.get_transform()
515
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
516
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
518
mangle_tree,root = self.get_transform()
519
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
520
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
521
mangle_tree.adjust_path('test', root, selftest)
522
mangle_tree.adjust_path('test_too_much', root, selftest)
523
mangle_tree.set_executability(True, blackbox)
526
def test_both_rename2(self):
527
create_tree,root = self.get_transform()
528
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
529
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
530
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
531
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
534
mangle_tree,root = self.get_transform()
535
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
536
tests = mangle_tree.trans_id_tree_file_id('tests-id')
537
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
538
mangle_tree.adjust_path('selftest', bzrlib, tests)
539
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
540
mangle_tree.set_executability(True, test_too_much)
543
def test_both_rename3(self):
544
create_tree,root = self.get_transform()
545
tests = create_tree.new_directory('tests', root, 'tests-id')
546
create_tree.new_file('test_too_much.py', tests, 'hello1',
549
mangle_tree,root = self.get_transform()
550
tests = mangle_tree.trans_id_tree_file_id('tests-id')
551
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
552
mangle_tree.adjust_path('selftest', root, tests)
553
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
554
mangle_tree.set_executability(True, test_too_much)
557
def test_move_dangling_ie(self):
558
create_tree, root = self.get_transform()
560
root = create_tree.root
561
create_tree.new_file('name1', root, 'hello1', 'name1')
563
delete_contents, root = self.get_transform()
564
file = delete_contents.trans_id_tree_file_id('name1')
565
delete_contents.delete_contents(file)
566
delete_contents.apply()
567
move_id, root = self.get_transform()
568
name1 = move_id.trans_id_tree_file_id('name1')
569
newdir = move_id.new_directory('dir', root, 'newdir')
570
move_id.adjust_path('name2', newdir, name1)
573
def test_replace_dangling_ie(self):
574
create_tree, root = self.get_transform()
576
root = create_tree.root
577
create_tree.new_file('name1', root, 'hello1', 'name1')
579
delete_contents = TreeTransform(self.wt)
580
self.addCleanup(delete_contents.finalize)
581
file = delete_contents.trans_id_tree_file_id('name1')
582
delete_contents.delete_contents(file)
583
delete_contents.apply()
584
delete_contents.finalize()
585
replace = TreeTransform(self.wt)
586
self.addCleanup(replace.finalize)
587
name2 = replace.new_file('name2', root, 'hello2', 'name1')
588
conflicts = replace.find_conflicts()
589
name1 = replace.trans_id_tree_file_id('name1')
590
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
591
resolve_conflicts(replace)
594
def _test_symlinks(self, link_name1,link_target1,
595
link_name2, link_target2):
597
def ozpath(p): return 'oz/' + p
599
self.requireFeature(SymlinkFeature)
600
transform, root = self.get_transform()
601
oz_id = transform.new_directory('oz', root, 'oz-id')
602
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
604
wiz_id = transform.create_path(link_name2, oz_id)
605
transform.create_symlink(link_target2, wiz_id)
606
transform.version_file('wiz-id2', wiz_id)
607
transform.set_executability(True, wiz_id)
608
self.assertEqual(transform.find_conflicts(),
609
[('non-file executability', wiz_id)])
610
transform.set_executability(None, wiz_id)
612
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
613
self.assertEqual('symlink',
614
file_kind(self.wt.abspath(ozpath(link_name1))))
615
self.assertEqual(link_target2,
616
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
617
self.assertEqual(link_target1,
618
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
620
def test_symlinks(self):
621
self._test_symlinks('wizard', 'wizard-target',
622
'wizard2', 'behind_curtain')
624
def test_symlinks_unicode(self):
625
self.requireFeature(tests.UnicodeFilenameFeature)
626
self._test_symlinks(u'\N{Euro Sign}wizard',
627
u'wizard-targ\N{Euro Sign}t',
628
u'\N{Euro Sign}wizard2',
629
u'b\N{Euro Sign}hind_curtain')
631
def test_unable_create_symlink(self):
633
wt = self.make_branch_and_tree('.')
634
tt = TreeTransform(wt) # TreeTransform obtains write lock
636
tt.new_symlink('foo', tt.root, 'bar')
640
os_symlink = getattr(os, 'symlink', None)
643
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
645
"Unable to create symlink 'foo' on this platform",
649
os.symlink = os_symlink
651
def get_conflicted(self):
652
create,root = self.get_transform()
653
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
654
oz = create.new_directory('oz', root, 'oz-id')
655
create.new_directory('emeraldcity', oz, 'emerald-id')
657
conflicts,root = self.get_transform()
658
# set up duplicate entry, duplicate id
659
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
661
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
662
oz = conflicts.trans_id_tree_file_id('oz-id')
663
# set up DeletedParent parent conflict
664
conflicts.delete_versioned(oz)
665
emerald = conflicts.trans_id_tree_file_id('emerald-id')
666
# set up MissingParent conflict
667
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
668
conflicts.adjust_path('munchkincity', root, munchkincity)
669
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
671
conflicts.adjust_path('emeraldcity', emerald, emerald)
672
return conflicts, emerald, oz, old_dorothy, new_dorothy
674
def test_conflict_resolution(self):
675
conflicts, emerald, oz, old_dorothy, new_dorothy =\
676
self.get_conflicted()
677
resolve_conflicts(conflicts)
678
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
679
self.assertIs(conflicts.final_file_id(old_dorothy), None)
680
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
681
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
682
self.assertEqual(conflicts.final_parent(emerald), oz)
685
def test_cook_conflicts(self):
686
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
687
raw_conflicts = resolve_conflicts(tt)
688
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
689
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
690
'dorothy', None, 'dorothy-id')
691
self.assertEqual(cooked_conflicts[0], duplicate)
692
duplicate_id = DuplicateID('Unversioned existing file',
693
'dorothy.moved', 'dorothy', None,
695
self.assertEqual(cooked_conflicts[1], duplicate_id)
696
missing_parent = MissingParent('Created directory', 'munchkincity',
698
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
699
self.assertEqual(cooked_conflicts[2], missing_parent)
700
unversioned_parent = UnversionedParent('Versioned directory',
703
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
705
self.assertEqual(cooked_conflicts[3], unversioned_parent)
706
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
707
'oz/emeraldcity', 'emerald-id', 'emerald-id')
708
self.assertEqual(cooked_conflicts[4], deleted_parent)
709
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
710
self.assertEqual(cooked_conflicts[6], parent_loop)
711
self.assertEqual(len(cooked_conflicts), 7)
714
def test_string_conflicts(self):
715
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
716
raw_conflicts = resolve_conflicts(tt)
717
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
719
conflicts_s = [str(c) for c in cooked_conflicts]
720
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
721
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
722
'Moved existing file to '
724
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
725
'Unversioned existing file '
727
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
728
' munchkincity. Created directory.')
729
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
730
' versioned, but has versioned'
731
' children. Versioned directory.')
732
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
733
" is not empty. Not deleting.")
734
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
735
' versioned, but has versioned'
736
' children. Versioned directory.')
737
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
738
' oz/emeraldcity. Cancelled move.')
740
def prepare_wrong_parent_kind(self):
741
tt, root = self.get_transform()
742
tt.new_file('parent', root, 'contents', 'parent-id')
744
tt, root = self.get_transform()
745
parent_id = tt.trans_id_file_id('parent-id')
746
tt.new_file('child,', parent_id, 'contents2', 'file-id')
749
def test_find_conflicts_wrong_parent_kind(self):
750
tt = self.prepare_wrong_parent_kind()
753
def test_resolve_conflicts_wrong_existing_parent_kind(self):
754
tt = self.prepare_wrong_parent_kind()
755
raw_conflicts = resolve_conflicts(tt)
756
self.assertEqual(set([('non-directory parent', 'Created directory',
757
'new-3')]), raw_conflicts)
758
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
759
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
760
'parent-id')], cooked_conflicts)
762
self.assertEqual(None, self.wt.path2id('parent'))
763
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
765
def test_resolve_conflicts_wrong_new_parent_kind(self):
766
tt, root = self.get_transform()
767
parent_id = tt.new_directory('parent', root, 'parent-id')
768
tt.new_file('child,', parent_id, 'contents2', 'file-id')
770
tt, root = self.get_transform()
771
parent_id = tt.trans_id_file_id('parent-id')
772
tt.delete_contents(parent_id)
773
tt.create_file('contents', parent_id)
774
raw_conflicts = resolve_conflicts(tt)
775
self.assertEqual(set([('non-directory parent', 'Created directory',
776
'new-3')]), raw_conflicts)
778
self.assertEqual(None, self.wt.path2id('parent'))
779
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
781
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
782
tt, root = self.get_transform()
783
parent_id = tt.new_directory('parent', root)
784
tt.new_file('child,', parent_id, 'contents2')
786
tt, root = self.get_transform()
787
parent_id = tt.trans_id_tree_path('parent')
788
tt.delete_contents(parent_id)
789
tt.create_file('contents', parent_id)
790
resolve_conflicts(tt)
792
self.assertIs(None, self.wt.path2id('parent'))
793
self.assertIs(None, self.wt.path2id('parent.new'))
795
def test_moving_versioned_directories(self):
796
create, root = self.get_transform()
797
kansas = create.new_directory('kansas', root, 'kansas-id')
798
create.new_directory('house', kansas, 'house-id')
799
create.new_directory('oz', root, 'oz-id')
801
cyclone, root = self.get_transform()
802
oz = cyclone.trans_id_tree_file_id('oz-id')
803
house = cyclone.trans_id_tree_file_id('house-id')
804
cyclone.adjust_path('house', oz, house)
807
def test_moving_root(self):
808
create, root = self.get_transform()
809
fun = create.new_directory('fun', root, 'fun-id')
810
create.new_directory('sun', root, 'sun-id')
811
create.new_directory('moon', root, 'moon')
813
transform, root = self.get_transform()
814
transform.adjust_root_path('oldroot', fun)
815
new_root = transform.trans_id_tree_path('')
816
transform.version_file('new-root', new_root)
819
def test_renames(self):
820
create, root = self.get_transform()
821
old = create.new_directory('old-parent', root, 'old-id')
822
intermediate = create.new_directory('intermediate', old, 'im-id')
823
myfile = create.new_file('myfile', intermediate, 'myfile-text',
826
rename, root = self.get_transform()
827
old = rename.trans_id_file_id('old-id')
828
rename.adjust_path('new', root, old)
829
myfile = rename.trans_id_file_id('myfile-id')
830
rename.set_executability(True, myfile)
833
def test_set_executability_order(self):
834
"""Ensure that executability behaves the same, no matter what order.
836
- create file and set executability simultaneously
837
- create file and set executability afterward
838
- unsetting the executability of a file whose executability has not been
839
declared should throw an exception (this may happen when a
840
merge attempts to create a file with a duplicate ID)
842
transform, root = self.get_transform()
845
self.addCleanup(wt.unlock)
846
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
848
sac = transform.new_file('set_after_creation', root,
849
'Set after creation', 'sac')
850
transform.set_executability(True, sac)
851
uws = transform.new_file('unset_without_set', root, 'Unset badly',
853
self.assertRaises(KeyError, transform.set_executability, None, uws)
855
self.assertTrue(wt.is_executable('soc'))
856
self.assertTrue(wt.is_executable('sac'))
858
def test_preserve_mode(self):
859
"""File mode is preserved when replacing content"""
860
if sys.platform == 'win32':
861
raise TestSkipped('chmod has no effect on win32')
862
transform, root = self.get_transform()
863
transform.new_file('file1', root, 'contents', 'file1-id', True)
866
self.addCleanup(self.wt.unlock)
867
self.assertTrue(self.wt.is_executable('file1-id'))
868
transform, root = self.get_transform()
869
file1_id = transform.trans_id_tree_file_id('file1-id')
870
transform.delete_contents(file1_id)
871
transform.create_file('contents2', file1_id)
873
self.assertTrue(self.wt.is_executable('file1-id'))
875
def test__set_mode_stats_correctly(self):
876
"""_set_mode stats to determine file mode."""
877
if sys.platform == 'win32':
878
raise TestSkipped('chmod has no effect on win32')
882
def instrumented_stat(path):
883
stat_paths.append(path)
884
return real_stat(path)
886
transform, root = self.get_transform()
888
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
889
file_id='bar-id-1', executable=False)
892
transform, root = self.get_transform()
893
bar1_id = transform.trans_id_tree_path('bar')
894
bar2_id = transform.trans_id_tree_path('bar2')
896
os.stat = instrumented_stat
897
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
902
bar1_abspath = self.wt.abspath('bar')
903
self.assertEqual([bar1_abspath], stat_paths)
905
def test_iter_changes(self):
906
self.wt.set_root_id('eert_toor')
907
transform, root = self.get_transform()
908
transform.new_file('old', root, 'blah', 'id-1', True)
910
transform, root = self.get_transform()
912
self.assertEqual([], list(transform.iter_changes()))
913
old = transform.trans_id_tree_file_id('id-1')
914
transform.unversion_file(old)
915
self.assertEqual([('id-1', ('old', None), False, (True, False),
916
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
917
(True, True))], list(transform.iter_changes()))
918
transform.new_directory('new', root, 'id-1')
919
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
920
('eert_toor', 'eert_toor'), ('old', 'new'),
921
('file', 'directory'),
922
(True, False))], list(transform.iter_changes()))
926
def test_iter_changes_new(self):
927
self.wt.set_root_id('eert_toor')
928
transform, root = self.get_transform()
929
transform.new_file('old', root, 'blah')
931
transform, root = self.get_transform()
933
old = transform.trans_id_tree_path('old')
934
transform.version_file('id-1', old)
935
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
936
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
937
(False, False))], list(transform.iter_changes()))
941
def test_iter_changes_modifications(self):
942
self.wt.set_root_id('eert_toor')
943
transform, root = self.get_transform()
944
transform.new_file('old', root, 'blah', 'id-1')
945
transform.new_file('new', root, 'blah')
946
transform.new_directory('subdir', root, 'subdir-id')
948
transform, root = self.get_transform()
950
old = transform.trans_id_tree_path('old')
951
subdir = transform.trans_id_tree_file_id('subdir-id')
952
new = transform.trans_id_tree_path('new')
953
self.assertEqual([], list(transform.iter_changes()))
956
transform.delete_contents(old)
957
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
958
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
959
(False, False))], list(transform.iter_changes()))
962
transform.create_file('blah', old)
963
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
964
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
965
(False, False))], list(transform.iter_changes()))
966
transform.cancel_deletion(old)
967
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
968
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
969
(False, False))], list(transform.iter_changes()))
970
transform.cancel_creation(old)
972
# move file_id to a different file
973
self.assertEqual([], list(transform.iter_changes()))
974
transform.unversion_file(old)
975
transform.version_file('id-1', new)
976
transform.adjust_path('old', root, new)
977
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
978
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
979
(False, False))], list(transform.iter_changes()))
980
transform.cancel_versioning(new)
981
transform._removed_id = set()
984
self.assertEqual([], list(transform.iter_changes()))
985
transform.set_executability(True, old)
986
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
987
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
988
(False, True))], list(transform.iter_changes()))
989
transform.set_executability(None, old)
992
self.assertEqual([], list(transform.iter_changes()))
993
transform.adjust_path('new', root, old)
994
transform._new_parent = {}
995
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
996
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
997
(False, False))], list(transform.iter_changes()))
998
transform._new_name = {}
1001
self.assertEqual([], list(transform.iter_changes()))
1002
transform.adjust_path('new', subdir, old)
1003
transform._new_name = {}
1004
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1005
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1006
('file', 'file'), (False, False))],
1007
list(transform.iter_changes()))
1008
transform._new_path = {}
1011
transform.finalize()
1013
def test_iter_changes_modified_bleed(self):
1014
self.wt.set_root_id('eert_toor')
1015
"""Modified flag should not bleed from one change to another"""
1016
# unfortunately, we have no guarantee that file1 (which is modified)
1017
# will be applied before file2. And if it's applied after file2, it
1018
# obviously can't bleed into file2's change output. But for now, it
1020
transform, root = self.get_transform()
1021
transform.new_file('file1', root, 'blah', 'id-1')
1022
transform.new_file('file2', root, 'blah', 'id-2')
1024
transform, root = self.get_transform()
1026
transform.delete_contents(transform.trans_id_file_id('id-1'))
1027
transform.set_executability(True,
1028
transform.trans_id_file_id('id-2'))
1029
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1030
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1031
('file', None), (False, False)),
1032
('id-2', (u'file2', u'file2'), False, (True, True),
1033
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1034
('file', 'file'), (False, True))],
1035
list(transform.iter_changes()))
1037
transform.finalize()
1039
def test_iter_changes_move_missing(self):
1040
"""Test moving ids with no files around"""
1041
self.wt.set_root_id('toor_eert')
1042
# Need two steps because versioning a non-existant file is a conflict.
1043
transform, root = self.get_transform()
1044
transform.new_directory('floater', root, 'floater-id')
1046
transform, root = self.get_transform()
1047
transform.delete_contents(transform.trans_id_tree_path('floater'))
1049
transform, root = self.get_transform()
1050
floater = transform.trans_id_tree_path('floater')
1052
transform.adjust_path('flitter', root, floater)
1053
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1054
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1055
(None, None), (False, False))], list(transform.iter_changes()))
1057
transform.finalize()
1059
def test_iter_changes_pointless(self):
1060
"""Ensure that no-ops are not treated as modifications"""
1061
self.wt.set_root_id('eert_toor')
1062
transform, root = self.get_transform()
1063
transform.new_file('old', root, 'blah', 'id-1')
1064
transform.new_directory('subdir', root, 'subdir-id')
1066
transform, root = self.get_transform()
1068
old = transform.trans_id_tree_path('old')
1069
subdir = transform.trans_id_tree_file_id('subdir-id')
1070
self.assertEqual([], list(transform.iter_changes()))
1071
transform.delete_contents(subdir)
1072
transform.create_directory(subdir)
1073
transform.set_executability(False, old)
1074
transform.unversion_file(old)
1075
transform.version_file('id-1', old)
1076
transform.adjust_path('old', root, old)
1077
self.assertEqual([], list(transform.iter_changes()))
1079
transform.finalize()
1081
def test_rename_count(self):
1082
transform, root = self.get_transform()
1083
transform.new_file('name1', root, 'contents')
1084
self.assertEqual(transform.rename_count, 0)
1086
self.assertEqual(transform.rename_count, 1)
1087
transform2, root = self.get_transform()
1088
transform2.adjust_path('name2', root,
1089
transform2.trans_id_tree_path('name1'))
1090
self.assertEqual(transform2.rename_count, 0)
1092
self.assertEqual(transform2.rename_count, 2)
1094
def test_change_parent(self):
1095
"""Ensure that after we change a parent, the results are still right.
1097
Renames and parent changes on pending transforms can happen as part
1098
of conflict resolution, and are explicitly permitted by the
1101
This test ensures they work correctly with the rename-avoidance
1104
transform, root = self.get_transform()
1105
parent1 = transform.new_directory('parent1', root)
1106
child1 = transform.new_file('child1', parent1, 'contents')
1107
parent2 = transform.new_directory('parent2', root)
1108
transform.adjust_path('child1', parent2, child1)
1110
self.failIfExists(self.wt.abspath('parent1/child1'))
1111
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1112
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1113
# no rename for child1 (counting only renames during apply)
1114
self.failUnlessEqual(2, transform.rename_count)
1116
def test_cancel_parent(self):
1117
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1119
This is like the test_change_parent, except that we cancel the parent
1120
before adjusting the path. The transform must detect that the
1121
directory is non-empty, and move children to safe locations.
1123
transform, root = self.get_transform()
1124
parent1 = transform.new_directory('parent1', root)
1125
child1 = transform.new_file('child1', parent1, 'contents')
1126
child2 = transform.new_file('child2', parent1, 'contents')
1128
transform.cancel_creation(parent1)
1130
self.fail('Failed to move child1 before deleting parent1')
1131
transform.cancel_creation(child2)
1132
transform.create_directory(parent1)
1134
transform.cancel_creation(parent1)
1135
# If the transform incorrectly believes that child2 is still in
1136
# parent1's limbo directory, it will try to rename it and fail
1137
# because was already moved by the first cancel_creation.
1139
self.fail('Transform still thinks child2 is a child of parent1')
1140
parent2 = transform.new_directory('parent2', root)
1141
transform.adjust_path('child1', parent2, child1)
1143
self.failIfExists(self.wt.abspath('parent1'))
1144
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1145
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1146
self.failUnlessEqual(2, transform.rename_count)
1148
def test_adjust_and_cancel(self):
1149
"""Make sure adjust_path keeps track of limbo children properly"""
1150
transform, root = self.get_transform()
1151
parent1 = transform.new_directory('parent1', root)
1152
child1 = transform.new_file('child1', parent1, 'contents')
1153
parent2 = transform.new_directory('parent2', root)
1154
transform.adjust_path('child1', parent2, child1)
1155
transform.cancel_creation(child1)
1157
transform.cancel_creation(parent1)
1158
# if the transform thinks child1 is still in parent1's limbo
1159
# directory, it will attempt to move it and fail.
1161
self.fail('Transform still thinks child1 is a child of parent1')
1162
transform.finalize()
1164
def test_noname_contents(self):
1165
"""TreeTransform should permit deferring naming files."""
1166
transform, root = self.get_transform()
1167
parent = transform.trans_id_file_id('parent-id')
1169
transform.create_directory(parent)
1171
self.fail("Can't handle contents with no name")
1172
transform.finalize()
1174
def test_noname_contents_nested(self):
1175
"""TreeTransform should permit deferring naming files."""
1176
transform, root = self.get_transform()
1177
parent = transform.trans_id_file_id('parent-id')
1179
transform.create_directory(parent)
1181
self.fail("Can't handle contents with no name")
1182
child = transform.new_directory('child', parent)
1183
transform.adjust_path('parent', root, parent)
1185
self.failUnlessExists(self.wt.abspath('parent/child'))
1186
self.assertEqual(1, transform.rename_count)
1188
def test_reuse_name(self):
1189
"""Avoid reusing the same limbo name for different files"""
1190
transform, root = self.get_transform()
1191
parent = transform.new_directory('parent', root)
1192
child1 = transform.new_directory('child', parent)
1194
child2 = transform.new_directory('child', parent)
1196
self.fail('Tranform tried to use the same limbo name twice')
1197
transform.adjust_path('child2', parent, child2)
1199
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1200
# child2 is put into top-level limbo because child1 has already
1201
# claimed the direct limbo path when child2 is created. There is no
1202
# advantage in renaming files once they're in top-level limbo, except
1204
self.assertEqual(2, transform.rename_count)
1206
def test_reuse_when_first_moved(self):
1207
"""Don't avoid direct paths when it is safe to use them"""
1208
transform, root = self.get_transform()
1209
parent = transform.new_directory('parent', root)
1210
child1 = transform.new_directory('child', parent)
1211
transform.adjust_path('child1', parent, child1)
1212
child2 = transform.new_directory('child', parent)
1214
# limbo/new-1 => parent
1215
self.assertEqual(1, transform.rename_count)
1217
def test_reuse_after_cancel(self):
1218
"""Don't avoid direct paths when it is safe to use them"""
1219
transform, root = self.get_transform()
1220
parent2 = transform.new_directory('parent2', root)
1221
child1 = transform.new_directory('child1', parent2)
1222
transform.cancel_creation(parent2)
1223
transform.create_directory(parent2)
1224
child2 = transform.new_directory('child1', parent2)
1225
transform.adjust_path('child2', parent2, child1)
1227
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1228
self.assertEqual(2, transform.rename_count)
1230
def test_finalize_order(self):
1231
"""Finalize must be done in child-to-parent order"""
1232
transform, root = self.get_transform()
1233
parent = transform.new_directory('parent', root)
1234
child = transform.new_directory('child', parent)
1236
transform.finalize()
1238
self.fail('Tried to remove parent before child1')
1240
def test_cancel_with_cancelled_child_should_succeed(self):
1241
transform, root = self.get_transform()
1242
parent = transform.new_directory('parent', root)
1243
child = transform.new_directory('child', parent)
1244
transform.cancel_creation(child)
1245
transform.cancel_creation(parent)
1246
transform.finalize()
1248
def test_rollback_on_directory_clash(self):
1250
wt = self.make_branch_and_tree('.')
1251
tt = TreeTransform(wt) # TreeTransform obtains write lock
1253
foo = tt.new_directory('foo', tt.root)
1254
tt.new_file('bar', foo, 'foobar')
1255
baz = tt.new_directory('baz', tt.root)
1256
tt.new_file('qux', baz, 'quux')
1257
# Ask for a rename 'foo' -> 'baz'
1258
tt.adjust_path('baz', tt.root, foo)
1259
# Lie to tt that we've already resolved all conflicts.
1260
tt.apply(no_conflicts=True)
1264
# The rename will fail because the target directory is not empty (but
1265
# raises FileExists anyway).
1266
err = self.assertRaises(errors.FileExists, tt_helper)
1267
self.assertContainsRe(str(err),
1268
"^File exists: .+/baz")
1270
def test_two_directories_clash(self):
1272
wt = self.make_branch_and_tree('.')
1273
tt = TreeTransform(wt) # TreeTransform obtains write lock
1275
foo_1 = tt.new_directory('foo', tt.root)
1276
tt.new_directory('bar', foo_1)
1277
# Adding the same directory with a different content
1278
foo_2 = tt.new_directory('foo', tt.root)
1279
tt.new_directory('baz', foo_2)
1280
# Lie to tt that we've already resolved all conflicts.
1281
tt.apply(no_conflicts=True)
1285
err = self.assertRaises(errors.FileExists, tt_helper)
1286
self.assertContainsRe(str(err),
1287
"^File exists: .+/foo")
1289
def test_two_directories_clash_finalize(self):
1291
wt = self.make_branch_and_tree('.')
1292
tt = TreeTransform(wt) # TreeTransform obtains write lock
1294
foo_1 = tt.new_directory('foo', tt.root)
1295
tt.new_directory('bar', foo_1)
1296
# Adding the same directory with a different content
1297
foo_2 = tt.new_directory('foo', tt.root)
1298
tt.new_directory('baz', foo_2)
1299
# Lie to tt that we've already resolved all conflicts.
1300
tt.apply(no_conflicts=True)
1304
err = self.assertRaises(errors.FileExists, tt_helper)
1305
self.assertContainsRe(str(err),
1306
"^File exists: .+/foo")
1308
def test_file_to_directory(self):
1309
wt = self.make_branch_and_tree('.')
1310
self.build_tree(['foo'])
1313
tt = TreeTransform(wt)
1314
self.addCleanup(tt.finalize)
1315
foo_trans_id = tt.trans_id_tree_path("foo")
1316
tt.delete_contents(foo_trans_id)
1317
tt.create_directory(foo_trans_id)
1318
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1319
tt.create_file(["aa\n"], bar_trans_id)
1320
tt.version_file("bar-1", bar_trans_id)
1322
self.failUnlessExists("foo/bar")
1325
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1330
changes = wt.changes_from(wt.basis_tree())
1331
self.assertFalse(changes.has_changed(), changes)
1333
def test_file_to_symlink(self):
1334
self.requireFeature(SymlinkFeature)
1335
wt = self.make_branch_and_tree('.')
1336
self.build_tree(['foo'])
1339
tt = TreeTransform(wt)
1340
self.addCleanup(tt.finalize)
1341
foo_trans_id = tt.trans_id_tree_path("foo")
1342
tt.delete_contents(foo_trans_id)
1343
tt.create_symlink("bar", foo_trans_id)
1345
self.failUnlessExists("foo")
1347
self.addCleanup(wt.unlock)
1348
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1351
def test_dir_to_file(self):
1352
wt = self.make_branch_and_tree('.')
1353
self.build_tree(['foo/', 'foo/bar'])
1354
wt.add(['foo', 'foo/bar'])
1356
tt = TreeTransform(wt)
1357
self.addCleanup(tt.finalize)
1358
foo_trans_id = tt.trans_id_tree_path("foo")
1359
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1360
tt.delete_contents(foo_trans_id)
1361
tt.delete_versioned(bar_trans_id)
1362
tt.create_file(["aa\n"], foo_trans_id)
1364
self.failUnlessExists("foo")
1366
self.addCleanup(wt.unlock)
1367
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1370
def test_dir_to_hardlink(self):
1371
self.requireFeature(HardlinkFeature)
1372
wt = self.make_branch_and_tree('.')
1373
self.build_tree(['foo/', 'foo/bar'])
1374
wt.add(['foo', 'foo/bar'])
1376
tt = TreeTransform(wt)
1377
self.addCleanup(tt.finalize)
1378
foo_trans_id = tt.trans_id_tree_path("foo")
1379
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1380
tt.delete_contents(foo_trans_id)
1381
tt.delete_versioned(bar_trans_id)
1382
self.build_tree(['baz'])
1383
tt.create_hardlink("baz", foo_trans_id)
1385
self.failUnlessExists("foo")
1386
self.failUnlessExists("baz")
1388
self.addCleanup(wt.unlock)
1389
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1392
def test_no_final_path(self):
1393
transform, root = self.get_transform()
1394
trans_id = transform.trans_id_file_id('foo')
1395
transform.create_file('bar', trans_id)
1396
transform.cancel_creation(trans_id)
1399
def test_create_from_tree(self):
1400
tree1 = self.make_branch_and_tree('tree1')
1401
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1402
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1403
tree2 = self.make_branch_and_tree('tree2')
1404
tt = TreeTransform(tree2)
1405
foo_trans_id = tt.create_path('foo', tt.root)
1406
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1407
bar_trans_id = tt.create_path('bar', tt.root)
1408
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1410
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1411
self.assertFileEqual('baz', 'tree2/bar')
1413
def test_create_from_tree_bytes(self):
1414
"""Provided lines are used instead of tree content."""
1415
tree1 = self.make_branch_and_tree('tree1')
1416
self.build_tree_contents([('tree1/foo', 'bar'),])
1417
tree1.add('foo', 'foo-id')
1418
tree2 = self.make_branch_and_tree('tree2')
1419
tt = TreeTransform(tree2)
1420
foo_trans_id = tt.create_path('foo', tt.root)
1421
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1423
self.assertFileEqual('qux', 'tree2/foo')
1425
def test_create_from_tree_symlink(self):
1426
self.requireFeature(SymlinkFeature)
1427
tree1 = self.make_branch_and_tree('tree1')
1428
os.symlink('bar', 'tree1/foo')
1429
tree1.add('foo', 'foo-id')
1430
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1431
foo_trans_id = tt.create_path('foo', tt.root)
1432
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1434
self.assertEqual('bar', os.readlink('tree2/foo'))
1437
class TransformGroup(object):
1439
def __init__(self, dirname, root_id):
1442
self.wt = BzrDir.create_standalone_workingtree(dirname)
1443
self.wt.set_root_id(root_id)
1444
self.b = self.wt.branch
1445
self.tt = TreeTransform(self.wt)
1446
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1449
def conflict_text(tree, merge):
1450
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1451
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1454
class TestTransformMerge(TestCaseInTempDir):
1456
def test_text_merge(self):
1457
root_id = generate_ids.gen_root_id()
1458
base = TransformGroup("base", root_id)
1459
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1460
base.tt.new_file('b', base.root, 'b1', 'b')
1461
base.tt.new_file('c', base.root, 'c', 'c')
1462
base.tt.new_file('d', base.root, 'd', 'd')
1463
base.tt.new_file('e', base.root, 'e', 'e')
1464
base.tt.new_file('f', base.root, 'f', 'f')
1465
base.tt.new_directory('g', base.root, 'g')
1466
base.tt.new_directory('h', base.root, 'h')
1468
other = TransformGroup("other", root_id)
1469
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1470
other.tt.new_file('b', other.root, 'b2', 'b')
1471
other.tt.new_file('c', other.root, 'c2', 'c')
1472
other.tt.new_file('d', other.root, 'd', 'd')
1473
other.tt.new_file('e', other.root, 'e2', 'e')
1474
other.tt.new_file('f', other.root, 'f', 'f')
1475
other.tt.new_file('g', other.root, 'g', 'g')
1476
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1477
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1479
this = TransformGroup("this", root_id)
1480
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1481
this.tt.new_file('b', this.root, 'b', 'b')
1482
this.tt.new_file('c', this.root, 'c', 'c')
1483
this.tt.new_file('d', this.root, 'd2', 'd')
1484
this.tt.new_file('e', this.root, 'e2', 'e')
1485
this.tt.new_file('f', this.root, 'f', 'f')
1486
this.tt.new_file('g', this.root, 'g', 'g')
1487
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1488
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1490
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1493
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1494
# three-way text conflict
1495
self.assertEqual(this.wt.get_file('b').read(),
1496
conflict_text('b', 'b2'))
1498
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1500
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1501
# Ambigious clean merge
1502
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1504
self.assertEqual(this.wt.get_file('f').read(), 'f')
1505
# Correct correct results when THIS == OTHER
1506
self.assertEqual(this.wt.get_file('g').read(), 'g')
1507
# Text conflict when THIS & OTHER are text and BASE is dir
1508
self.assertEqual(this.wt.get_file('h').read(),
1509
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1510
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1512
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1514
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1515
self.assertEqual(this.wt.get_file('i').read(),
1516
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1517
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1519
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1521
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1522
modified = ['a', 'b', 'c', 'h', 'i']
1523
merge_modified = this.wt.merge_modified()
1524
self.assertSubset(merge_modified, modified)
1525
self.assertEqual(len(merge_modified), len(modified))
1526
file(this.wt.id2abspath('a'), 'wb').write('booga')
1528
merge_modified = this.wt.merge_modified()
1529
self.assertSubset(merge_modified, modified)
1530
self.assertEqual(len(merge_modified), len(modified))
1534
def test_file_merge(self):
1535
self.requireFeature(SymlinkFeature)
1536
root_id = generate_ids.gen_root_id()
1537
base = TransformGroup("BASE", root_id)
1538
this = TransformGroup("THIS", root_id)
1539
other = TransformGroup("OTHER", root_id)
1540
for tg in this, base, other:
1541
tg.tt.new_directory('a', tg.root, 'a')
1542
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1543
tg.tt.new_file('c', tg.root, 'c', 'c')
1544
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1545
targets = ((base, 'base-e', 'base-f', None, None),
1546
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1547
(other, 'other-e', None, 'other-g', 'other-h'))
1548
for tg, e_target, f_target, g_target, h_target in targets:
1549
for link, target in (('e', e_target), ('f', f_target),
1550
('g', g_target), ('h', h_target)):
1551
if target is not None:
1552
tg.tt.new_symlink(link, tg.root, target, link)
1554
for tg in this, base, other:
1556
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1557
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1558
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1559
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1560
for suffix in ('THIS', 'BASE', 'OTHER'):
1561
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1562
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1563
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1564
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1565
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1566
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1567
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1568
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1569
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1570
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1571
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1572
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1573
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1574
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1576
def test_filename_merge(self):
1577
root_id = generate_ids.gen_root_id()
1578
base = TransformGroup("BASE", root_id)
1579
this = TransformGroup("THIS", root_id)
1580
other = TransformGroup("OTHER", root_id)
1581
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1582
for t in [base, this, other]]
1583
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1584
for t in [base, this, other]]
1585
base.tt.new_directory('c', base_a, 'c')
1586
this.tt.new_directory('c1', this_a, 'c')
1587
other.tt.new_directory('c', other_b, 'c')
1589
base.tt.new_directory('d', base_a, 'd')
1590
this.tt.new_directory('d1', this_b, 'd')
1591
other.tt.new_directory('d', other_a, 'd')
1593
base.tt.new_directory('e', base_a, 'e')
1594
this.tt.new_directory('e', this_a, 'e')
1595
other.tt.new_directory('e1', other_b, 'e')
1597
base.tt.new_directory('f', base_a, 'f')
1598
this.tt.new_directory('f1', this_b, 'f')
1599
other.tt.new_directory('f1', other_b, 'f')
1601
for tg in [this, base, other]:
1603
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1604
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1605
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1606
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1607
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1609
def test_filename_merge_conflicts(self):
1610
root_id = generate_ids.gen_root_id()
1611
base = TransformGroup("BASE", root_id)
1612
this = TransformGroup("THIS", root_id)
1613
other = TransformGroup("OTHER", root_id)
1614
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1615
for t in [base, this, other]]
1616
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1617
for t in [base, this, other]]
1619
base.tt.new_file('g', base_a, 'g', 'g')
1620
other.tt.new_file('g1', other_b, 'g1', 'g')
1622
base.tt.new_file('h', base_a, 'h', 'h')
1623
this.tt.new_file('h1', this_b, 'h1', 'h')
1625
base.tt.new_file('i', base.root, 'i', 'i')
1626
other.tt.new_directory('i1', this_b, 'i')
1628
for tg in [this, base, other]:
1630
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1632
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1633
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1634
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1635
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1636
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1637
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1638
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1641
class TestBuildTree(tests.TestCaseWithTransport):
1643
def test_build_tree_with_symlinks(self):
1644
self.requireFeature(SymlinkFeature)
1646
a = BzrDir.create_standalone_workingtree('a')
1648
file('a/foo/bar', 'wb').write('contents')
1649
os.symlink('a/foo/bar', 'a/foo/baz')
1650
a.add(['foo', 'foo/bar', 'foo/baz'])
1651
a.commit('initial commit')
1652
b = BzrDir.create_standalone_workingtree('b')
1653
basis = a.basis_tree()
1655
self.addCleanup(basis.unlock)
1656
build_tree(basis, b)
1657
self.assertIs(os.path.isdir('b/foo'), True)
1658
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1659
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1661
def test_build_with_references(self):
1662
tree = self.make_branch_and_tree('source',
1663
format='dirstate-with-subtree')
1664
subtree = self.make_branch_and_tree('source/subtree',
1665
format='dirstate-with-subtree')
1666
tree.add_reference(subtree)
1667
tree.commit('a revision')
1668
tree.branch.create_checkout('target')
1669
self.failUnlessExists('target')
1670
self.failUnlessExists('target/subtree')
1672
def test_file_conflict_handling(self):
1673
"""Ensure that when building trees, conflict handling is done"""
1674
source = self.make_branch_and_tree('source')
1675
target = self.make_branch_and_tree('target')
1676
self.build_tree(['source/file', 'target/file'])
1677
source.add('file', 'new-file')
1678
source.commit('added file')
1679
build_tree(source.basis_tree(), target)
1680
self.assertEqual([DuplicateEntry('Moved existing file to',
1681
'file.moved', 'file', None, 'new-file')],
1683
target2 = self.make_branch_and_tree('target2')
1684
target_file = file('target2/file', 'wb')
1686
source_file = file('source/file', 'rb')
1688
target_file.write(source_file.read())
1693
build_tree(source.basis_tree(), target2)
1694
self.assertEqual([], target2.conflicts())
1696
def test_symlink_conflict_handling(self):
1697
"""Ensure that when building trees, conflict handling is done"""
1698
self.requireFeature(SymlinkFeature)
1699
source = self.make_branch_and_tree('source')
1700
os.symlink('foo', 'source/symlink')
1701
source.add('symlink', 'new-symlink')
1702
source.commit('added file')
1703
target = self.make_branch_and_tree('target')
1704
os.symlink('bar', 'target/symlink')
1705
build_tree(source.basis_tree(), target)
1706
self.assertEqual([DuplicateEntry('Moved existing file to',
1707
'symlink.moved', 'symlink', None, 'new-symlink')],
1709
target = self.make_branch_and_tree('target2')
1710
os.symlink('foo', 'target2/symlink')
1711
build_tree(source.basis_tree(), target)
1712
self.assertEqual([], target.conflicts())
1714
def test_directory_conflict_handling(self):
1715
"""Ensure that when building trees, conflict handling is done"""
1716
source = self.make_branch_and_tree('source')
1717
target = self.make_branch_and_tree('target')
1718
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1719
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1720
source.commit('added file')
1721
build_tree(source.basis_tree(), target)
1722
self.assertEqual([], target.conflicts())
1723
self.failUnlessExists('target/dir1/file')
1725
# Ensure contents are merged
1726
target = self.make_branch_and_tree('target2')
1727
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1728
build_tree(source.basis_tree(), target)
1729
self.assertEqual([], target.conflicts())
1730
self.failUnlessExists('target2/dir1/file2')
1731
self.failUnlessExists('target2/dir1/file')
1733
# Ensure new contents are suppressed for existing branches
1734
target = self.make_branch_and_tree('target3')
1735
self.make_branch('target3/dir1')
1736
self.build_tree(['target3/dir1/file2'])
1737
build_tree(source.basis_tree(), target)
1738
self.failIfExists('target3/dir1/file')
1739
self.failUnlessExists('target3/dir1/file2')
1740
self.failUnlessExists('target3/dir1.diverted/file')
1741
self.assertEqual([DuplicateEntry('Diverted to',
1742
'dir1.diverted', 'dir1', 'new-dir1', None)],
1745
target = self.make_branch_and_tree('target4')
1746
self.build_tree(['target4/dir1/'])
1747
self.make_branch('target4/dir1/file')
1748
build_tree(source.basis_tree(), target)
1749
self.failUnlessExists('target4/dir1/file')
1750
self.assertEqual('directory', file_kind('target4/dir1/file'))
1751
self.failUnlessExists('target4/dir1/file.diverted')
1752
self.assertEqual([DuplicateEntry('Diverted to',
1753
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1756
def test_mixed_conflict_handling(self):
1757
"""Ensure that when building trees, conflict handling is done"""
1758
source = self.make_branch_and_tree('source')
1759
target = self.make_branch_and_tree('target')
1760
self.build_tree(['source/name', 'target/name/'])
1761
source.add('name', 'new-name')
1762
source.commit('added file')
1763
build_tree(source.basis_tree(), target)
1764
self.assertEqual([DuplicateEntry('Moved existing file to',
1765
'name.moved', 'name', None, 'new-name')], target.conflicts())
1767
def test_raises_in_populated(self):
1768
source = self.make_branch_and_tree('source')
1769
self.build_tree(['source/name'])
1771
source.commit('added name')
1772
target = self.make_branch_and_tree('target')
1773
self.build_tree(['target/name'])
1775
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1776
build_tree, source.basis_tree(), target)
1778
def test_build_tree_rename_count(self):
1779
source = self.make_branch_and_tree('source')
1780
self.build_tree(['source/file1', 'source/dir1/'])
1781
source.add(['file1', 'dir1'])
1782
source.commit('add1')
1783
target1 = self.make_branch_and_tree('target1')
1784
transform_result = build_tree(source.basis_tree(), target1)
1785
self.assertEqual(2, transform_result.rename_count)
1787
self.build_tree(['source/dir1/file2'])
1788
source.add(['dir1/file2'])
1789
source.commit('add3')
1790
target2 = self.make_branch_and_tree('target2')
1791
transform_result = build_tree(source.basis_tree(), target2)
1792
# children of non-root directories should not be renamed
1793
self.assertEqual(2, transform_result.rename_count)
1795
def create_ab_tree(self):
1796
"""Create a committed test tree with two files"""
1797
source = self.make_branch_and_tree('source')
1798
self.build_tree_contents([('source/file1', 'A')])
1799
self.build_tree_contents([('source/file2', 'B')])
1800
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1801
source.commit('commit files')
1803
self.addCleanup(source.unlock)
1806
def test_build_tree_accelerator_tree(self):
1807
source = self.create_ab_tree()
1808
self.build_tree_contents([('source/file2', 'C')])
1810
real_source_get_file = source.get_file
1811
def get_file(file_id, path=None):
1812
calls.append(file_id)
1813
return real_source_get_file(file_id, path)
1814
source.get_file = get_file
1815
target = self.make_branch_and_tree('target')
1816
revision_tree = source.basis_tree()
1817
revision_tree.lock_read()
1818
self.addCleanup(revision_tree.unlock)
1819
build_tree(revision_tree, target, source)
1820
self.assertEqual(['file1-id'], calls)
1822
self.addCleanup(target.unlock)
1823
self.assertEqual([], list(target.iter_changes(revision_tree)))
1825
def test_build_tree_accelerator_tree_missing_file(self):
1826
source = self.create_ab_tree()
1827
os.unlink('source/file1')
1828
source.remove(['file2'])
1829
target = self.make_branch_and_tree('target')
1830
revision_tree = source.basis_tree()
1831
revision_tree.lock_read()
1832
self.addCleanup(revision_tree.unlock)
1833
build_tree(revision_tree, target, source)
1835
self.addCleanup(target.unlock)
1836
self.assertEqual([], list(target.iter_changes(revision_tree)))
1838
def test_build_tree_accelerator_wrong_kind(self):
1839
self.requireFeature(SymlinkFeature)
1840
source = self.make_branch_and_tree('source')
1841
self.build_tree_contents([('source/file1', '')])
1842
self.build_tree_contents([('source/file2', '')])
1843
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1844
source.commit('commit files')
1845
os.unlink('source/file2')
1846
self.build_tree_contents([('source/file2/', 'C')])
1847
os.unlink('source/file1')
1848
os.symlink('file2', 'source/file1')
1850
real_source_get_file = source.get_file
1851
def get_file(file_id, path=None):
1852
calls.append(file_id)
1853
return real_source_get_file(file_id, path)
1854
source.get_file = get_file
1855
target = self.make_branch_and_tree('target')
1856
revision_tree = source.basis_tree()
1857
revision_tree.lock_read()
1858
self.addCleanup(revision_tree.unlock)
1859
build_tree(revision_tree, target, source)
1860
self.assertEqual([], calls)
1862
self.addCleanup(target.unlock)
1863
self.assertEqual([], list(target.iter_changes(revision_tree)))
1865
def test_build_tree_hardlink(self):
1866
self.requireFeature(HardlinkFeature)
1867
source = self.create_ab_tree()
1868
target = self.make_branch_and_tree('target')
1869
revision_tree = source.basis_tree()
1870
revision_tree.lock_read()
1871
self.addCleanup(revision_tree.unlock)
1872
build_tree(revision_tree, target, source, hardlink=True)
1874
self.addCleanup(target.unlock)
1875
self.assertEqual([], list(target.iter_changes(revision_tree)))
1876
source_stat = os.stat('source/file1')
1877
target_stat = os.stat('target/file1')
1878
self.assertEqual(source_stat, target_stat)
1880
# Explicitly disallowing hardlinks should prevent them.
1881
target2 = self.make_branch_and_tree('target2')
1882
build_tree(revision_tree, target2, source, hardlink=False)
1884
self.addCleanup(target2.unlock)
1885
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1886
source_stat = os.stat('source/file1')
1887
target2_stat = os.stat('target2/file1')
1888
self.assertNotEqual(source_stat, target2_stat)
1890
def test_build_tree_accelerator_tree_moved(self):
1891
source = self.make_branch_and_tree('source')
1892
self.build_tree_contents([('source/file1', 'A')])
1893
source.add(['file1'], ['file1-id'])
1894
source.commit('commit files')
1895
source.rename_one('file1', 'file2')
1897
self.addCleanup(source.unlock)
1898
target = self.make_branch_and_tree('target')
1899
revision_tree = source.basis_tree()
1900
revision_tree.lock_read()
1901
self.addCleanup(revision_tree.unlock)
1902
build_tree(revision_tree, target, source)
1904
self.addCleanup(target.unlock)
1905
self.assertEqual([], list(target.iter_changes(revision_tree)))
1907
def test_build_tree_hardlinks_preserve_execute(self):
1908
self.requireFeature(HardlinkFeature)
1909
source = self.create_ab_tree()
1910
tt = TreeTransform(source)
1911
trans_id = tt.trans_id_tree_file_id('file1-id')
1912
tt.set_executability(True, trans_id)
1914
self.assertTrue(source.is_executable('file1-id'))
1915
target = self.make_branch_and_tree('target')
1916
revision_tree = source.basis_tree()
1917
revision_tree.lock_read()
1918
self.addCleanup(revision_tree.unlock)
1919
build_tree(revision_tree, target, source, hardlink=True)
1921
self.addCleanup(target.unlock)
1922
self.assertEqual([], list(target.iter_changes(revision_tree)))
1923
self.assertTrue(source.is_executable('file1-id'))
1925
def install_rot13_content_filter(self, pattern):
1926
original_registry = filters._reset_registry()
1927
def restore_registry():
1928
filters._reset_registry(original_registry)
1929
self.addCleanup(restore_registry)
1930
def rot13(chunks, context=None):
1931
return [''.join(chunks).encode('rot13')]
1932
rot13filter = filters.ContentFilter(rot13, rot13)
1933
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1934
os.mkdir(self.test_home_dir + '/.bazaar')
1935
rules_filename = self.test_home_dir + '/.bazaar/rules'
1936
f = open(rules_filename, 'wb')
1937
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1939
def uninstall_rules():
1940
os.remove(rules_filename)
1942
self.addCleanup(uninstall_rules)
1945
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1946
"""build_tree will not hardlink files that have content filtering rules
1947
applied to them (but will still hardlink other files from the same tree
1950
self.requireFeature(HardlinkFeature)
1951
self.install_rot13_content_filter('file1')
1952
source = self.create_ab_tree()
1953
target = self.make_branch_and_tree('target')
1954
revision_tree = source.basis_tree()
1955
revision_tree.lock_read()
1956
self.addCleanup(revision_tree.unlock)
1957
build_tree(revision_tree, target, source, hardlink=True)
1959
self.addCleanup(target.unlock)
1960
self.assertEqual([], list(target.iter_changes(revision_tree)))
1961
source_stat = os.stat('source/file1')
1962
target_stat = os.stat('target/file1')
1963
self.assertNotEqual(source_stat, target_stat)
1964
source_stat = os.stat('source/file2')
1965
target_stat = os.stat('target/file2')
1966
self.assertEqualStat(source_stat, target_stat)
1968
def test_case_insensitive_build_tree_inventory(self):
1969
if (tests.CaseInsensitiveFilesystemFeature.available()
1970
or tests.CaseInsCasePresFilenameFeature.available()):
1971
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1972
source = self.make_branch_and_tree('source')
1973
self.build_tree(['source/file', 'source/FILE'])
1974
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1975
source.commit('added files')
1976
# Don't try this at home, kids!
1977
# Force the tree to report that it is case insensitive
1978
target = self.make_branch_and_tree('target')
1979
target.case_sensitive = False
1980
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1981
self.assertEqual('file.moved', target.id2path('lower-id'))
1982
self.assertEqual('FILE', target.id2path('upper-id'))
1985
class TestCommitTransform(tests.TestCaseWithTransport):
1987
def get_branch(self):
1988
tree = self.make_branch_and_tree('tree')
1990
self.addCleanup(tree.unlock)
1991
tree.commit('empty commit')
1994
def get_branch_and_transform(self):
1995
branch = self.get_branch()
1996
tt = TransformPreview(branch.basis_tree())
1997
self.addCleanup(tt.finalize)
2000
def test_commit_wrong_basis(self):
2001
branch = self.get_branch()
2002
basis = branch.repository.revision_tree(
2003
_mod_revision.NULL_REVISION)
2004
tt = TransformPreview(basis)
2005
self.addCleanup(tt.finalize)
2006
e = self.assertRaises(ValueError, tt.commit, branch, '')
2007
self.assertEqual('TreeTransform not based on branch basis: null:',
2010
def test_empy_commit(self):
2011
branch, tt = self.get_branch_and_transform()
2012
rev = tt.commit(branch, 'my message')
2013
self.assertEqual(2, branch.revno())
2014
repo = branch.repository
2015
self.assertEqual('my message', repo.get_revision(rev).message)
2017
def test_merge_parents(self):
2018
branch, tt = self.get_branch_and_transform()
2019
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2020
self.assertEqual(['rev1b', 'rev1c'],
2021
branch.basis_tree().get_parent_ids()[1:])
2023
def test_first_commit(self):
2024
branch = self.make_branch('branch')
2026
self.addCleanup(branch.unlock)
2027
tt = TransformPreview(branch.basis_tree())
2028
self.addCleanup(tt.finalize)
2029
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2030
rev = tt.commit(branch, 'my message')
2031
self.assertEqual([], branch.basis_tree().get_parent_ids())
2032
self.assertNotEqual(_mod_revision.NULL_REVISION,
2033
branch.last_revision())
2035
def test_first_commit_with_merge_parents(self):
2036
branch = self.make_branch('branch')
2038
self.addCleanup(branch.unlock)
2039
tt = TransformPreview(branch.basis_tree())
2040
self.addCleanup(tt.finalize)
2041
e = self.assertRaises(ValueError, tt.commit, branch,
2042
'my message', ['rev1b-id'])
2043
self.assertEqual('Cannot supply merge parents for first commit.',
2045
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2047
def test_add_files(self):
2048
branch, tt = self.get_branch_and_transform()
2049
tt.new_file('file', tt.root, 'contents', 'file-id')
2050
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2051
if SymlinkFeature.available():
2052
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2053
rev = tt.commit(branch, 'message')
2054
tree = branch.basis_tree()
2055
self.assertEqual('file', tree.id2path('file-id'))
2056
self.assertEqual('contents', tree.get_file_text('file-id'))
2057
self.assertEqual('dir', tree.id2path('dir-id'))
2058
if SymlinkFeature.available():
2059
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2060
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2062
def test_add_unversioned(self):
2063
branch, tt = self.get_branch_and_transform()
2064
tt.new_file('file', tt.root, 'contents')
2065
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2066
'message', strict=True)
2068
def test_modify_strict(self):
2069
branch, tt = self.get_branch_and_transform()
2070
tt.new_file('file', tt.root, 'contents', 'file-id')
2071
tt.commit(branch, 'message', strict=True)
2072
tt = TransformPreview(branch.basis_tree())
2073
self.addCleanup(tt.finalize)
2074
trans_id = tt.trans_id_file_id('file-id')
2075
tt.delete_contents(trans_id)
2076
tt.create_file('contents', trans_id)
2077
tt.commit(branch, 'message', strict=True)
2079
def test_commit_malformed(self):
2080
"""Committing a malformed transform should raise an exception.
2082
In this case, we are adding a file without adding its parent.
2084
branch, tt = self.get_branch_and_transform()
2085
parent_id = tt.trans_id_file_id('parent-id')
2086
tt.new_file('file', parent_id, 'contents', 'file-id')
2087
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2091
class MockTransform(object):
2093
def has_named_child(self, by_parent, parent_id, name):
2094
for child_id in by_parent[parent_id]:
2098
elif name == "name.~%s~" % child_id:
2103
class MockEntry(object):
2105
object.__init__(self)
2109
class TestGetBackupName(TestCase):
2110
def test_get_backup_name(self):
2111
tt = MockTransform()
2112
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2113
self.assertEqual(name, 'name.~1~')
2114
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2115
self.assertEqual(name, 'name.~2~')
2116
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2117
self.assertEqual(name, 'name.~1~')
2118
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2119
self.assertEqual(name, 'name.~1~')
2120
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2121
self.assertEqual(name, 'name.~4~')
2124
class TestFileMover(tests.TestCaseWithTransport):
2126
def test_file_mover(self):
2127
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2128
mover = _FileMover()
2129
mover.rename('a', 'q')
2130
self.failUnlessExists('q')
2131
self.failIfExists('a')
2132
self.failUnlessExists('q/b')
2133
self.failUnlessExists('c')
2134
self.failUnlessExists('c/d')
2136
def test_pre_delete_rollback(self):
2137
self.build_tree(['a/'])
2138
mover = _FileMover()
2139
mover.pre_delete('a', 'q')
2140
self.failUnlessExists('q')
2141
self.failIfExists('a')
2143
self.failIfExists('q')
2144
self.failUnlessExists('a')
2146
def test_apply_deletions(self):
2147
self.build_tree(['a/', 'b/'])
2148
mover = _FileMover()
2149
mover.pre_delete('a', 'q')
2150
mover.pre_delete('b', 'r')
2151
self.failUnlessExists('q')
2152
self.failUnlessExists('r')
2153
self.failIfExists('a')
2154
self.failIfExists('b')
2155
mover.apply_deletions()
2156
self.failIfExists('q')
2157
self.failIfExists('r')
2158
self.failIfExists('a')
2159
self.failIfExists('b')
2161
def test_file_mover_rollback(self):
2162
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2163
mover = _FileMover()
2164
mover.rename('c/d', 'c/f')
2165
mover.rename('c/e', 'c/d')
2167
mover.rename('a', 'c')
2168
except errors.FileExists, e:
2170
self.failUnlessExists('a')
2171
self.failUnlessExists('c/d')
2174
class Bogus(Exception):
2178
class TestTransformRollback(tests.TestCaseWithTransport):
2180
class ExceptionFileMover(_FileMover):
2182
def __init__(self, bad_source=None, bad_target=None):
2183
_FileMover.__init__(self)
2184
self.bad_source = bad_source
2185
self.bad_target = bad_target
2187
def rename(self, source, target):
2188
if (self.bad_source is not None and
2189
source.endswith(self.bad_source)):
2191
elif (self.bad_target is not None and
2192
target.endswith(self.bad_target)):
2195
_FileMover.rename(self, source, target)
2197
def test_rollback_rename(self):
2198
tree = self.make_branch_and_tree('.')
2199
self.build_tree(['a/', 'a/b'])
2200
tt = TreeTransform(tree)
2201
self.addCleanup(tt.finalize)
2202
a_id = tt.trans_id_tree_path('a')
2203
tt.adjust_path('c', tt.root, a_id)
2204
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2205
self.assertRaises(Bogus, tt.apply,
2206
_mover=self.ExceptionFileMover(bad_source='a'))
2207
self.failUnlessExists('a')
2208
self.failUnlessExists('a/b')
2210
self.failUnlessExists('c')
2211
self.failUnlessExists('c/d')
2213
def test_rollback_rename_into_place(self):
2214
tree = self.make_branch_and_tree('.')
2215
self.build_tree(['a/', 'a/b'])
2216
tt = TreeTransform(tree)
2217
self.addCleanup(tt.finalize)
2218
a_id = tt.trans_id_tree_path('a')
2219
tt.adjust_path('c', tt.root, a_id)
2220
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2221
self.assertRaises(Bogus, tt.apply,
2222
_mover=self.ExceptionFileMover(bad_target='c/d'))
2223
self.failUnlessExists('a')
2224
self.failUnlessExists('a/b')
2226
self.failUnlessExists('c')
2227
self.failUnlessExists('c/d')
2229
def test_rollback_deletion(self):
2230
tree = self.make_branch_and_tree('.')
2231
self.build_tree(['a/', 'a/b'])
2232
tt = TreeTransform(tree)
2233
self.addCleanup(tt.finalize)
2234
a_id = tt.trans_id_tree_path('a')
2235
tt.delete_contents(a_id)
2236
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2237
self.assertRaises(Bogus, tt.apply,
2238
_mover=self.ExceptionFileMover(bad_target='d'))
2239
self.failUnlessExists('a')
2240
self.failUnlessExists('a/b')
2242
def test_resolve_no_parent(self):
2243
wt = self.make_branch_and_tree('.')
2244
tt = TreeTransform(wt)
2245
self.addCleanup(tt.finalize)
2246
parent = tt.trans_id_file_id('parent-id')
2247
tt.new_file('file', parent, 'Contents')
2248
resolve_conflicts(tt)
2251
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2252
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2254
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2255
('', ''), ('directory', 'directory'), (False, None))
2258
class TestTransformPreview(tests.TestCaseWithTransport):
2260
def create_tree(self):
2261
tree = self.make_branch_and_tree('.')
2262
self.build_tree_contents([('a', 'content 1')])
2263
tree.set_root_id('TREE_ROOT')
2264
tree.add('a', 'a-id')
2265
tree.commit('rev1', rev_id='rev1')
2266
return tree.branch.repository.revision_tree('rev1')
2268
def get_empty_preview(self):
2269
repository = self.make_repository('repo')
2270
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2271
preview = TransformPreview(tree)
2272
self.addCleanup(preview.finalize)
2275
def test_transform_preview(self):
2276
revision_tree = self.create_tree()
2277
preview = TransformPreview(revision_tree)
2278
self.addCleanup(preview.finalize)
2280
def test_transform_preview_tree(self):
2281
revision_tree = self.create_tree()
2282
preview = TransformPreview(revision_tree)
2283
self.addCleanup(preview.finalize)
2284
preview.get_preview_tree()
2286
def test_transform_new_file(self):
2287
revision_tree = self.create_tree()
2288
preview = TransformPreview(revision_tree)
2289
self.addCleanup(preview.finalize)
2290
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2291
preview_tree = preview.get_preview_tree()
2292
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2294
preview_tree.get_file('file2-id').read(), 'content B\n')
2296
def test_diff_preview_tree(self):
2297
revision_tree = self.create_tree()
2298
preview = TransformPreview(revision_tree)
2299
self.addCleanup(preview.finalize)
2300
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2301
preview_tree = preview.get_preview_tree()
2303
show_diff_trees(revision_tree, preview_tree, out)
2304
lines = out.getvalue().splitlines()
2305
self.assertEqual(lines[0], "=== added file 'file2'")
2306
# 3 lines of diff administrivia
2307
self.assertEqual(lines[4], "+content B")
2309
def test_transform_conflicts(self):
2310
revision_tree = self.create_tree()
2311
preview = TransformPreview(revision_tree)
2312
self.addCleanup(preview.finalize)
2313
preview.new_file('a', preview.root, 'content 2')
2314
resolve_conflicts(preview)
2315
trans_id = preview.trans_id_file_id('a-id')
2316
self.assertEqual('a.moved', preview.final_name(trans_id))
2318
def get_tree_and_preview_tree(self):
2319
revision_tree = self.create_tree()
2320
preview = TransformPreview(revision_tree)
2321
self.addCleanup(preview.finalize)
2322
a_trans_id = preview.trans_id_file_id('a-id')
2323
preview.delete_contents(a_trans_id)
2324
preview.create_file('b content', a_trans_id)
2325
preview_tree = preview.get_preview_tree()
2326
return revision_tree, preview_tree
2328
def test_iter_changes(self):
2329
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2330
root = revision_tree.inventory.root.file_id
2331
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2332
(root, root), ('a', 'a'), ('file', 'file'),
2334
list(preview_tree.iter_changes(revision_tree)))
2336
def test_include_unchanged_succeeds(self):
2337
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2338
changes = preview_tree.iter_changes(revision_tree,
2339
include_unchanged=True)
2340
root = revision_tree.inventory.root.file_id
2342
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2344
def test_specific_files(self):
2345
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2346
changes = preview_tree.iter_changes(revision_tree,
2347
specific_files=[''])
2348
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2350
def test_want_unversioned(self):
2351
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2352
changes = preview_tree.iter_changes(revision_tree,
2353
want_unversioned=True)
2354
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2356
def test_ignore_extra_trees_no_specific_files(self):
2357
# extra_trees is harmless without specific_files, so we'll silently
2358
# accept it, even though we won't use it.
2359
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2360
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2362
def test_ignore_require_versioned_no_specific_files(self):
2363
# require_versioned is meaningless without specific_files.
2364
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2365
preview_tree.iter_changes(revision_tree, require_versioned=False)
2367
def test_ignore_pb(self):
2368
# pb could be supported, but TT.iter_changes doesn't support it.
2369
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2370
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2372
def test_kind(self):
2373
revision_tree = self.create_tree()
2374
preview = TransformPreview(revision_tree)
2375
self.addCleanup(preview.finalize)
2376
preview.new_file('file', preview.root, 'contents', 'file-id')
2377
preview.new_directory('directory', preview.root, 'dir-id')
2378
preview_tree = preview.get_preview_tree()
2379
self.assertEqual('file', preview_tree.kind('file-id'))
2380
self.assertEqual('directory', preview_tree.kind('dir-id'))
2382
def test_get_file_mtime(self):
2383
preview = self.get_empty_preview()
2384
file_trans_id = preview.new_file('file', preview.root, 'contents',
2386
limbo_path = preview._limbo_name(file_trans_id)
2387
preview_tree = preview.get_preview_tree()
2388
self.assertEqual(os.stat(limbo_path).st_mtime,
2389
preview_tree.get_file_mtime('file-id'))
2391
def test_get_file_mtime_renamed(self):
2392
work_tree = self.make_branch_and_tree('tree')
2393
self.build_tree(['tree/file'])
2394
work_tree.add('file', 'file-id')
2395
preview = TransformPreview(work_tree)
2396
self.addCleanup(preview.finalize)
2397
file_trans_id = preview.trans_id_tree_file_id('file-id')
2398
preview.adjust_path('renamed', preview.root, file_trans_id)
2399
preview_tree = preview.get_preview_tree()
2400
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2401
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2403
def test_get_file(self):
2404
preview = self.get_empty_preview()
2405
preview.new_file('file', preview.root, 'contents', 'file-id')
2406
preview_tree = preview.get_preview_tree()
2407
tree_file = preview_tree.get_file('file-id')
2409
self.assertEqual('contents', tree_file.read())
2413
def test_get_symlink_target(self):
2414
self.requireFeature(SymlinkFeature)
2415
preview = self.get_empty_preview()
2416
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2417
preview_tree = preview.get_preview_tree()
2418
self.assertEqual('target',
2419
preview_tree.get_symlink_target('symlink-id'))
2421
def test_all_file_ids(self):
2422
tree = self.make_branch_and_tree('tree')
2423
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2424
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2425
preview = TransformPreview(tree)
2426
self.addCleanup(preview.finalize)
2427
preview.unversion_file(preview.trans_id_file_id('b-id'))
2428
c_trans_id = preview.trans_id_file_id('c-id')
2429
preview.unversion_file(c_trans_id)
2430
preview.version_file('c-id', c_trans_id)
2431
preview_tree = preview.get_preview_tree()
2432
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2433
preview_tree.all_file_ids())
2435
def test_path2id_deleted_unchanged(self):
2436
tree = self.make_branch_and_tree('tree')
2437
self.build_tree(['tree/unchanged', 'tree/deleted'])
2438
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2439
preview = TransformPreview(tree)
2440
self.addCleanup(preview.finalize)
2441
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2442
preview_tree = preview.get_preview_tree()
2443
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2444
self.assertIs(None, preview_tree.path2id('deleted'))
2446
def test_path2id_created(self):
2447
tree = self.make_branch_and_tree('tree')
2448
self.build_tree(['tree/unchanged'])
2449
tree.add(['unchanged'], ['unchanged-id'])
2450
preview = TransformPreview(tree)
2451
self.addCleanup(preview.finalize)
2452
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2453
'contents', 'new-id')
2454
preview_tree = preview.get_preview_tree()
2455
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2457
def test_path2id_moved(self):
2458
tree = self.make_branch_and_tree('tree')
2459
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2460
tree.add(['old_parent', 'old_parent/child'],
2461
['old_parent-id', 'child-id'])
2462
preview = TransformPreview(tree)
2463
self.addCleanup(preview.finalize)
2464
new_parent = preview.new_directory('new_parent', preview.root,
2466
preview.adjust_path('child', new_parent,
2467
preview.trans_id_file_id('child-id'))
2468
preview_tree = preview.get_preview_tree()
2469
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2470
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2472
def test_path2id_renamed_parent(self):
2473
tree = self.make_branch_and_tree('tree')
2474
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2475
tree.add(['old_name', 'old_name/child'],
2476
['parent-id', 'child-id'])
2477
preview = TransformPreview(tree)
2478
self.addCleanup(preview.finalize)
2479
preview.adjust_path('new_name', preview.root,
2480
preview.trans_id_file_id('parent-id'))
2481
preview_tree = preview.get_preview_tree()
2482
self.assertIs(None, preview_tree.path2id('old_name/child'))
2483
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2485
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2486
preview_tree = tt.get_preview_tree()
2487
preview_result = list(preview_tree.iter_entries_by_dir(
2491
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2492
self.assertEqual(actual_result, preview_result)
2494
def test_iter_entries_by_dir_new(self):
2495
tree = self.make_branch_and_tree('tree')
2496
tt = TreeTransform(tree)
2497
tt.new_file('new', tt.root, 'contents', 'new-id')
2498
self.assertMatchingIterEntries(tt)
2500
def test_iter_entries_by_dir_deleted(self):
2501
tree = self.make_branch_and_tree('tree')
2502
self.build_tree(['tree/deleted'])
2503
tree.add('deleted', 'deleted-id')
2504
tt = TreeTransform(tree)
2505
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2506
self.assertMatchingIterEntries(tt)
2508
def test_iter_entries_by_dir_unversioned(self):
2509
tree = self.make_branch_and_tree('tree')
2510
self.build_tree(['tree/removed'])
2511
tree.add('removed', 'removed-id')
2512
tt = TreeTransform(tree)
2513
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2514
self.assertMatchingIterEntries(tt)
2516
def test_iter_entries_by_dir_moved(self):
2517
tree = self.make_branch_and_tree('tree')
2518
self.build_tree(['tree/moved', 'tree/new_parent/'])
2519
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2520
tt = TreeTransform(tree)
2521
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2522
tt.trans_id_file_id('moved-id'))
2523
self.assertMatchingIterEntries(tt)
2525
def test_iter_entries_by_dir_specific_file_ids(self):
2526
tree = self.make_branch_and_tree('tree')
2527
tree.set_root_id('tree-root-id')
2528
self.build_tree(['tree/parent/', 'tree/parent/child'])
2529
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2530
tt = TreeTransform(tree)
2531
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2533
def test_symlink_content_summary(self):
2534
self.requireFeature(SymlinkFeature)
2535
preview = self.get_empty_preview()
2536
preview.new_symlink('path', preview.root, 'target', 'path-id')
2537
summary = preview.get_preview_tree().path_content_summary('path')
2538
self.assertEqual(('symlink', None, None, 'target'), summary)
2540
def test_missing_content_summary(self):
2541
preview = self.get_empty_preview()
2542
summary = preview.get_preview_tree().path_content_summary('path')
2543
self.assertEqual(('missing', None, None, None), summary)
2545
def test_deleted_content_summary(self):
2546
tree = self.make_branch_and_tree('tree')
2547
self.build_tree(['tree/path/'])
2549
preview = TransformPreview(tree)
2550
self.addCleanup(preview.finalize)
2551
preview.delete_contents(preview.trans_id_tree_path('path'))
2552
summary = preview.get_preview_tree().path_content_summary('path')
2553
self.assertEqual(('missing', None, None, None), summary)
2555
def test_file_content_summary_executable(self):
2556
preview = self.get_empty_preview()
2557
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2558
preview.set_executability(True, path_id)
2559
summary = preview.get_preview_tree().path_content_summary('path')
2560
self.assertEqual(4, len(summary))
2561
self.assertEqual('file', summary[0])
2562
# size must be known
2563
self.assertEqual(len('contents'), summary[1])
2565
self.assertEqual(True, summary[2])
2566
# will not have hash (not cheap to determine)
2567
self.assertIs(None, summary[3])
2569
def test_change_executability(self):
2570
tree = self.make_branch_and_tree('tree')
2571
self.build_tree(['tree/path'])
2573
preview = TransformPreview(tree)
2574
self.addCleanup(preview.finalize)
2575
path_id = preview.trans_id_tree_path('path')
2576
preview.set_executability(True, path_id)
2577
summary = preview.get_preview_tree().path_content_summary('path')
2578
self.assertEqual(True, summary[2])
2580
def test_file_content_summary_non_exec(self):
2581
preview = self.get_empty_preview()
2582
preview.new_file('path', preview.root, 'contents', 'path-id')
2583
summary = preview.get_preview_tree().path_content_summary('path')
2584
self.assertEqual(4, len(summary))
2585
self.assertEqual('file', summary[0])
2586
# size must be known
2587
self.assertEqual(len('contents'), summary[1])
2589
self.assertEqual(False, summary[2])
2590
# will not have hash (not cheap to determine)
2591
self.assertIs(None, summary[3])
2593
def test_dir_content_summary(self):
2594
preview = self.get_empty_preview()
2595
preview.new_directory('path', preview.root, 'path-id')
2596
summary = preview.get_preview_tree().path_content_summary('path')
2597
self.assertEqual(('directory', None, None, None), summary)
2599
def test_tree_content_summary(self):
2600
preview = self.get_empty_preview()
2601
path = preview.new_directory('path', preview.root, 'path-id')
2602
preview.set_tree_reference('rev-1', path)
2603
summary = preview.get_preview_tree().path_content_summary('path')
2604
self.assertEqual(4, len(summary))
2605
self.assertEqual('tree-reference', summary[0])
2607
def test_annotate(self):
2608
tree = self.make_branch_and_tree('tree')
2609
self.build_tree_contents([('tree/file', 'a\n')])
2610
tree.add('file', 'file-id')
2611
tree.commit('a', rev_id='one')
2612
self.build_tree_contents([('tree/file', 'a\nb\n')])
2613
preview = TransformPreview(tree)
2614
self.addCleanup(preview.finalize)
2615
file_trans_id = preview.trans_id_file_id('file-id')
2616
preview.delete_contents(file_trans_id)
2617
preview.create_file('a\nb\nc\n', file_trans_id)
2618
preview_tree = preview.get_preview_tree()
2624
annotation = preview_tree.annotate_iter('file-id', 'me:')
2625
self.assertEqual(expected, annotation)
2627
def test_annotate_missing(self):
2628
preview = self.get_empty_preview()
2629
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2630
preview_tree = preview.get_preview_tree()
2636
annotation = preview_tree.annotate_iter('file-id', 'me:')
2637
self.assertEqual(expected, annotation)
2639
def test_annotate_rename(self):
2640
tree = self.make_branch_and_tree('tree')
2641
self.build_tree_contents([('tree/file', 'a\n')])
2642
tree.add('file', 'file-id')
2643
tree.commit('a', rev_id='one')
2644
preview = TransformPreview(tree)
2645
self.addCleanup(preview.finalize)
2646
file_trans_id = preview.trans_id_file_id('file-id')
2647
preview.adjust_path('newname', preview.root, file_trans_id)
2648
preview_tree = preview.get_preview_tree()
2652
annotation = preview_tree.annotate_iter('file-id', 'me:')
2653
self.assertEqual(expected, annotation)
2655
def test_annotate_deleted(self):
2656
tree = self.make_branch_and_tree('tree')
2657
self.build_tree_contents([('tree/file', 'a\n')])
2658
tree.add('file', 'file-id')
2659
tree.commit('a', rev_id='one')
2660
self.build_tree_contents([('tree/file', 'a\nb\n')])
2661
preview = TransformPreview(tree)
2662
self.addCleanup(preview.finalize)
2663
file_trans_id = preview.trans_id_file_id('file-id')
2664
preview.delete_contents(file_trans_id)
2665
preview_tree = preview.get_preview_tree()
2666
annotation = preview_tree.annotate_iter('file-id', 'me:')
2667
self.assertIs(None, annotation)
2669
def test_stored_kind(self):
2670
preview = self.get_empty_preview()
2671
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2672
preview_tree = preview.get_preview_tree()
2673
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2675
def test_is_executable(self):
2676
preview = self.get_empty_preview()
2677
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2678
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2679
preview_tree = preview.get_preview_tree()
2680
self.assertEqual(True, preview_tree.is_executable('file-id'))
2682
def test_get_set_parent_ids(self):
2683
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2684
self.assertEqual([], preview_tree.get_parent_ids())
2685
preview_tree.set_parent_ids(['rev-1'])
2686
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2688
def test_plan_file_merge(self):
2689
work_a = self.make_branch_and_tree('wta')
2690
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2691
work_a.add('file', 'file-id')
2692
base_id = work_a.commit('base version')
2693
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2694
preview = TransformPreview(work_a)
2695
self.addCleanup(preview.finalize)
2696
trans_id = preview.trans_id_file_id('file-id')
2697
preview.delete_contents(trans_id)
2698
preview.create_file('b\nc\nd\ne\n', trans_id)
2699
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2700
tree_a = preview.get_preview_tree()
2701
tree_a.set_parent_ids([base_id])
2703
('killed-a', 'a\n'),
2704
('killed-b', 'b\n'),
2705
('unchanged', 'c\n'),
2706
('unchanged', 'd\n'),
2709
], list(tree_a.plan_file_merge('file-id', tree_b)))
2711
def test_plan_file_merge_revision_tree(self):
2712
work_a = self.make_branch_and_tree('wta')
2713
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2714
work_a.add('file', 'file-id')
2715
base_id = work_a.commit('base version')
2716
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2717
preview = TransformPreview(work_a.basis_tree())
2718
self.addCleanup(preview.finalize)
2719
trans_id = preview.trans_id_file_id('file-id')
2720
preview.delete_contents(trans_id)
2721
preview.create_file('b\nc\nd\ne\n', trans_id)
2722
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2723
tree_a = preview.get_preview_tree()
2724
tree_a.set_parent_ids([base_id])
2726
('killed-a', 'a\n'),
2727
('killed-b', 'b\n'),
2728
('unchanged', 'c\n'),
2729
('unchanged', 'd\n'),
2732
], list(tree_a.plan_file_merge('file-id', tree_b)))
2734
def test_walkdirs(self):
2735
preview = self.get_empty_preview()
2736
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2737
# FIXME: new_directory should mark root.
2738
preview.fixup_new_roots()
2739
preview_tree = preview.get_preview_tree()
2740
file_trans_id = preview.new_file('a', preview.root, 'contents',
2742
expected = [(('', 'tree-root'),
2743
[('a', 'a', 'file', None, 'a-id', 'file')])]
2744
self.assertEqual(expected, list(preview_tree.walkdirs()))
2746
def test_extras(self):
2747
work_tree = self.make_branch_and_tree('tree')
2748
self.build_tree(['tree/removed-file', 'tree/existing-file',
2749
'tree/not-removed-file'])
2750
work_tree.add(['removed-file', 'not-removed-file'])
2751
preview = TransformPreview(work_tree)
2752
self.addCleanup(preview.finalize)
2753
preview.new_file('new-file', preview.root, 'contents')
2754
preview.new_file('new-versioned-file', preview.root, 'contents',
2756
tree = preview.get_preview_tree()
2757
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2758
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2761
def test_merge_into_preview(self):
2762
work_tree = self.make_branch_and_tree('tree')
2763
self.build_tree_contents([('tree/file','b\n')])
2764
work_tree.add('file', 'file-id')
2765
work_tree.commit('first commit')
2766
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2767
self.build_tree_contents([('child/file','b\nc\n')])
2768
child_tree.commit('child commit')
2769
child_tree.lock_write()
2770
self.addCleanup(child_tree.unlock)
2771
work_tree.lock_write()
2772
self.addCleanup(work_tree.unlock)
2773
preview = TransformPreview(work_tree)
2774
self.addCleanup(preview.finalize)
2775
file_trans_id = preview.trans_id_file_id('file-id')
2776
preview.delete_contents(file_trans_id)
2777
preview.create_file('a\nb\n', file_trans_id)
2778
pb = progress.DummyProgress()
2779
preview_tree = preview.get_preview_tree()
2780
merger = Merger.from_revision_ids(pb, preview_tree,
2781
child_tree.branch.last_revision(),
2782
other_branch=child_tree.branch,
2783
tree_branch=work_tree.branch)
2784
merger.merge_type = Merge3Merger
2785
tt = merger.make_merger().make_preview_transform()
2786
self.addCleanup(tt.finalize)
2787
final_tree = tt.get_preview_tree()
2788
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2790
def test_merge_preview_into_workingtree(self):
2791
tree = self.make_branch_and_tree('tree')
2792
tree.set_root_id('TREE_ROOT')
2793
tt = TransformPreview(tree)
2794
self.addCleanup(tt.finalize)
2795
tt.new_file('name', tt.root, 'content', 'file-id')
2796
tree2 = self.make_branch_and_tree('tree2')
2797
tree2.set_root_id('TREE_ROOT')
2798
pb = progress.DummyProgress()
2799
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2800
pb, tree.basis_tree())
2801
merger.merge_type = Merge3Merger
2804
def test_merge_preview_into_workingtree_handles_conflicts(self):
2805
tree = self.make_branch_and_tree('tree')
2806
self.build_tree_contents([('tree/foo', 'bar')])
2807
tree.add('foo', 'foo-id')
2809
tt = TransformPreview(tree)
2810
self.addCleanup(tt.finalize)
2811
trans_id = tt.trans_id_file_id('foo-id')
2812
tt.delete_contents(trans_id)
2813
tt.create_file('baz', trans_id)
2814
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2815
self.build_tree_contents([('tree2/foo', 'qux')])
2816
pb = progress.DummyProgress()
2817
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2818
pb, tree.basis_tree())
2819
merger.merge_type = Merge3Merger
2822
def test_is_executable(self):
2823
tree = self.make_branch_and_tree('tree')
2824
preview = TransformPreview(tree)
2825
self.addCleanup(preview.finalize)
2826
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2827
preview_tree = preview.get_preview_tree()
2828
self.assertEqual(False, preview_tree.is_executable('baz-id',
2830
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2832
def test_commit_preview_tree(self):
2833
tree = self.make_branch_and_tree('tree')
2834
rev_id = tree.commit('rev1')
2835
tree.branch.lock_write()
2836
self.addCleanup(tree.branch.unlock)
2837
tt = TransformPreview(tree)
2838
tt.new_file('file', tt.root, 'contents', 'file_id')
2839
self.addCleanup(tt.finalize)
2840
preview = tt.get_preview_tree()
2841
preview.set_parent_ids([rev_id])
2842
builder = tree.branch.get_commit_builder([rev_id])
2843
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2844
builder.finish_inventory()
2845
rev2_id = builder.commit('rev2')
2846
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2847
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2849
def test_ascii_limbo_paths(self):
2850
self.requireFeature(tests.UnicodeFilenameFeature)
2851
branch = self.make_branch('any')
2852
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2853
tt = TransformPreview(tree)
2854
self.addCleanup(tt.finalize)
2855
foo_id = tt.new_directory('', ROOT_PARENT)
2856
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2857
limbo_path = tt._limbo_name(bar_id)
2858
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2861
class FakeSerializer(object):
2862
"""Serializer implementation that simply returns the input.
2864
The input is returned in the order used by pack.ContainerPushParser.
2867
def bytes_record(bytes, names):
2871
class TestSerializeTransform(tests.TestCaseWithTransport):
2873
_test_needs_features = [tests.UnicodeFilenameFeature]
2875
def get_preview(self, tree=None):
2877
tree = self.make_branch_and_tree('tree')
2878
tt = TransformPreview(tree)
2879
self.addCleanup(tt.finalize)
2882
def assertSerializesTo(self, expected, tt):
2883
records = list(tt.serialize(FakeSerializer()))
2884
self.assertEqual(expected, records)
2887
def default_attribs():
2892
'_new_executability': {},
2894
'_tree_path_ids': {'': 'new-0'},
2896
'_removed_contents': [],
2897
'_non_present_ids': {},
2900
def make_records(self, attribs, contents):
2902
(((('attribs'),),), bencode.bencode(attribs))]
2903
records.extend([(((n, k),), c) for n, k, c in contents])
2906
def creation_records(self):
2907
attribs = self.default_attribs()
2908
attribs['_id_number'] = 3
2909
attribs['_new_name'] = {
2910
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2911
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2912
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2913
attribs['_new_executability'] = {'new-1': 1}
2915
('new-1', 'file', 'i 1\nbar\n'),
2916
('new-2', 'directory', ''),
2918
return self.make_records(attribs, contents)
2920
def test_serialize_creation(self):
2921
tt = self.get_preview()
2922
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2923
tt.new_directory('qux', tt.root, 'quxx')
2924
self.assertSerializesTo(self.creation_records(), tt)
2926
def test_deserialize_creation(self):
2927
tt = self.get_preview()
2928
tt.deserialize(iter(self.creation_records()))
2929
self.assertEqual(3, tt._id_number)
2930
self.assertEqual({'new-1': u'foo\u1234',
2931
'new-2': 'qux'}, tt._new_name)
2932
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2933
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2934
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2935
self.assertEqual({'new-1': True}, tt._new_executability)
2936
self.assertEqual({'new-1': 'file',
2937
'new-2': 'directory'}, tt._new_contents)
2938
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2940
foo_content = foo_limbo.read()
2943
self.assertEqual('bar', foo_content)
2945
def symlink_creation_records(self):
2946
attribs = self.default_attribs()
2947
attribs['_id_number'] = 2
2948
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2949
attribs['_new_parent'] = {'new-1': 'new-0'}
2950
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2951
return self.make_records(attribs, contents)
2953
def test_serialize_symlink_creation(self):
2954
self.requireFeature(tests.SymlinkFeature)
2955
tt = self.get_preview()
2956
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2957
self.assertSerializesTo(self.symlink_creation_records(), tt)
2959
def test_deserialize_symlink_creation(self):
2960
self.requireFeature(tests.SymlinkFeature)
2961
tt = self.get_preview()
2962
tt.deserialize(iter(self.symlink_creation_records()))
2963
abspath = tt._limbo_name('new-1')
2964
foo_content = osutils.readlink(abspath)
2965
self.assertEqual(u'bar\u1234', foo_content)
2967
def make_destruction_preview(self):
2968
tree = self.make_branch_and_tree('.')
2969
self.build_tree([u'foo\u1234', 'bar'])
2970
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2971
return self.get_preview(tree)
2973
def destruction_records(self):
2974
attribs = self.default_attribs()
2975
attribs['_id_number'] = 3
2976
attribs['_removed_id'] = ['new-1']
2977
attribs['_removed_contents'] = ['new-2']
2978
attribs['_tree_path_ids'] = {
2980
u'foo\u1234'.encode('utf-8'): 'new-1',
2983
return self.make_records(attribs, [])
2985
def test_serialize_destruction(self):
2986
tt = self.make_destruction_preview()
2987
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2988
tt.unversion_file(foo_trans_id)
2989
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2990
tt.delete_contents(bar_trans_id)
2991
self.assertSerializesTo(self.destruction_records(), tt)
2993
def test_deserialize_destruction(self):
2994
tt = self.make_destruction_preview()
2995
tt.deserialize(iter(self.destruction_records()))
2996
self.assertEqual({u'foo\u1234': 'new-1',
2998
'': tt.root}, tt._tree_path_ids)
2999
self.assertEqual({'new-1': u'foo\u1234',
3001
tt.root: ''}, tt._tree_id_paths)
3002
self.assertEqual(set(['new-1']), tt._removed_id)
3003
self.assertEqual(set(['new-2']), tt._removed_contents)
3005
def missing_records(self):
3006
attribs = self.default_attribs()
3007
attribs['_id_number'] = 2
3008
attribs['_non_present_ids'] = {
3010
return self.make_records(attribs, [])
3012
def test_serialize_missing(self):
3013
tt = self.get_preview()
3014
boo_trans_id = tt.trans_id_file_id('boo')
3015
self.assertSerializesTo(self.missing_records(), tt)
3017
def test_deserialize_missing(self):
3018
tt = self.get_preview()
3019
tt.deserialize(iter(self.missing_records()))
3020
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3022
def make_modification_preview(self):
3023
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3024
LINES_TWO = 'z\nbb\nx\ndd\n'
3025
tree = self.make_branch_and_tree('tree')
3026
self.build_tree_contents([('tree/file', LINES_ONE)])
3027
tree.add('file', 'file-id')
3028
return self.get_preview(tree), LINES_TWO
3030
def modification_records(self):
3031
attribs = self.default_attribs()
3032
attribs['_id_number'] = 2
3033
attribs['_tree_path_ids'] = {
3036
attribs['_removed_contents'] = ['new-1']
3037
contents = [('new-1', 'file',
3038
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3039
return self.make_records(attribs, contents)
3041
def test_serialize_modification(self):
3042
tt, LINES = self.make_modification_preview()
3043
trans_id = tt.trans_id_file_id('file-id')
3044
tt.delete_contents(trans_id)
3045
tt.create_file(LINES, trans_id)
3046
self.assertSerializesTo(self.modification_records(), tt)
3048
def test_deserialize_modification(self):
3049
tt, LINES = self.make_modification_preview()
3050
tt.deserialize(iter(self.modification_records()))
3051
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3053
def make_kind_change_preview(self):
3054
LINES = 'a\nb\nc\nd\n'
3055
tree = self.make_branch_and_tree('tree')
3056
self.build_tree(['tree/foo/'])
3057
tree.add('foo', 'foo-id')
3058
return self.get_preview(tree), LINES
3060
def kind_change_records(self):
3061
attribs = self.default_attribs()
3062
attribs['_id_number'] = 2
3063
attribs['_tree_path_ids'] = {
3066
attribs['_removed_contents'] = ['new-1']
3067
contents = [('new-1', 'file',
3068
'i 4\na\nb\nc\nd\n\n')]
3069
return self.make_records(attribs, contents)
3071
def test_serialize_kind_change(self):
3072
tt, LINES = self.make_kind_change_preview()
3073
trans_id = tt.trans_id_file_id('foo-id')
3074
tt.delete_contents(trans_id)
3075
tt.create_file(LINES, trans_id)
3076
self.assertSerializesTo(self.kind_change_records(), tt)
3078
def test_deserialize_kind_change(self):
3079
tt, LINES = self.make_kind_change_preview()
3080
tt.deserialize(iter(self.kind_change_records()))
3081
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3083
def make_add_contents_preview(self):
3084
LINES = 'a\nb\nc\nd\n'
3085
tree = self.make_branch_and_tree('tree')
3086
self.build_tree(['tree/foo'])
3088
os.unlink('tree/foo')
3089
return self.get_preview(tree), LINES
3091
def add_contents_records(self):
3092
attribs = self.default_attribs()
3093
attribs['_id_number'] = 2
3094
attribs['_tree_path_ids'] = {
3097
contents = [('new-1', 'file',
3098
'i 4\na\nb\nc\nd\n\n')]
3099
return self.make_records(attribs, contents)
3101
def test_serialize_add_contents(self):
3102
tt, LINES = self.make_add_contents_preview()
3103
trans_id = tt.trans_id_tree_path('foo')
3104
tt.create_file(LINES, trans_id)
3105
self.assertSerializesTo(self.add_contents_records(), tt)
3107
def test_deserialize_add_contents(self):
3108
tt, LINES = self.make_add_contents_preview()
3109
tt.deserialize(iter(self.add_contents_records()))
3110
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3112
def test_get_parents_lines(self):
3113
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3114
LINES_TWO = 'z\nbb\nx\ndd\n'
3115
tree = self.make_branch_and_tree('tree')
3116
self.build_tree_contents([('tree/file', LINES_ONE)])
3117
tree.add('file', 'file-id')
3118
tt = self.get_preview(tree)
3119
trans_id = tt.trans_id_tree_path('file')
3120
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3121
tt._get_parents_lines(trans_id))
3123
def test_get_parents_texts(self):
3124
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3125
LINES_TWO = 'z\nbb\nx\ndd\n'
3126
tree = self.make_branch_and_tree('tree')
3127
self.build_tree_contents([('tree/file', LINES_ONE)])
3128
tree.add('file', 'file-id')
3129
tt = self.get_preview(tree)
3130
trans_id = tt.trans_id_tree_path('file')
3131
self.assertEqual((LINES_ONE,),
3132
tt._get_parents_texts(trans_id))