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):
1927
# self.addCleanup(filters._reset_registry, filters._reset_registry())
1928
# below, but that looks a bit... hard to read even if it's exactly
1930
original_registry = filters._reset_registry()
1931
def restore_registry():
1932
filters._reset_registry(original_registry)
1933
self.addCleanup(restore_registry)
1934
def rot13(chunks, context=None):
1935
return [''.join(chunks).encode('rot13')]
1936
rot13filter = filters.ContentFilter(rot13, rot13)
1937
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1938
os.mkdir(self.test_home_dir + '/.bazaar')
1939
rules_filename = self.test_home_dir + '/.bazaar/rules'
1940
f = open(rules_filename, 'wb')
1941
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1943
def uninstall_rules():
1944
os.remove(rules_filename)
1946
self.addCleanup(uninstall_rules)
1949
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1950
"""build_tree will not hardlink files that have content filtering rules
1951
applied to them (but will still hardlink other files from the same tree
1954
self.requireFeature(HardlinkFeature)
1955
self.install_rot13_content_filter('file1')
1956
source = self.create_ab_tree()
1957
target = self.make_branch_and_tree('target')
1958
revision_tree = source.basis_tree()
1959
revision_tree.lock_read()
1960
self.addCleanup(revision_tree.unlock)
1961
build_tree(revision_tree, target, source, hardlink=True)
1963
self.addCleanup(target.unlock)
1964
self.assertEqual([], list(target.iter_changes(revision_tree)))
1965
source_stat = os.stat('source/file1')
1966
target_stat = os.stat('target/file1')
1967
self.assertNotEqual(source_stat, target_stat)
1968
source_stat = os.stat('source/file2')
1969
target_stat = os.stat('target/file2')
1970
self.assertEqualStat(source_stat, target_stat)
1972
def test_case_insensitive_build_tree_inventory(self):
1973
if (tests.CaseInsensitiveFilesystemFeature.available()
1974
or tests.CaseInsCasePresFilenameFeature.available()):
1975
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1976
source = self.make_branch_and_tree('source')
1977
self.build_tree(['source/file', 'source/FILE'])
1978
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1979
source.commit('added files')
1980
# Don't try this at home, kids!
1981
# Force the tree to report that it is case insensitive
1982
target = self.make_branch_and_tree('target')
1983
target.case_sensitive = False
1984
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1985
self.assertEqual('file.moved', target.id2path('lower-id'))
1986
self.assertEqual('FILE', target.id2path('upper-id'))
1989
class TestCommitTransform(tests.TestCaseWithTransport):
1991
def get_branch(self):
1992
tree = self.make_branch_and_tree('tree')
1994
self.addCleanup(tree.unlock)
1995
tree.commit('empty commit')
1998
def get_branch_and_transform(self):
1999
branch = self.get_branch()
2000
tt = TransformPreview(branch.basis_tree())
2001
self.addCleanup(tt.finalize)
2004
def test_commit_wrong_basis(self):
2005
branch = self.get_branch()
2006
basis = branch.repository.revision_tree(
2007
_mod_revision.NULL_REVISION)
2008
tt = TransformPreview(basis)
2009
self.addCleanup(tt.finalize)
2010
e = self.assertRaises(ValueError, tt.commit, branch, '')
2011
self.assertEqual('TreeTransform not based on branch basis: null:',
2014
def test_empy_commit(self):
2015
branch, tt = self.get_branch_and_transform()
2016
rev = tt.commit(branch, 'my message')
2017
self.assertEqual(2, branch.revno())
2018
repo = branch.repository
2019
self.assertEqual('my message', repo.get_revision(rev).message)
2021
def test_merge_parents(self):
2022
branch, tt = self.get_branch_and_transform()
2023
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2024
self.assertEqual(['rev1b', 'rev1c'],
2025
branch.basis_tree().get_parent_ids()[1:])
2027
def test_first_commit(self):
2028
branch = self.make_branch('branch')
2030
self.addCleanup(branch.unlock)
2031
tt = TransformPreview(branch.basis_tree())
2032
self.addCleanup(tt.finalize)
2033
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2034
rev = tt.commit(branch, 'my message')
2035
self.assertEqual([], branch.basis_tree().get_parent_ids())
2036
self.assertNotEqual(_mod_revision.NULL_REVISION,
2037
branch.last_revision())
2039
def test_first_commit_with_merge_parents(self):
2040
branch = self.make_branch('branch')
2042
self.addCleanup(branch.unlock)
2043
tt = TransformPreview(branch.basis_tree())
2044
self.addCleanup(tt.finalize)
2045
e = self.assertRaises(ValueError, tt.commit, branch,
2046
'my message', ['rev1b-id'])
2047
self.assertEqual('Cannot supply merge parents for first commit.',
2049
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2051
def test_add_files(self):
2052
branch, tt = self.get_branch_and_transform()
2053
tt.new_file('file', tt.root, 'contents', 'file-id')
2054
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2055
if SymlinkFeature.available():
2056
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2057
rev = tt.commit(branch, 'message')
2058
tree = branch.basis_tree()
2059
self.assertEqual('file', tree.id2path('file-id'))
2060
self.assertEqual('contents', tree.get_file_text('file-id'))
2061
self.assertEqual('dir', tree.id2path('dir-id'))
2062
if SymlinkFeature.available():
2063
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2064
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2066
def test_add_unversioned(self):
2067
branch, tt = self.get_branch_and_transform()
2068
tt.new_file('file', tt.root, 'contents')
2069
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2070
'message', strict=True)
2072
def test_modify_strict(self):
2073
branch, tt = self.get_branch_and_transform()
2074
tt.new_file('file', tt.root, 'contents', 'file-id')
2075
tt.commit(branch, 'message', strict=True)
2076
tt = TransformPreview(branch.basis_tree())
2077
self.addCleanup(tt.finalize)
2078
trans_id = tt.trans_id_file_id('file-id')
2079
tt.delete_contents(trans_id)
2080
tt.create_file('contents', trans_id)
2081
tt.commit(branch, 'message', strict=True)
2083
def test_commit_malformed(self):
2084
"""Committing a malformed transform should raise an exception.
2086
In this case, we are adding a file without adding its parent.
2088
branch, tt = self.get_branch_and_transform()
2089
parent_id = tt.trans_id_file_id('parent-id')
2090
tt.new_file('file', parent_id, 'contents', 'file-id')
2091
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2095
class MockTransform(object):
2097
def has_named_child(self, by_parent, parent_id, name):
2098
for child_id in by_parent[parent_id]:
2102
elif name == "name.~%s~" % child_id:
2107
class MockEntry(object):
2109
object.__init__(self)
2113
class TestGetBackupName(TestCase):
2114
def test_get_backup_name(self):
2115
tt = MockTransform()
2116
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2117
self.assertEqual(name, 'name.~1~')
2118
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2119
self.assertEqual(name, 'name.~2~')
2120
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2121
self.assertEqual(name, 'name.~1~')
2122
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2123
self.assertEqual(name, 'name.~1~')
2124
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2125
self.assertEqual(name, 'name.~4~')
2128
class TestFileMover(tests.TestCaseWithTransport):
2130
def test_file_mover(self):
2131
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2132
mover = _FileMover()
2133
mover.rename('a', 'q')
2134
self.failUnlessExists('q')
2135
self.failIfExists('a')
2136
self.failUnlessExists('q/b')
2137
self.failUnlessExists('c')
2138
self.failUnlessExists('c/d')
2140
def test_pre_delete_rollback(self):
2141
self.build_tree(['a/'])
2142
mover = _FileMover()
2143
mover.pre_delete('a', 'q')
2144
self.failUnlessExists('q')
2145
self.failIfExists('a')
2147
self.failIfExists('q')
2148
self.failUnlessExists('a')
2150
def test_apply_deletions(self):
2151
self.build_tree(['a/', 'b/'])
2152
mover = _FileMover()
2153
mover.pre_delete('a', 'q')
2154
mover.pre_delete('b', 'r')
2155
self.failUnlessExists('q')
2156
self.failUnlessExists('r')
2157
self.failIfExists('a')
2158
self.failIfExists('b')
2159
mover.apply_deletions()
2160
self.failIfExists('q')
2161
self.failIfExists('r')
2162
self.failIfExists('a')
2163
self.failIfExists('b')
2165
def test_file_mover_rollback(self):
2166
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2167
mover = _FileMover()
2168
mover.rename('c/d', 'c/f')
2169
mover.rename('c/e', 'c/d')
2171
mover.rename('a', 'c')
2172
except errors.FileExists, e:
2174
self.failUnlessExists('a')
2175
self.failUnlessExists('c/d')
2178
class Bogus(Exception):
2182
class TestTransformRollback(tests.TestCaseWithTransport):
2184
class ExceptionFileMover(_FileMover):
2186
def __init__(self, bad_source=None, bad_target=None):
2187
_FileMover.__init__(self)
2188
self.bad_source = bad_source
2189
self.bad_target = bad_target
2191
def rename(self, source, target):
2192
if (self.bad_source is not None and
2193
source.endswith(self.bad_source)):
2195
elif (self.bad_target is not None and
2196
target.endswith(self.bad_target)):
2199
_FileMover.rename(self, source, target)
2201
def test_rollback_rename(self):
2202
tree = self.make_branch_and_tree('.')
2203
self.build_tree(['a/', 'a/b'])
2204
tt = TreeTransform(tree)
2205
self.addCleanup(tt.finalize)
2206
a_id = tt.trans_id_tree_path('a')
2207
tt.adjust_path('c', tt.root, a_id)
2208
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2209
self.assertRaises(Bogus, tt.apply,
2210
_mover=self.ExceptionFileMover(bad_source='a'))
2211
self.failUnlessExists('a')
2212
self.failUnlessExists('a/b')
2214
self.failUnlessExists('c')
2215
self.failUnlessExists('c/d')
2217
def test_rollback_rename_into_place(self):
2218
tree = self.make_branch_and_tree('.')
2219
self.build_tree(['a/', 'a/b'])
2220
tt = TreeTransform(tree)
2221
self.addCleanup(tt.finalize)
2222
a_id = tt.trans_id_tree_path('a')
2223
tt.adjust_path('c', tt.root, a_id)
2224
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2225
self.assertRaises(Bogus, tt.apply,
2226
_mover=self.ExceptionFileMover(bad_target='c/d'))
2227
self.failUnlessExists('a')
2228
self.failUnlessExists('a/b')
2230
self.failUnlessExists('c')
2231
self.failUnlessExists('c/d')
2233
def test_rollback_deletion(self):
2234
tree = self.make_branch_and_tree('.')
2235
self.build_tree(['a/', 'a/b'])
2236
tt = TreeTransform(tree)
2237
self.addCleanup(tt.finalize)
2238
a_id = tt.trans_id_tree_path('a')
2239
tt.delete_contents(a_id)
2240
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2241
self.assertRaises(Bogus, tt.apply,
2242
_mover=self.ExceptionFileMover(bad_target='d'))
2243
self.failUnlessExists('a')
2244
self.failUnlessExists('a/b')
2246
def test_resolve_no_parent(self):
2247
wt = self.make_branch_and_tree('.')
2248
tt = TreeTransform(wt)
2249
self.addCleanup(tt.finalize)
2250
parent = tt.trans_id_file_id('parent-id')
2251
tt.new_file('file', parent, 'Contents')
2252
resolve_conflicts(tt)
2255
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2256
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2258
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2259
('', ''), ('directory', 'directory'), (False, None))
2262
class TestTransformPreview(tests.TestCaseWithTransport):
2264
def create_tree(self):
2265
tree = self.make_branch_and_tree('.')
2266
self.build_tree_contents([('a', 'content 1')])
2267
tree.set_root_id('TREE_ROOT')
2268
tree.add('a', 'a-id')
2269
tree.commit('rev1', rev_id='rev1')
2270
return tree.branch.repository.revision_tree('rev1')
2272
def get_empty_preview(self):
2273
repository = self.make_repository('repo')
2274
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2275
preview = TransformPreview(tree)
2276
self.addCleanup(preview.finalize)
2279
def test_transform_preview(self):
2280
revision_tree = self.create_tree()
2281
preview = TransformPreview(revision_tree)
2282
self.addCleanup(preview.finalize)
2284
def test_transform_preview_tree(self):
2285
revision_tree = self.create_tree()
2286
preview = TransformPreview(revision_tree)
2287
self.addCleanup(preview.finalize)
2288
preview.get_preview_tree()
2290
def test_transform_new_file(self):
2291
revision_tree = self.create_tree()
2292
preview = TransformPreview(revision_tree)
2293
self.addCleanup(preview.finalize)
2294
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2295
preview_tree = preview.get_preview_tree()
2296
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2298
preview_tree.get_file('file2-id').read(), 'content B\n')
2300
def test_diff_preview_tree(self):
2301
revision_tree = self.create_tree()
2302
preview = TransformPreview(revision_tree)
2303
self.addCleanup(preview.finalize)
2304
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2305
preview_tree = preview.get_preview_tree()
2307
show_diff_trees(revision_tree, preview_tree, out)
2308
lines = out.getvalue().splitlines()
2309
self.assertEqual(lines[0], "=== added file 'file2'")
2310
# 3 lines of diff administrivia
2311
self.assertEqual(lines[4], "+content B")
2313
def test_transform_conflicts(self):
2314
revision_tree = self.create_tree()
2315
preview = TransformPreview(revision_tree)
2316
self.addCleanup(preview.finalize)
2317
preview.new_file('a', preview.root, 'content 2')
2318
resolve_conflicts(preview)
2319
trans_id = preview.trans_id_file_id('a-id')
2320
self.assertEqual('a.moved', preview.final_name(trans_id))
2322
def get_tree_and_preview_tree(self):
2323
revision_tree = self.create_tree()
2324
preview = TransformPreview(revision_tree)
2325
self.addCleanup(preview.finalize)
2326
a_trans_id = preview.trans_id_file_id('a-id')
2327
preview.delete_contents(a_trans_id)
2328
preview.create_file('b content', a_trans_id)
2329
preview_tree = preview.get_preview_tree()
2330
return revision_tree, preview_tree
2332
def test_iter_changes(self):
2333
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2334
root = revision_tree.inventory.root.file_id
2335
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2336
(root, root), ('a', 'a'), ('file', 'file'),
2338
list(preview_tree.iter_changes(revision_tree)))
2340
def test_include_unchanged_succeeds(self):
2341
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2342
changes = preview_tree.iter_changes(revision_tree,
2343
include_unchanged=True)
2344
root = revision_tree.inventory.root.file_id
2346
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2348
def test_specific_files(self):
2349
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2350
changes = preview_tree.iter_changes(revision_tree,
2351
specific_files=[''])
2352
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2354
def test_want_unversioned(self):
2355
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2356
changes = preview_tree.iter_changes(revision_tree,
2357
want_unversioned=True)
2358
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2360
def test_ignore_extra_trees_no_specific_files(self):
2361
# extra_trees is harmless without specific_files, so we'll silently
2362
# accept it, even though we won't use it.
2363
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2364
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2366
def test_ignore_require_versioned_no_specific_files(self):
2367
# require_versioned is meaningless without specific_files.
2368
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2369
preview_tree.iter_changes(revision_tree, require_versioned=False)
2371
def test_ignore_pb(self):
2372
# pb could be supported, but TT.iter_changes doesn't support it.
2373
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2374
preview_tree.iter_changes(revision_tree)
2376
def test_kind(self):
2377
revision_tree = self.create_tree()
2378
preview = TransformPreview(revision_tree)
2379
self.addCleanup(preview.finalize)
2380
preview.new_file('file', preview.root, 'contents', 'file-id')
2381
preview.new_directory('directory', preview.root, 'dir-id')
2382
preview_tree = preview.get_preview_tree()
2383
self.assertEqual('file', preview_tree.kind('file-id'))
2384
self.assertEqual('directory', preview_tree.kind('dir-id'))
2386
def test_get_file_mtime(self):
2387
preview = self.get_empty_preview()
2388
file_trans_id = preview.new_file('file', preview.root, 'contents',
2390
limbo_path = preview._limbo_name(file_trans_id)
2391
preview_tree = preview.get_preview_tree()
2392
self.assertEqual(os.stat(limbo_path).st_mtime,
2393
preview_tree.get_file_mtime('file-id'))
2395
def test_get_file_mtime_renamed(self):
2396
work_tree = self.make_branch_and_tree('tree')
2397
self.build_tree(['tree/file'])
2398
work_tree.add('file', 'file-id')
2399
preview = TransformPreview(work_tree)
2400
self.addCleanup(preview.finalize)
2401
file_trans_id = preview.trans_id_tree_file_id('file-id')
2402
preview.adjust_path('renamed', preview.root, file_trans_id)
2403
preview_tree = preview.get_preview_tree()
2404
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2405
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2407
def test_get_file(self):
2408
preview = self.get_empty_preview()
2409
preview.new_file('file', preview.root, 'contents', 'file-id')
2410
preview_tree = preview.get_preview_tree()
2411
tree_file = preview_tree.get_file('file-id')
2413
self.assertEqual('contents', tree_file.read())
2417
def test_get_symlink_target(self):
2418
self.requireFeature(SymlinkFeature)
2419
preview = self.get_empty_preview()
2420
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2421
preview_tree = preview.get_preview_tree()
2422
self.assertEqual('target',
2423
preview_tree.get_symlink_target('symlink-id'))
2425
def test_all_file_ids(self):
2426
tree = self.make_branch_and_tree('tree')
2427
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2428
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2429
preview = TransformPreview(tree)
2430
self.addCleanup(preview.finalize)
2431
preview.unversion_file(preview.trans_id_file_id('b-id'))
2432
c_trans_id = preview.trans_id_file_id('c-id')
2433
preview.unversion_file(c_trans_id)
2434
preview.version_file('c-id', c_trans_id)
2435
preview_tree = preview.get_preview_tree()
2436
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2437
preview_tree.all_file_ids())
2439
def test_path2id_deleted_unchanged(self):
2440
tree = self.make_branch_and_tree('tree')
2441
self.build_tree(['tree/unchanged', 'tree/deleted'])
2442
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2443
preview = TransformPreview(tree)
2444
self.addCleanup(preview.finalize)
2445
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2446
preview_tree = preview.get_preview_tree()
2447
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2448
self.assertIs(None, preview_tree.path2id('deleted'))
2450
def test_path2id_created(self):
2451
tree = self.make_branch_and_tree('tree')
2452
self.build_tree(['tree/unchanged'])
2453
tree.add(['unchanged'], ['unchanged-id'])
2454
preview = TransformPreview(tree)
2455
self.addCleanup(preview.finalize)
2456
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2457
'contents', 'new-id')
2458
preview_tree = preview.get_preview_tree()
2459
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2461
def test_path2id_moved(self):
2462
tree = self.make_branch_and_tree('tree')
2463
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2464
tree.add(['old_parent', 'old_parent/child'],
2465
['old_parent-id', 'child-id'])
2466
preview = TransformPreview(tree)
2467
self.addCleanup(preview.finalize)
2468
new_parent = preview.new_directory('new_parent', preview.root,
2470
preview.adjust_path('child', new_parent,
2471
preview.trans_id_file_id('child-id'))
2472
preview_tree = preview.get_preview_tree()
2473
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2474
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2476
def test_path2id_renamed_parent(self):
2477
tree = self.make_branch_and_tree('tree')
2478
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2479
tree.add(['old_name', 'old_name/child'],
2480
['parent-id', 'child-id'])
2481
preview = TransformPreview(tree)
2482
self.addCleanup(preview.finalize)
2483
preview.adjust_path('new_name', preview.root,
2484
preview.trans_id_file_id('parent-id'))
2485
preview_tree = preview.get_preview_tree()
2486
self.assertIs(None, preview_tree.path2id('old_name/child'))
2487
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2489
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2490
preview_tree = tt.get_preview_tree()
2491
preview_result = list(preview_tree.iter_entries_by_dir(
2495
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2496
self.assertEqual(actual_result, preview_result)
2498
def test_iter_entries_by_dir_new(self):
2499
tree = self.make_branch_and_tree('tree')
2500
tt = TreeTransform(tree)
2501
tt.new_file('new', tt.root, 'contents', 'new-id')
2502
self.assertMatchingIterEntries(tt)
2504
def test_iter_entries_by_dir_deleted(self):
2505
tree = self.make_branch_and_tree('tree')
2506
self.build_tree(['tree/deleted'])
2507
tree.add('deleted', 'deleted-id')
2508
tt = TreeTransform(tree)
2509
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2510
self.assertMatchingIterEntries(tt)
2512
def test_iter_entries_by_dir_unversioned(self):
2513
tree = self.make_branch_and_tree('tree')
2514
self.build_tree(['tree/removed'])
2515
tree.add('removed', 'removed-id')
2516
tt = TreeTransform(tree)
2517
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2518
self.assertMatchingIterEntries(tt)
2520
def test_iter_entries_by_dir_moved(self):
2521
tree = self.make_branch_and_tree('tree')
2522
self.build_tree(['tree/moved', 'tree/new_parent/'])
2523
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2524
tt = TreeTransform(tree)
2525
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2526
tt.trans_id_file_id('moved-id'))
2527
self.assertMatchingIterEntries(tt)
2529
def test_iter_entries_by_dir_specific_file_ids(self):
2530
tree = self.make_branch_and_tree('tree')
2531
tree.set_root_id('tree-root-id')
2532
self.build_tree(['tree/parent/', 'tree/parent/child'])
2533
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2534
tt = TreeTransform(tree)
2535
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2537
def test_symlink_content_summary(self):
2538
self.requireFeature(SymlinkFeature)
2539
preview = self.get_empty_preview()
2540
preview.new_symlink('path', preview.root, 'target', 'path-id')
2541
summary = preview.get_preview_tree().path_content_summary('path')
2542
self.assertEqual(('symlink', None, None, 'target'), summary)
2544
def test_missing_content_summary(self):
2545
preview = self.get_empty_preview()
2546
summary = preview.get_preview_tree().path_content_summary('path')
2547
self.assertEqual(('missing', None, None, None), summary)
2549
def test_deleted_content_summary(self):
2550
tree = self.make_branch_and_tree('tree')
2551
self.build_tree(['tree/path/'])
2553
preview = TransformPreview(tree)
2554
self.addCleanup(preview.finalize)
2555
preview.delete_contents(preview.trans_id_tree_path('path'))
2556
summary = preview.get_preview_tree().path_content_summary('path')
2557
self.assertEqual(('missing', None, None, None), summary)
2559
def test_file_content_summary_executable(self):
2560
preview = self.get_empty_preview()
2561
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2562
preview.set_executability(True, path_id)
2563
summary = preview.get_preview_tree().path_content_summary('path')
2564
self.assertEqual(4, len(summary))
2565
self.assertEqual('file', summary[0])
2566
# size must be known
2567
self.assertEqual(len('contents'), summary[1])
2569
self.assertEqual(True, summary[2])
2570
# will not have hash (not cheap to determine)
2571
self.assertIs(None, summary[3])
2573
def test_change_executability(self):
2574
tree = self.make_branch_and_tree('tree')
2575
self.build_tree(['tree/path'])
2577
preview = TransformPreview(tree)
2578
self.addCleanup(preview.finalize)
2579
path_id = preview.trans_id_tree_path('path')
2580
preview.set_executability(True, path_id)
2581
summary = preview.get_preview_tree().path_content_summary('path')
2582
self.assertEqual(True, summary[2])
2584
def test_file_content_summary_non_exec(self):
2585
preview = self.get_empty_preview()
2586
preview.new_file('path', preview.root, 'contents', 'path-id')
2587
summary = preview.get_preview_tree().path_content_summary('path')
2588
self.assertEqual(4, len(summary))
2589
self.assertEqual('file', summary[0])
2590
# size must be known
2591
self.assertEqual(len('contents'), summary[1])
2593
self.assertEqual(False, summary[2])
2594
# will not have hash (not cheap to determine)
2595
self.assertIs(None, summary[3])
2597
def test_dir_content_summary(self):
2598
preview = self.get_empty_preview()
2599
preview.new_directory('path', preview.root, 'path-id')
2600
summary = preview.get_preview_tree().path_content_summary('path')
2601
self.assertEqual(('directory', None, None, None), summary)
2603
def test_tree_content_summary(self):
2604
preview = self.get_empty_preview()
2605
path = preview.new_directory('path', preview.root, 'path-id')
2606
preview.set_tree_reference('rev-1', path)
2607
summary = preview.get_preview_tree().path_content_summary('path')
2608
self.assertEqual(4, len(summary))
2609
self.assertEqual('tree-reference', summary[0])
2611
def test_annotate(self):
2612
tree = self.make_branch_and_tree('tree')
2613
self.build_tree_contents([('tree/file', 'a\n')])
2614
tree.add('file', 'file-id')
2615
tree.commit('a', rev_id='one')
2616
self.build_tree_contents([('tree/file', 'a\nb\n')])
2617
preview = TransformPreview(tree)
2618
self.addCleanup(preview.finalize)
2619
file_trans_id = preview.trans_id_file_id('file-id')
2620
preview.delete_contents(file_trans_id)
2621
preview.create_file('a\nb\nc\n', file_trans_id)
2622
preview_tree = preview.get_preview_tree()
2628
annotation = preview_tree.annotate_iter('file-id', 'me:')
2629
self.assertEqual(expected, annotation)
2631
def test_annotate_missing(self):
2632
preview = self.get_empty_preview()
2633
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2634
preview_tree = preview.get_preview_tree()
2640
annotation = preview_tree.annotate_iter('file-id', 'me:')
2641
self.assertEqual(expected, annotation)
2643
def test_annotate_rename(self):
2644
tree = self.make_branch_and_tree('tree')
2645
self.build_tree_contents([('tree/file', 'a\n')])
2646
tree.add('file', 'file-id')
2647
tree.commit('a', rev_id='one')
2648
preview = TransformPreview(tree)
2649
self.addCleanup(preview.finalize)
2650
file_trans_id = preview.trans_id_file_id('file-id')
2651
preview.adjust_path('newname', preview.root, file_trans_id)
2652
preview_tree = preview.get_preview_tree()
2656
annotation = preview_tree.annotate_iter('file-id', 'me:')
2657
self.assertEqual(expected, annotation)
2659
def test_annotate_deleted(self):
2660
tree = self.make_branch_and_tree('tree')
2661
self.build_tree_contents([('tree/file', 'a\n')])
2662
tree.add('file', 'file-id')
2663
tree.commit('a', rev_id='one')
2664
self.build_tree_contents([('tree/file', 'a\nb\n')])
2665
preview = TransformPreview(tree)
2666
self.addCleanup(preview.finalize)
2667
file_trans_id = preview.trans_id_file_id('file-id')
2668
preview.delete_contents(file_trans_id)
2669
preview_tree = preview.get_preview_tree()
2670
annotation = preview_tree.annotate_iter('file-id', 'me:')
2671
self.assertIs(None, annotation)
2673
def test_stored_kind(self):
2674
preview = self.get_empty_preview()
2675
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2676
preview_tree = preview.get_preview_tree()
2677
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2679
def test_is_executable(self):
2680
preview = self.get_empty_preview()
2681
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2682
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2683
preview_tree = preview.get_preview_tree()
2684
self.assertEqual(True, preview_tree.is_executable('file-id'))
2686
def test_get_set_parent_ids(self):
2687
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2688
self.assertEqual([], preview_tree.get_parent_ids())
2689
preview_tree.set_parent_ids(['rev-1'])
2690
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2692
def test_plan_file_merge(self):
2693
work_a = self.make_branch_and_tree('wta')
2694
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2695
work_a.add('file', 'file-id')
2696
base_id = work_a.commit('base version')
2697
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2698
preview = TransformPreview(work_a)
2699
self.addCleanup(preview.finalize)
2700
trans_id = preview.trans_id_file_id('file-id')
2701
preview.delete_contents(trans_id)
2702
preview.create_file('b\nc\nd\ne\n', trans_id)
2703
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2704
tree_a = preview.get_preview_tree()
2705
tree_a.set_parent_ids([base_id])
2707
('killed-a', 'a\n'),
2708
('killed-b', 'b\n'),
2709
('unchanged', 'c\n'),
2710
('unchanged', 'd\n'),
2713
], list(tree_a.plan_file_merge('file-id', tree_b)))
2715
def test_plan_file_merge_revision_tree(self):
2716
work_a = self.make_branch_and_tree('wta')
2717
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2718
work_a.add('file', 'file-id')
2719
base_id = work_a.commit('base version')
2720
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2721
preview = TransformPreview(work_a.basis_tree())
2722
self.addCleanup(preview.finalize)
2723
trans_id = preview.trans_id_file_id('file-id')
2724
preview.delete_contents(trans_id)
2725
preview.create_file('b\nc\nd\ne\n', trans_id)
2726
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2727
tree_a = preview.get_preview_tree()
2728
tree_a.set_parent_ids([base_id])
2730
('killed-a', 'a\n'),
2731
('killed-b', 'b\n'),
2732
('unchanged', 'c\n'),
2733
('unchanged', 'd\n'),
2736
], list(tree_a.plan_file_merge('file-id', tree_b)))
2738
def test_walkdirs(self):
2739
preview = self.get_empty_preview()
2740
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2741
# FIXME: new_directory should mark root.
2742
preview.fixup_new_roots()
2743
preview_tree = preview.get_preview_tree()
2744
file_trans_id = preview.new_file('a', preview.root, 'contents',
2746
expected = [(('', 'tree-root'),
2747
[('a', 'a', 'file', None, 'a-id', 'file')])]
2748
self.assertEqual(expected, list(preview_tree.walkdirs()))
2750
def test_extras(self):
2751
work_tree = self.make_branch_and_tree('tree')
2752
self.build_tree(['tree/removed-file', 'tree/existing-file',
2753
'tree/not-removed-file'])
2754
work_tree.add(['removed-file', 'not-removed-file'])
2755
preview = TransformPreview(work_tree)
2756
self.addCleanup(preview.finalize)
2757
preview.new_file('new-file', preview.root, 'contents')
2758
preview.new_file('new-versioned-file', preview.root, 'contents',
2760
tree = preview.get_preview_tree()
2761
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2762
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2765
def test_merge_into_preview(self):
2766
work_tree = self.make_branch_and_tree('tree')
2767
self.build_tree_contents([('tree/file','b\n')])
2768
work_tree.add('file', 'file-id')
2769
work_tree.commit('first commit')
2770
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2771
self.build_tree_contents([('child/file','b\nc\n')])
2772
child_tree.commit('child commit')
2773
child_tree.lock_write()
2774
self.addCleanup(child_tree.unlock)
2775
work_tree.lock_write()
2776
self.addCleanup(work_tree.unlock)
2777
preview = TransformPreview(work_tree)
2778
self.addCleanup(preview.finalize)
2779
file_trans_id = preview.trans_id_file_id('file-id')
2780
preview.delete_contents(file_trans_id)
2781
preview.create_file('a\nb\n', file_trans_id)
2782
preview_tree = preview.get_preview_tree()
2783
merger = Merger.from_revision_ids(None, preview_tree,
2784
child_tree.branch.last_revision(),
2785
other_branch=child_tree.branch,
2786
tree_branch=work_tree.branch)
2787
merger.merge_type = Merge3Merger
2788
tt = merger.make_merger().make_preview_transform()
2789
self.addCleanup(tt.finalize)
2790
final_tree = tt.get_preview_tree()
2791
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2793
def test_merge_preview_into_workingtree(self):
2794
tree = self.make_branch_and_tree('tree')
2795
tree.set_root_id('TREE_ROOT')
2796
tt = TransformPreview(tree)
2797
self.addCleanup(tt.finalize)
2798
tt.new_file('name', tt.root, 'content', 'file-id')
2799
tree2 = self.make_branch_and_tree('tree2')
2800
tree2.set_root_id('TREE_ROOT')
2801
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2802
None, tree.basis_tree())
2803
merger.merge_type = Merge3Merger
2806
def test_merge_preview_into_workingtree_handles_conflicts(self):
2807
tree = self.make_branch_and_tree('tree')
2808
self.build_tree_contents([('tree/foo', 'bar')])
2809
tree.add('foo', 'foo-id')
2811
tt = TransformPreview(tree)
2812
self.addCleanup(tt.finalize)
2813
trans_id = tt.trans_id_file_id('foo-id')
2814
tt.delete_contents(trans_id)
2815
tt.create_file('baz', trans_id)
2816
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2817
self.build_tree_contents([('tree2/foo', 'qux')])
2819
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2820
pb, tree.basis_tree())
2821
merger.merge_type = Merge3Merger
2824
def test_is_executable(self):
2825
tree = self.make_branch_and_tree('tree')
2826
preview = TransformPreview(tree)
2827
self.addCleanup(preview.finalize)
2828
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2829
preview_tree = preview.get_preview_tree()
2830
self.assertEqual(False, preview_tree.is_executable('baz-id',
2832
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2834
def test_commit_preview_tree(self):
2835
tree = self.make_branch_and_tree('tree')
2836
rev_id = tree.commit('rev1')
2837
tree.branch.lock_write()
2838
self.addCleanup(tree.branch.unlock)
2839
tt = TransformPreview(tree)
2840
tt.new_file('file', tt.root, 'contents', 'file_id')
2841
self.addCleanup(tt.finalize)
2842
preview = tt.get_preview_tree()
2843
preview.set_parent_ids([rev_id])
2844
builder = tree.branch.get_commit_builder([rev_id])
2845
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2846
builder.finish_inventory()
2847
rev2_id = builder.commit('rev2')
2848
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2849
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2851
def test_ascii_limbo_paths(self):
2852
self.requireFeature(tests.UnicodeFilenameFeature)
2853
branch = self.make_branch('any')
2854
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2855
tt = TransformPreview(tree)
2856
self.addCleanup(tt.finalize)
2857
foo_id = tt.new_directory('', ROOT_PARENT)
2858
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2859
limbo_path = tt._limbo_name(bar_id)
2860
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2863
class FakeSerializer(object):
2864
"""Serializer implementation that simply returns the input.
2866
The input is returned in the order used by pack.ContainerPushParser.
2869
def bytes_record(bytes, names):
2873
class TestSerializeTransform(tests.TestCaseWithTransport):
2875
_test_needs_features = [tests.UnicodeFilenameFeature]
2877
def get_preview(self, tree=None):
2879
tree = self.make_branch_and_tree('tree')
2880
tt = TransformPreview(tree)
2881
self.addCleanup(tt.finalize)
2884
def assertSerializesTo(self, expected, tt):
2885
records = list(tt.serialize(FakeSerializer()))
2886
self.assertEqual(expected, records)
2889
def default_attribs():
2894
'_new_executability': {},
2896
'_tree_path_ids': {'': 'new-0'},
2898
'_removed_contents': [],
2899
'_non_present_ids': {},
2902
def make_records(self, attribs, contents):
2904
(((('attribs'),),), bencode.bencode(attribs))]
2905
records.extend([(((n, k),), c) for n, k, c in contents])
2908
def creation_records(self):
2909
attribs = self.default_attribs()
2910
attribs['_id_number'] = 3
2911
attribs['_new_name'] = {
2912
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2913
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2914
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2915
attribs['_new_executability'] = {'new-1': 1}
2917
('new-1', 'file', 'i 1\nbar\n'),
2918
('new-2', 'directory', ''),
2920
return self.make_records(attribs, contents)
2922
def test_serialize_creation(self):
2923
tt = self.get_preview()
2924
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2925
tt.new_directory('qux', tt.root, 'quxx')
2926
self.assertSerializesTo(self.creation_records(), tt)
2928
def test_deserialize_creation(self):
2929
tt = self.get_preview()
2930
tt.deserialize(iter(self.creation_records()))
2931
self.assertEqual(3, tt._id_number)
2932
self.assertEqual({'new-1': u'foo\u1234',
2933
'new-2': 'qux'}, tt._new_name)
2934
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2935
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2936
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2937
self.assertEqual({'new-1': True}, tt._new_executability)
2938
self.assertEqual({'new-1': 'file',
2939
'new-2': 'directory'}, tt._new_contents)
2940
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2942
foo_content = foo_limbo.read()
2945
self.assertEqual('bar', foo_content)
2947
def symlink_creation_records(self):
2948
attribs = self.default_attribs()
2949
attribs['_id_number'] = 2
2950
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2951
attribs['_new_parent'] = {'new-1': 'new-0'}
2952
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2953
return self.make_records(attribs, contents)
2955
def test_serialize_symlink_creation(self):
2956
self.requireFeature(tests.SymlinkFeature)
2957
tt = self.get_preview()
2958
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2959
self.assertSerializesTo(self.symlink_creation_records(), tt)
2961
def test_deserialize_symlink_creation(self):
2962
self.requireFeature(tests.SymlinkFeature)
2963
tt = self.get_preview()
2964
tt.deserialize(iter(self.symlink_creation_records()))
2965
abspath = tt._limbo_name('new-1')
2966
foo_content = osutils.readlink(abspath)
2967
self.assertEqual(u'bar\u1234', foo_content)
2969
def make_destruction_preview(self):
2970
tree = self.make_branch_and_tree('.')
2971
self.build_tree([u'foo\u1234', 'bar'])
2972
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2973
return self.get_preview(tree)
2975
def destruction_records(self):
2976
attribs = self.default_attribs()
2977
attribs['_id_number'] = 3
2978
attribs['_removed_id'] = ['new-1']
2979
attribs['_removed_contents'] = ['new-2']
2980
attribs['_tree_path_ids'] = {
2982
u'foo\u1234'.encode('utf-8'): 'new-1',
2985
return self.make_records(attribs, [])
2987
def test_serialize_destruction(self):
2988
tt = self.make_destruction_preview()
2989
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2990
tt.unversion_file(foo_trans_id)
2991
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2992
tt.delete_contents(bar_trans_id)
2993
self.assertSerializesTo(self.destruction_records(), tt)
2995
def test_deserialize_destruction(self):
2996
tt = self.make_destruction_preview()
2997
tt.deserialize(iter(self.destruction_records()))
2998
self.assertEqual({u'foo\u1234': 'new-1',
3000
'': tt.root}, tt._tree_path_ids)
3001
self.assertEqual({'new-1': u'foo\u1234',
3003
tt.root: ''}, tt._tree_id_paths)
3004
self.assertEqual(set(['new-1']), tt._removed_id)
3005
self.assertEqual(set(['new-2']), tt._removed_contents)
3007
def missing_records(self):
3008
attribs = self.default_attribs()
3009
attribs['_id_number'] = 2
3010
attribs['_non_present_ids'] = {
3012
return self.make_records(attribs, [])
3014
def test_serialize_missing(self):
3015
tt = self.get_preview()
3016
boo_trans_id = tt.trans_id_file_id('boo')
3017
self.assertSerializesTo(self.missing_records(), tt)
3019
def test_deserialize_missing(self):
3020
tt = self.get_preview()
3021
tt.deserialize(iter(self.missing_records()))
3022
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3024
def make_modification_preview(self):
3025
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3026
LINES_TWO = 'z\nbb\nx\ndd\n'
3027
tree = self.make_branch_and_tree('tree')
3028
self.build_tree_contents([('tree/file', LINES_ONE)])
3029
tree.add('file', 'file-id')
3030
return self.get_preview(tree), LINES_TWO
3032
def modification_records(self):
3033
attribs = self.default_attribs()
3034
attribs['_id_number'] = 2
3035
attribs['_tree_path_ids'] = {
3038
attribs['_removed_contents'] = ['new-1']
3039
contents = [('new-1', 'file',
3040
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3041
return self.make_records(attribs, contents)
3043
def test_serialize_modification(self):
3044
tt, LINES = self.make_modification_preview()
3045
trans_id = tt.trans_id_file_id('file-id')
3046
tt.delete_contents(trans_id)
3047
tt.create_file(LINES, trans_id)
3048
self.assertSerializesTo(self.modification_records(), tt)
3050
def test_deserialize_modification(self):
3051
tt, LINES = self.make_modification_preview()
3052
tt.deserialize(iter(self.modification_records()))
3053
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3055
def make_kind_change_preview(self):
3056
LINES = 'a\nb\nc\nd\n'
3057
tree = self.make_branch_and_tree('tree')
3058
self.build_tree(['tree/foo/'])
3059
tree.add('foo', 'foo-id')
3060
return self.get_preview(tree), LINES
3062
def kind_change_records(self):
3063
attribs = self.default_attribs()
3064
attribs['_id_number'] = 2
3065
attribs['_tree_path_ids'] = {
3068
attribs['_removed_contents'] = ['new-1']
3069
contents = [('new-1', 'file',
3070
'i 4\na\nb\nc\nd\n\n')]
3071
return self.make_records(attribs, contents)
3073
def test_serialize_kind_change(self):
3074
tt, LINES = self.make_kind_change_preview()
3075
trans_id = tt.trans_id_file_id('foo-id')
3076
tt.delete_contents(trans_id)
3077
tt.create_file(LINES, trans_id)
3078
self.assertSerializesTo(self.kind_change_records(), tt)
3080
def test_deserialize_kind_change(self):
3081
tt, LINES = self.make_kind_change_preview()
3082
tt.deserialize(iter(self.kind_change_records()))
3083
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3085
def make_add_contents_preview(self):
3086
LINES = 'a\nb\nc\nd\n'
3087
tree = self.make_branch_and_tree('tree')
3088
self.build_tree(['tree/foo'])
3090
os.unlink('tree/foo')
3091
return self.get_preview(tree), LINES
3093
def add_contents_records(self):
3094
attribs = self.default_attribs()
3095
attribs['_id_number'] = 2
3096
attribs['_tree_path_ids'] = {
3099
contents = [('new-1', 'file',
3100
'i 4\na\nb\nc\nd\n\n')]
3101
return self.make_records(attribs, contents)
3103
def test_serialize_add_contents(self):
3104
tt, LINES = self.make_add_contents_preview()
3105
trans_id = tt.trans_id_tree_path('foo')
3106
tt.create_file(LINES, trans_id)
3107
self.assertSerializesTo(self.add_contents_records(), tt)
3109
def test_deserialize_add_contents(self):
3110
tt, LINES = self.make_add_contents_preview()
3111
tt.deserialize(iter(self.add_contents_records()))
3112
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3114
def test_get_parents_lines(self):
3115
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3116
LINES_TWO = 'z\nbb\nx\ndd\n'
3117
tree = self.make_branch_and_tree('tree')
3118
self.build_tree_contents([('tree/file', LINES_ONE)])
3119
tree.add('file', 'file-id')
3120
tt = self.get_preview(tree)
3121
trans_id = tt.trans_id_tree_path('file')
3122
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3123
tt._get_parents_lines(trans_id))
3125
def test_get_parents_texts(self):
3126
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3127
LINES_TWO = 'z\nbb\nx\ndd\n'
3128
tree = self.make_branch_and_tree('tree')
3129
self.build_tree_contents([('tree/file', LINES_ONE)])
3130
tree.add('file', 'file-id')
3131
tt = self.get_preview(tree)
3132
trans_id = tt.trans_id_tree_path('file')
3133
self.assertEqual((LINES_ONE,),
3134
tt._get_parents_texts(trans_id))