1
# Copyright (C) 2006, 2007, 2008 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
19
from StringIO import StringIO
28
revision as _mod_revision,
33
from bzrlib.bzrdir import BzrDir
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
35
UnversionedParent, ParentLoop, DeletingParent,
37
from bzrlib.diff import show_diff_trees
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
40
PathsNotVersionedError, ExistingLimbo,
41
ExistingPendingDeletion, ImmortalLimbo,
42
ImmortalPendingDeletion, LockError)
43
from bzrlib.osutils import file_kind, pathjoin
44
from bzrlib.merge import Merge3Merger, Merger
45
from bzrlib.tests import (
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
resolve_conflicts, cook_conflicts,
54
build_tree, get_backup_name,
55
_FileMover, resolve_checkout,
56
TransformPreview, create_from_tree)
59
class TestTreeTransform(tests.TestCaseWithTransport):
62
super(TestTreeTransform, self).setUp()
63
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
66
def get_transform(self):
67
transform = TreeTransform(self.wt)
68
self.addCleanup(transform.finalize)
69
return transform, transform.root
71
def test_existing_limbo(self):
72
transform, root = self.get_transform()
73
limbo_name = transform._limbodir
74
deletion_path = transform._deletiondir
75
os.mkdir(pathjoin(limbo_name, 'hehe'))
76
self.assertRaises(ImmortalLimbo, transform.apply)
77
self.assertRaises(LockError, self.wt.unlock)
78
self.assertRaises(ExistingLimbo, self.get_transform)
79
self.assertRaises(LockError, self.wt.unlock)
80
os.rmdir(pathjoin(limbo_name, 'hehe'))
82
os.rmdir(deletion_path)
83
transform, root = self.get_transform()
86
def test_existing_pending_deletion(self):
87
transform, root = self.get_transform()
88
deletion_path = self._limbodir = urlutils.local_path_from_url(
89
transform._tree._transport.abspath('pending-deletion'))
90
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
91
self.assertRaises(ImmortalPendingDeletion, transform.apply)
92
self.assertRaises(LockError, self.wt.unlock)
93
self.assertRaises(ExistingPendingDeletion, self.get_transform)
96
transform, root = self.get_transform()
97
self.wt.lock_tree_write()
98
self.addCleanup(self.wt.unlock)
99
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
100
imaginary_id = transform.trans_id_tree_path('imaginary')
101
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
102
self.assertEqual(imaginary_id, imaginary_id2)
103
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
104
self.assertEqual(transform.final_kind(root), 'directory')
105
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
106
trans_id = transform.create_path('name', root)
107
self.assertIs(transform.final_file_id(trans_id), None)
108
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
109
transform.create_file('contents', trans_id)
110
transform.set_executability(True, trans_id)
111
transform.version_file('my_pretties', trans_id)
112
self.assertRaises(DuplicateKey, transform.version_file,
113
'my_pretties', trans_id)
114
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
115
self.assertEqual(transform.final_parent(trans_id), root)
116
self.assertIs(transform.final_parent(root), ROOT_PARENT)
117
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
118
oz_id = transform.create_path('oz', root)
119
transform.create_directory(oz_id)
120
transform.version_file('ozzie', oz_id)
121
trans_id2 = transform.create_path('name2', root)
122
transform.create_file('contents', trans_id2)
123
transform.set_executability(False, trans_id2)
124
transform.version_file('my_pretties2', trans_id2)
125
modified_paths = transform.apply().modified_paths
126
self.assertEqual('contents', self.wt.get_file_byname('name').read())
127
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
128
self.assertIs(self.wt.is_executable('my_pretties'), True)
129
self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
self.assertEqual(len(modified_paths), 3)
132
tree_mod_paths = [self.wt.id2abspath(f) for f in
133
('ozzie', 'my_pretties', 'my_pretties2')]
134
self.assertSubset(tree_mod_paths, modified_paths)
135
# is it safe to finalize repeatedly?
139
def test_hardlink(self):
140
self.requireFeature(HardlinkFeature)
141
transform, root = self.get_transform()
142
transform.new_file('file1', root, 'contents')
144
target = self.make_branch_and_tree('target')
145
target_transform = TreeTransform(target)
146
trans_id = target_transform.create_path('file1', target_transform.root)
147
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
148
target_transform.apply()
149
self.failUnlessExists('target/file1')
150
source_stat = os.stat(self.wt.abspath('file1'))
151
target_stat = os.stat('target/file1')
152
self.assertEqual(source_stat, target_stat)
154
def test_convenience(self):
155
transform, root = self.get_transform()
156
self.wt.lock_tree_write()
157
self.addCleanup(self.wt.unlock)
158
trans_id = transform.new_file('name', root, 'contents',
160
oz = transform.new_directory('oz', root, 'oz-id')
161
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
toto = transform.new_file('toto', dorothy, 'toto-contents',
165
self.assertEqual(len(transform.find_conflicts()), 0)
167
self.assertRaises(ReusingTransform, transform.find_conflicts)
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
169
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
170
self.assertIs(self.wt.is_executable('my_pretties'), True)
171
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
172
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
173
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
175
self.assertEqual('toto-contents',
176
self.wt.get_file_byname('oz/dorothy/toto').read())
177
self.assertIs(self.wt.is_executable('toto-id'), False)
179
def test_tree_reference(self):
180
transform, root = self.get_transform()
181
tree = transform._tree
182
trans_id = transform.new_directory('reference', root, 'subtree-id')
183
transform.set_tree_reference('subtree-revision', trans_id)
186
self.addCleanup(tree.unlock)
187
self.assertEqual('subtree-revision',
188
tree.inventory['subtree-id'].reference_revision)
190
def test_conflicts(self):
191
transform, root = self.get_transform()
192
trans_id = transform.new_file('name', root, 'contents',
194
self.assertEqual(len(transform.find_conflicts()), 0)
195
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
self.assertEqual(transform.find_conflicts(),
197
[('duplicate', trans_id, trans_id2, 'name')])
198
self.assertRaises(MalformedTransform, transform.apply)
199
transform.adjust_path('name', trans_id, trans_id2)
200
self.assertEqual(transform.find_conflicts(),
201
[('non-directory parent', trans_id)])
202
tinman_id = transform.trans_id_tree_path('tinman')
203
transform.adjust_path('name', tinman_id, trans_id2)
204
self.assertEqual(transform.find_conflicts(),
205
[('unversioned parent', tinman_id),
206
('missing parent', tinman_id)])
207
lion_id = transform.create_path('lion', root)
208
self.assertEqual(transform.find_conflicts(),
209
[('unversioned parent', tinman_id),
210
('missing parent', tinman_id)])
211
transform.adjust_path('name', lion_id, trans_id2)
212
self.assertEqual(transform.find_conflicts(),
213
[('unversioned parent', lion_id),
214
('missing parent', lion_id)])
215
transform.version_file("Courage", lion_id)
216
self.assertEqual(transform.find_conflicts(),
217
[('missing parent', lion_id),
218
('versioning no contents', lion_id)])
219
transform.adjust_path('name2', root, trans_id2)
220
self.assertEqual(transform.find_conflicts(),
221
[('versioning no contents', lion_id)])
222
transform.create_file('Contents, okay?', lion_id)
223
transform.adjust_path('name2', trans_id2, trans_id2)
224
self.assertEqual(transform.find_conflicts(),
225
[('parent loop', trans_id2),
226
('non-directory parent', trans_id2)])
227
transform.adjust_path('name2', root, trans_id2)
228
oz_id = transform.new_directory('oz', root)
229
transform.set_executability(True, oz_id)
230
self.assertEqual(transform.find_conflicts(),
231
[('unversioned executability', oz_id)])
232
transform.version_file('oz-id', oz_id)
233
self.assertEqual(transform.find_conflicts(),
234
[('non-file executability', oz_id)])
235
transform.set_executability(None, oz_id)
236
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
238
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
239
self.assertEqual('contents', file(self.wt.abspath('name')).read())
240
transform2, root = self.get_transform()
241
oz_id = transform2.trans_id_tree_file_id('oz-id')
242
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
243
result = transform2.find_conflicts()
244
fp = FinalPaths(transform2)
245
self.assert_('oz/tip' in transform2._tree_path_ids)
246
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
self.assertEqual(len(result), 2)
248
self.assertEqual((result[0][0], result[0][1]),
249
('duplicate', newtip))
250
self.assertEqual((result[1][0], result[1][2]),
251
('duplicate id', newtip))
252
transform2.finalize()
253
transform3 = TreeTransform(self.wt)
254
self.addCleanup(transform3.finalize)
255
oz_id = transform3.trans_id_tree_file_id('oz-id')
256
transform3.delete_contents(oz_id)
257
self.assertEqual(transform3.find_conflicts(),
258
[('missing parent', oz_id)])
259
root_id = transform3.root
260
tip_id = transform3.trans_id_tree_file_id('tip-id')
261
transform3.adjust_path('tip', root_id, tip_id)
264
def test_conflict_on_case_insensitive(self):
265
tree = self.make_branch_and_tree('tree')
266
# Don't try this at home, kids!
267
# Force the tree to report that it is case sensitive, for conflict
269
tree.case_sensitive = True
270
transform = TreeTransform(tree)
271
self.addCleanup(transform.finalize)
272
transform.new_file('file', transform.root, 'content')
273
transform.new_file('FiLe', transform.root, 'content')
274
result = transform.find_conflicts()
275
self.assertEqual([], result)
277
# Force the tree to report that it is case insensitive, for conflict
279
tree.case_sensitive = False
280
transform = TreeTransform(tree)
281
self.addCleanup(transform.finalize)
282
transform.new_file('file', transform.root, 'content')
283
transform.new_file('FiLe', transform.root, 'content')
284
result = transform.find_conflicts()
285
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
287
def test_conflict_on_case_insensitive_existing(self):
288
tree = self.make_branch_and_tree('tree')
289
self.build_tree(['tree/FiLe'])
290
# Don't try this at home, kids!
291
# Force the tree to report that it is case sensitive, for conflict
293
tree.case_sensitive = True
294
transform = TreeTransform(tree)
295
self.addCleanup(transform.finalize)
296
transform.new_file('file', transform.root, 'content')
297
result = transform.find_conflicts()
298
self.assertEqual([], result)
300
# Force the tree to report that it is case insensitive, for conflict
302
tree.case_sensitive = False
303
transform = TreeTransform(tree)
304
self.addCleanup(transform.finalize)
305
transform.new_file('file', transform.root, 'content')
306
result = transform.find_conflicts()
307
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
309
def test_resolve_case_insensitive_conflict(self):
310
tree = self.make_branch_and_tree('tree')
311
# Don't try this at home, kids!
312
# Force the tree to report that it is case insensitive, for conflict
314
tree.case_sensitive = False
315
transform = TreeTransform(tree)
316
self.addCleanup(transform.finalize)
317
transform.new_file('file', transform.root, 'content')
318
transform.new_file('FiLe', transform.root, 'content')
319
resolve_conflicts(transform)
321
self.failUnlessExists('tree/file')
322
self.failUnlessExists('tree/FiLe.moved')
324
def test_resolve_checkout_case_conflict(self):
325
tree = self.make_branch_and_tree('tree')
326
# Don't try this at home, kids!
327
# Force the tree to report that it is case insensitive, for conflict
329
tree.case_sensitive = False
330
transform = TreeTransform(tree)
331
self.addCleanup(transform.finalize)
332
transform.new_file('file', transform.root, 'content')
333
transform.new_file('FiLe', transform.root, 'content')
334
resolve_conflicts(transform,
335
pass_func=lambda t, c: resolve_checkout(t, c, []))
337
self.failUnlessExists('tree/file')
338
self.failUnlessExists('tree/FiLe.moved')
340
def test_apply_case_conflict(self):
341
"""Ensure that a transform with case conflicts can always be applied"""
342
tree = self.make_branch_and_tree('tree')
343
transform = TreeTransform(tree)
344
self.addCleanup(transform.finalize)
345
transform.new_file('file', transform.root, 'content')
346
transform.new_file('FiLe', transform.root, 'content')
347
dir = transform.new_directory('dir', transform.root)
348
transform.new_file('dirfile', dir, 'content')
349
transform.new_file('dirFiLe', dir, 'content')
350
resolve_conflicts(transform)
352
self.failUnlessExists('tree/file')
353
if not os.path.exists('tree/FiLe.moved'):
354
self.failUnlessExists('tree/FiLe')
355
self.failUnlessExists('tree/dir/dirfile')
356
if not os.path.exists('tree/dir/dirFiLe.moved'):
357
self.failUnlessExists('tree/dir/dirFiLe')
359
def test_case_insensitive_limbo(self):
360
tree = self.make_branch_and_tree('tree')
361
# Don't try this at home, kids!
362
# Force the tree to report that it is case insensitive
363
tree.case_sensitive = False
364
transform = TreeTransform(tree)
365
self.addCleanup(transform.finalize)
366
dir = transform.new_directory('dir', transform.root)
367
first = transform.new_file('file', dir, 'content')
368
second = transform.new_file('FiLe', dir, 'content')
369
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
372
def test_adjust_path_updates_child_limbo_names(self):
373
tree = self.make_branch_and_tree('tree')
374
transform = TreeTransform(tree)
375
self.addCleanup(transform.finalize)
376
foo_id = transform.new_directory('foo', transform.root)
377
bar_id = transform.new_directory('bar', foo_id)
378
baz_id = transform.new_directory('baz', bar_id)
379
qux_id = transform.new_directory('qux', baz_id)
380
transform.adjust_path('quxx', foo_id, bar_id)
381
self.assertStartsWith(transform._limbo_name(qux_id),
382
transform._limbo_name(bar_id))
384
def test_add_del(self):
385
start, root = self.get_transform()
386
start.new_directory('a', root, 'a')
388
transform, root = self.get_transform()
389
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
390
transform.new_directory('a', root, 'a')
393
def test_unversioning(self):
394
create_tree, root = self.get_transform()
395
parent_id = create_tree.new_directory('parent', root, 'parent-id')
396
create_tree.new_file('child', parent_id, 'child', 'child-id')
398
unversion = TreeTransform(self.wt)
399
self.addCleanup(unversion.finalize)
400
parent = unversion.trans_id_tree_path('parent')
401
unversion.unversion_file(parent)
402
self.assertEqual(unversion.find_conflicts(),
403
[('unversioned parent', parent_id)])
404
file_id = unversion.trans_id_tree_file_id('child-id')
405
unversion.unversion_file(file_id)
408
def test_name_invariants(self):
409
create_tree, root = self.get_transform()
411
root = create_tree.root
412
create_tree.new_file('name1', root, 'hello1', 'name1')
413
create_tree.new_file('name2', root, 'hello2', 'name2')
414
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
415
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
416
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
417
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
420
mangle_tree,root = self.get_transform()
421
root = mangle_tree.root
423
name1 = mangle_tree.trans_id_tree_file_id('name1')
424
name2 = mangle_tree.trans_id_tree_file_id('name2')
425
mangle_tree.adjust_path('name2', root, name1)
426
mangle_tree.adjust_path('name1', root, name2)
428
#tests for deleting parent directories
429
ddir = mangle_tree.trans_id_tree_file_id('ddir')
430
mangle_tree.delete_contents(ddir)
431
dfile = mangle_tree.trans_id_tree_file_id('dfile')
432
mangle_tree.delete_versioned(dfile)
433
mangle_tree.unversion_file(dfile)
434
mfile = mangle_tree.trans_id_tree_file_id('mfile')
435
mangle_tree.adjust_path('mfile', root, mfile)
437
#tests for adding parent directories
438
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
439
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
440
mangle_tree.adjust_path('mfile2', newdir, mfile2)
441
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
442
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
443
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
444
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
446
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
447
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
448
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
449
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
450
self.assertEqual(file(mfile2_path).read(), 'later2')
451
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
452
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
453
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
454
self.assertEqual(file(newfile_path).read(), 'hello3')
455
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
456
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
457
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
459
def test_both_rename(self):
460
create_tree,root = self.get_transform()
461
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
462
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
464
mangle_tree,root = self.get_transform()
465
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
466
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
467
mangle_tree.adjust_path('test', root, selftest)
468
mangle_tree.adjust_path('test_too_much', root, selftest)
469
mangle_tree.set_executability(True, blackbox)
472
def test_both_rename2(self):
473
create_tree,root = self.get_transform()
474
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
475
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
476
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
477
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
480
mangle_tree,root = self.get_transform()
481
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
482
tests = mangle_tree.trans_id_tree_file_id('tests-id')
483
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
484
mangle_tree.adjust_path('selftest', bzrlib, tests)
485
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
486
mangle_tree.set_executability(True, test_too_much)
489
def test_both_rename3(self):
490
create_tree,root = self.get_transform()
491
tests = create_tree.new_directory('tests', root, 'tests-id')
492
create_tree.new_file('test_too_much.py', tests, 'hello1',
495
mangle_tree,root = self.get_transform()
496
tests = mangle_tree.trans_id_tree_file_id('tests-id')
497
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
498
mangle_tree.adjust_path('selftest', root, tests)
499
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
500
mangle_tree.set_executability(True, test_too_much)
503
def test_move_dangling_ie(self):
504
create_tree, root = self.get_transform()
506
root = create_tree.root
507
create_tree.new_file('name1', root, 'hello1', 'name1')
509
delete_contents, root = self.get_transform()
510
file = delete_contents.trans_id_tree_file_id('name1')
511
delete_contents.delete_contents(file)
512
delete_contents.apply()
513
move_id, root = self.get_transform()
514
name1 = move_id.trans_id_tree_file_id('name1')
515
newdir = move_id.new_directory('dir', root, 'newdir')
516
move_id.adjust_path('name2', newdir, name1)
519
def test_replace_dangling_ie(self):
520
create_tree, root = self.get_transform()
522
root = create_tree.root
523
create_tree.new_file('name1', root, 'hello1', 'name1')
525
delete_contents = TreeTransform(self.wt)
526
self.addCleanup(delete_contents.finalize)
527
file = delete_contents.trans_id_tree_file_id('name1')
528
delete_contents.delete_contents(file)
529
delete_contents.apply()
530
delete_contents.finalize()
531
replace = TreeTransform(self.wt)
532
self.addCleanup(replace.finalize)
533
name2 = replace.new_file('name2', root, 'hello2', 'name1')
534
conflicts = replace.find_conflicts()
535
name1 = replace.trans_id_tree_file_id('name1')
536
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
537
resolve_conflicts(replace)
540
def _test_symlinks(self, link_name1,link_target1,
541
link_name2, link_target2):
543
def ozpath(p): return 'oz/' + p
545
self.requireFeature(SymlinkFeature)
546
transform, root = self.get_transform()
547
oz_id = transform.new_directory('oz', root, 'oz-id')
548
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
550
wiz_id = transform.create_path(link_name2, oz_id)
551
transform.create_symlink(link_target2, wiz_id)
552
transform.version_file('wiz-id2', wiz_id)
553
transform.set_executability(True, wiz_id)
554
self.assertEqual(transform.find_conflicts(),
555
[('non-file executability', wiz_id)])
556
transform.set_executability(None, wiz_id)
558
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
559
self.assertEqual('symlink',
560
file_kind(self.wt.abspath(ozpath(link_name1))))
561
self.assertEqual(link_target2,
562
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
563
self.assertEqual(link_target1,
564
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
566
def test_symlinks(self):
567
self._test_symlinks('wizard', 'wizard-target',
568
'wizard2', 'behind_curtain')
570
def test_symlinks_unicode(self):
571
self.requireFeature(tests.UnicodeFilenameFeature)
572
self._test_symlinks(u'\N{Euro Sign}wizard',
573
u'wizard-targ\N{Euro Sign}t',
574
u'\N{Euro Sign}wizard2',
575
u'b\N{Euro Sign}hind_curtain')
577
def test_unable_create_symlink(self):
579
wt = self.make_branch_and_tree('.')
580
tt = TreeTransform(wt) # TreeTransform obtains write lock
582
tt.new_symlink('foo', tt.root, 'bar')
586
os_symlink = getattr(os, 'symlink', None)
589
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
591
"Unable to create symlink 'foo' on this platform",
595
os.symlink = os_symlink
597
def get_conflicted(self):
598
create,root = self.get_transform()
599
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
600
oz = create.new_directory('oz', root, 'oz-id')
601
create.new_directory('emeraldcity', oz, 'emerald-id')
603
conflicts,root = self.get_transform()
604
# set up duplicate entry, duplicate id
605
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
607
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
608
oz = conflicts.trans_id_tree_file_id('oz-id')
609
# set up DeletedParent parent conflict
610
conflicts.delete_versioned(oz)
611
emerald = conflicts.trans_id_tree_file_id('emerald-id')
612
# set up MissingParent conflict
613
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
614
conflicts.adjust_path('munchkincity', root, munchkincity)
615
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
617
conflicts.adjust_path('emeraldcity', emerald, emerald)
618
return conflicts, emerald, oz, old_dorothy, new_dorothy
620
def test_conflict_resolution(self):
621
conflicts, emerald, oz, old_dorothy, new_dorothy =\
622
self.get_conflicted()
623
resolve_conflicts(conflicts)
624
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
625
self.assertIs(conflicts.final_file_id(old_dorothy), None)
626
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
627
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
628
self.assertEqual(conflicts.final_parent(emerald), oz)
631
def test_cook_conflicts(self):
632
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
633
raw_conflicts = resolve_conflicts(tt)
634
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
635
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
636
'dorothy', None, 'dorothy-id')
637
self.assertEqual(cooked_conflicts[0], duplicate)
638
duplicate_id = DuplicateID('Unversioned existing file',
639
'dorothy.moved', 'dorothy', None,
641
self.assertEqual(cooked_conflicts[1], duplicate_id)
642
missing_parent = MissingParent('Created directory', 'munchkincity',
644
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
645
self.assertEqual(cooked_conflicts[2], missing_parent)
646
unversioned_parent = UnversionedParent('Versioned directory',
649
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
651
self.assertEqual(cooked_conflicts[3], unversioned_parent)
652
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
653
'oz/emeraldcity', 'emerald-id', 'emerald-id')
654
self.assertEqual(cooked_conflicts[4], deleted_parent)
655
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
656
self.assertEqual(cooked_conflicts[6], parent_loop)
657
self.assertEqual(len(cooked_conflicts), 7)
660
def test_string_conflicts(self):
661
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
662
raw_conflicts = resolve_conflicts(tt)
663
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
665
conflicts_s = [str(c) for c in cooked_conflicts]
666
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
667
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
668
'Moved existing file to '
670
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
671
'Unversioned existing file '
673
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
674
' munchkincity. Created directory.')
675
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
676
' versioned, but has versioned'
677
' children. Versioned directory.')
678
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
679
" is not empty. Not deleting.")
680
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
681
' versioned, but has versioned'
682
' children. Versioned directory.')
683
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
684
' oz/emeraldcity. Cancelled move.')
686
def prepare_wrong_parent_kind(self):
687
tt, root = self.get_transform()
688
tt.new_file('parent', root, 'contents', 'parent-id')
690
tt, root = self.get_transform()
691
parent_id = tt.trans_id_file_id('parent-id')
692
tt.new_file('child,', parent_id, 'contents2', 'file-id')
695
def test_find_conflicts_wrong_parent_kind(self):
696
tt = self.prepare_wrong_parent_kind()
699
def test_resolve_conflicts_wrong_existing_parent_kind(self):
700
tt = self.prepare_wrong_parent_kind()
701
raw_conflicts = resolve_conflicts(tt)
702
self.assertEqual(set([('non-directory parent', 'Created directory',
703
'new-3')]), raw_conflicts)
704
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
705
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
706
'parent-id')], cooked_conflicts)
708
self.assertEqual(None, self.wt.path2id('parent'))
709
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
711
def test_resolve_conflicts_wrong_new_parent_kind(self):
712
tt, root = self.get_transform()
713
parent_id = tt.new_directory('parent', root, 'parent-id')
714
tt.new_file('child,', parent_id, 'contents2', 'file-id')
716
tt, root = self.get_transform()
717
parent_id = tt.trans_id_file_id('parent-id')
718
tt.delete_contents(parent_id)
719
tt.create_file('contents', parent_id)
720
raw_conflicts = resolve_conflicts(tt)
721
self.assertEqual(set([('non-directory parent', 'Created directory',
722
'new-3')]), raw_conflicts)
724
self.assertEqual(None, self.wt.path2id('parent'))
725
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
727
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
728
tt, root = self.get_transform()
729
parent_id = tt.new_directory('parent', root)
730
tt.new_file('child,', parent_id, 'contents2')
732
tt, root = self.get_transform()
733
parent_id = tt.trans_id_tree_path('parent')
734
tt.delete_contents(parent_id)
735
tt.create_file('contents', parent_id)
736
resolve_conflicts(tt)
738
self.assertIs(None, self.wt.path2id('parent'))
739
self.assertIs(None, self.wt.path2id('parent.new'))
741
def test_moving_versioned_directories(self):
742
create, root = self.get_transform()
743
kansas = create.new_directory('kansas', root, 'kansas-id')
744
create.new_directory('house', kansas, 'house-id')
745
create.new_directory('oz', root, 'oz-id')
747
cyclone, root = self.get_transform()
748
oz = cyclone.trans_id_tree_file_id('oz-id')
749
house = cyclone.trans_id_tree_file_id('house-id')
750
cyclone.adjust_path('house', oz, house)
753
def test_moving_root(self):
754
create, root = self.get_transform()
755
fun = create.new_directory('fun', root, 'fun-id')
756
create.new_directory('sun', root, 'sun-id')
757
create.new_directory('moon', root, 'moon')
759
transform, root = self.get_transform()
760
transform.adjust_root_path('oldroot', fun)
761
new_root=transform.trans_id_tree_path('')
762
transform.version_file('new-root', new_root)
765
def test_renames(self):
766
create, root = self.get_transform()
767
old = create.new_directory('old-parent', root, 'old-id')
768
intermediate = create.new_directory('intermediate', old, 'im-id')
769
myfile = create.new_file('myfile', intermediate, 'myfile-text',
772
rename, root = self.get_transform()
773
old = rename.trans_id_file_id('old-id')
774
rename.adjust_path('new', root, old)
775
myfile = rename.trans_id_file_id('myfile-id')
776
rename.set_executability(True, myfile)
779
def test_set_executability_order(self):
780
"""Ensure that executability behaves the same, no matter what order.
782
- create file and set executability simultaneously
783
- create file and set executability afterward
784
- unsetting the executability of a file whose executability has not been
785
declared should throw an exception (this may happen when a
786
merge attempts to create a file with a duplicate ID)
788
transform, root = self.get_transform()
791
self.addCleanup(wt.unlock)
792
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
794
sac = transform.new_file('set_after_creation', root,
795
'Set after creation', 'sac')
796
transform.set_executability(True, sac)
797
uws = transform.new_file('unset_without_set', root, 'Unset badly',
799
self.assertRaises(KeyError, transform.set_executability, None, uws)
801
self.assertTrue(wt.is_executable('soc'))
802
self.assertTrue(wt.is_executable('sac'))
804
def test_preserve_mode(self):
805
"""File mode is preserved when replacing content"""
806
if sys.platform == 'win32':
807
raise TestSkipped('chmod has no effect on win32')
808
transform, root = self.get_transform()
809
transform.new_file('file1', root, 'contents', 'file1-id', True)
812
self.addCleanup(self.wt.unlock)
813
self.assertTrue(self.wt.is_executable('file1-id'))
814
transform, root = self.get_transform()
815
file1_id = transform.trans_id_tree_file_id('file1-id')
816
transform.delete_contents(file1_id)
817
transform.create_file('contents2', file1_id)
819
self.assertTrue(self.wt.is_executable('file1-id'))
821
def test__set_mode_stats_correctly(self):
822
"""_set_mode stats to determine file mode."""
823
if sys.platform == 'win32':
824
raise TestSkipped('chmod has no effect on win32')
828
def instrumented_stat(path):
829
stat_paths.append(path)
830
return real_stat(path)
832
transform, root = self.get_transform()
834
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
835
file_id='bar-id-1', executable=False)
838
transform, root = self.get_transform()
839
bar1_id = transform.trans_id_tree_path('bar')
840
bar2_id = transform.trans_id_tree_path('bar2')
842
os.stat = instrumented_stat
843
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
848
bar1_abspath = self.wt.abspath('bar')
849
self.assertEqual([bar1_abspath], stat_paths)
851
def test_iter_changes(self):
852
self.wt.set_root_id('eert_toor')
853
transform, root = self.get_transform()
854
transform.new_file('old', root, 'blah', 'id-1', True)
856
transform, root = self.get_transform()
858
self.assertEqual([], list(transform.iter_changes()))
859
old = transform.trans_id_tree_file_id('id-1')
860
transform.unversion_file(old)
861
self.assertEqual([('id-1', ('old', None), False, (True, False),
862
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
863
(True, True))], list(transform.iter_changes()))
864
transform.new_directory('new', root, 'id-1')
865
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
866
('eert_toor', 'eert_toor'), ('old', 'new'),
867
('file', 'directory'),
868
(True, False))], list(transform.iter_changes()))
872
def test_iter_changes_new(self):
873
self.wt.set_root_id('eert_toor')
874
transform, root = self.get_transform()
875
transform.new_file('old', root, 'blah')
877
transform, root = self.get_transform()
879
old = transform.trans_id_tree_path('old')
880
transform.version_file('id-1', old)
881
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
882
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
883
(False, False))], list(transform.iter_changes()))
887
def test_iter_changes_modifications(self):
888
self.wt.set_root_id('eert_toor')
889
transform, root = self.get_transform()
890
transform.new_file('old', root, 'blah', 'id-1')
891
transform.new_file('new', root, 'blah')
892
transform.new_directory('subdir', root, 'subdir-id')
894
transform, root = self.get_transform()
896
old = transform.trans_id_tree_path('old')
897
subdir = transform.trans_id_tree_file_id('subdir-id')
898
new = transform.trans_id_tree_path('new')
899
self.assertEqual([], list(transform.iter_changes()))
902
transform.delete_contents(old)
903
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
904
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
905
(False, False))], list(transform.iter_changes()))
908
transform.create_file('blah', old)
909
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
910
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
911
(False, False))], list(transform.iter_changes()))
912
transform.cancel_deletion(old)
913
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
914
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
915
(False, False))], list(transform.iter_changes()))
916
transform.cancel_creation(old)
918
# move file_id to a different file
919
self.assertEqual([], list(transform.iter_changes()))
920
transform.unversion_file(old)
921
transform.version_file('id-1', new)
922
transform.adjust_path('old', root, new)
923
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
924
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
925
(False, False))], list(transform.iter_changes()))
926
transform.cancel_versioning(new)
927
transform._removed_id = set()
930
self.assertEqual([], list(transform.iter_changes()))
931
transform.set_executability(True, old)
932
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
933
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
934
(False, True))], list(transform.iter_changes()))
935
transform.set_executability(None, old)
938
self.assertEqual([], list(transform.iter_changes()))
939
transform.adjust_path('new', root, old)
940
transform._new_parent = {}
941
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
942
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
943
(False, False))], list(transform.iter_changes()))
944
transform._new_name = {}
947
self.assertEqual([], list(transform.iter_changes()))
948
transform.adjust_path('new', subdir, old)
949
transform._new_name = {}
950
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
951
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
952
('file', 'file'), (False, False))],
953
list(transform.iter_changes()))
954
transform._new_path = {}
959
def test_iter_changes_modified_bleed(self):
960
self.wt.set_root_id('eert_toor')
961
"""Modified flag should not bleed from one change to another"""
962
# unfortunately, we have no guarantee that file1 (which is modified)
963
# will be applied before file2. And if it's applied after file2, it
964
# obviously can't bleed into file2's change output. But for now, it
966
transform, root = self.get_transform()
967
transform.new_file('file1', root, 'blah', 'id-1')
968
transform.new_file('file2', root, 'blah', 'id-2')
970
transform, root = self.get_transform()
972
transform.delete_contents(transform.trans_id_file_id('id-1'))
973
transform.set_executability(True,
974
transform.trans_id_file_id('id-2'))
975
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
976
('eert_toor', 'eert_toor'), ('file1', u'file1'),
977
('file', None), (False, False)),
978
('id-2', (u'file2', u'file2'), False, (True, True),
979
('eert_toor', 'eert_toor'), ('file2', u'file2'),
980
('file', 'file'), (False, True))],
981
list(transform.iter_changes()))
985
def test_iter_changes_move_missing(self):
986
"""Test moving ids with no files around"""
987
self.wt.set_root_id('toor_eert')
988
# Need two steps because versioning a non-existant file is a conflict.
989
transform, root = self.get_transform()
990
transform.new_directory('floater', root, 'floater-id')
992
transform, root = self.get_transform()
993
transform.delete_contents(transform.trans_id_tree_path('floater'))
995
transform, root = self.get_transform()
996
floater = transform.trans_id_tree_path('floater')
998
transform.adjust_path('flitter', root, floater)
999
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1000
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1001
(None, None), (False, False))], list(transform.iter_changes()))
1003
transform.finalize()
1005
def test_iter_changes_pointless(self):
1006
"""Ensure that no-ops are not treated as modifications"""
1007
self.wt.set_root_id('eert_toor')
1008
transform, root = self.get_transform()
1009
transform.new_file('old', root, 'blah', 'id-1')
1010
transform.new_directory('subdir', root, 'subdir-id')
1012
transform, root = self.get_transform()
1014
old = transform.trans_id_tree_path('old')
1015
subdir = transform.trans_id_tree_file_id('subdir-id')
1016
self.assertEqual([], list(transform.iter_changes()))
1017
transform.delete_contents(subdir)
1018
transform.create_directory(subdir)
1019
transform.set_executability(False, old)
1020
transform.unversion_file(old)
1021
transform.version_file('id-1', old)
1022
transform.adjust_path('old', root, old)
1023
self.assertEqual([], list(transform.iter_changes()))
1025
transform.finalize()
1027
def test_rename_count(self):
1028
transform, root = self.get_transform()
1029
transform.new_file('name1', root, 'contents')
1030
self.assertEqual(transform.rename_count, 0)
1032
self.assertEqual(transform.rename_count, 1)
1033
transform2, root = self.get_transform()
1034
transform2.adjust_path('name2', root,
1035
transform2.trans_id_tree_path('name1'))
1036
self.assertEqual(transform2.rename_count, 0)
1038
self.assertEqual(transform2.rename_count, 2)
1040
def test_change_parent(self):
1041
"""Ensure that after we change a parent, the results are still right.
1043
Renames and parent changes on pending transforms can happen as part
1044
of conflict resolution, and are explicitly permitted by the
1047
This test ensures they work correctly with the rename-avoidance
1050
transform, root = self.get_transform()
1051
parent1 = transform.new_directory('parent1', root)
1052
child1 = transform.new_file('child1', parent1, 'contents')
1053
parent2 = transform.new_directory('parent2', root)
1054
transform.adjust_path('child1', parent2, child1)
1056
self.failIfExists(self.wt.abspath('parent1/child1'))
1057
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1058
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1059
# no rename for child1 (counting only renames during apply)
1060
self.failUnlessEqual(2, transform.rename_count)
1062
def test_cancel_parent(self):
1063
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1065
This is like the test_change_parent, except that we cancel the parent
1066
before adjusting the path. The transform must detect that the
1067
directory is non-empty, and move children to safe locations.
1069
transform, root = self.get_transform()
1070
parent1 = transform.new_directory('parent1', root)
1071
child1 = transform.new_file('child1', parent1, 'contents')
1072
child2 = transform.new_file('child2', parent1, 'contents')
1074
transform.cancel_creation(parent1)
1076
self.fail('Failed to move child1 before deleting parent1')
1077
transform.cancel_creation(child2)
1078
transform.create_directory(parent1)
1080
transform.cancel_creation(parent1)
1081
# If the transform incorrectly believes that child2 is still in
1082
# parent1's limbo directory, it will try to rename it and fail
1083
# because was already moved by the first cancel_creation.
1085
self.fail('Transform still thinks child2 is a child of parent1')
1086
parent2 = transform.new_directory('parent2', root)
1087
transform.adjust_path('child1', parent2, child1)
1089
self.failIfExists(self.wt.abspath('parent1'))
1090
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1091
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1092
self.failUnlessEqual(2, transform.rename_count)
1094
def test_adjust_and_cancel(self):
1095
"""Make sure adjust_path keeps track of limbo children properly"""
1096
transform, root = self.get_transform()
1097
parent1 = transform.new_directory('parent1', root)
1098
child1 = transform.new_file('child1', parent1, 'contents')
1099
parent2 = transform.new_directory('parent2', root)
1100
transform.adjust_path('child1', parent2, child1)
1101
transform.cancel_creation(child1)
1103
transform.cancel_creation(parent1)
1104
# if the transform thinks child1 is still in parent1's limbo
1105
# directory, it will attempt to move it and fail.
1107
self.fail('Transform still thinks child1 is a child of parent1')
1108
transform.finalize()
1110
def test_noname_contents(self):
1111
"""TreeTransform should permit deferring naming files."""
1112
transform, root = self.get_transform()
1113
parent = transform.trans_id_file_id('parent-id')
1115
transform.create_directory(parent)
1117
self.fail("Can't handle contents with no name")
1118
transform.finalize()
1120
def test_noname_contents_nested(self):
1121
"""TreeTransform should permit deferring naming files."""
1122
transform, root = self.get_transform()
1123
parent = transform.trans_id_file_id('parent-id')
1125
transform.create_directory(parent)
1127
self.fail("Can't handle contents with no name")
1128
child = transform.new_directory('child', parent)
1129
transform.adjust_path('parent', root, parent)
1131
self.failUnlessExists(self.wt.abspath('parent/child'))
1132
self.assertEqual(1, transform.rename_count)
1134
def test_reuse_name(self):
1135
"""Avoid reusing the same limbo name for different files"""
1136
transform, root = self.get_transform()
1137
parent = transform.new_directory('parent', root)
1138
child1 = transform.new_directory('child', parent)
1140
child2 = transform.new_directory('child', parent)
1142
self.fail('Tranform tried to use the same limbo name twice')
1143
transform.adjust_path('child2', parent, child2)
1145
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1146
# child2 is put into top-level limbo because child1 has already
1147
# claimed the direct limbo path when child2 is created. There is no
1148
# advantage in renaming files once they're in top-level limbo, except
1150
self.assertEqual(2, transform.rename_count)
1152
def test_reuse_when_first_moved(self):
1153
"""Don't avoid direct paths when it is safe to use them"""
1154
transform, root = self.get_transform()
1155
parent = transform.new_directory('parent', root)
1156
child1 = transform.new_directory('child', parent)
1157
transform.adjust_path('child1', parent, child1)
1158
child2 = transform.new_directory('child', parent)
1160
# limbo/new-1 => parent
1161
self.assertEqual(1, transform.rename_count)
1163
def test_reuse_after_cancel(self):
1164
"""Don't avoid direct paths when it is safe to use them"""
1165
transform, root = self.get_transform()
1166
parent2 = transform.new_directory('parent2', root)
1167
child1 = transform.new_directory('child1', parent2)
1168
transform.cancel_creation(parent2)
1169
transform.create_directory(parent2)
1170
child2 = transform.new_directory('child1', parent2)
1171
transform.adjust_path('child2', parent2, child1)
1173
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1174
self.assertEqual(2, transform.rename_count)
1176
def test_finalize_order(self):
1177
"""Finalize must be done in child-to-parent order"""
1178
transform, root = self.get_transform()
1179
parent = transform.new_directory('parent', root)
1180
child = transform.new_directory('child', parent)
1182
transform.finalize()
1184
self.fail('Tried to remove parent before child1')
1186
def test_cancel_with_cancelled_child_should_succeed(self):
1187
transform, root = self.get_transform()
1188
parent = transform.new_directory('parent', root)
1189
child = transform.new_directory('child', parent)
1190
transform.cancel_creation(child)
1191
transform.cancel_creation(parent)
1192
transform.finalize()
1194
def test_rollback_on_directory_clash(self):
1196
wt = self.make_branch_and_tree('.')
1197
tt = TreeTransform(wt) # TreeTransform obtains write lock
1199
foo = tt.new_directory('foo', tt.root)
1200
tt.new_file('bar', foo, 'foobar')
1201
baz = tt.new_directory('baz', tt.root)
1202
tt.new_file('qux', baz, 'quux')
1203
# Ask for a rename 'foo' -> 'baz'
1204
tt.adjust_path('baz', tt.root, foo)
1205
# Lie to tt that we've already resolved all conflicts.
1206
tt.apply(no_conflicts=True)
1210
# The rename will fail because the target directory is not empty (but
1211
# raises FileExists anyway).
1212
err = self.assertRaises(errors.FileExists, tt_helper)
1213
self.assertContainsRe(str(err),
1214
"^File exists: .+/baz")
1216
def test_two_directories_clash(self):
1218
wt = self.make_branch_and_tree('.')
1219
tt = TreeTransform(wt) # TreeTransform obtains write lock
1221
foo_1 = tt.new_directory('foo', tt.root)
1222
tt.new_directory('bar', foo_1)
1223
# Adding the same directory with a different content
1224
foo_2 = tt.new_directory('foo', tt.root)
1225
tt.new_directory('baz', foo_2)
1226
# Lie to tt that we've already resolved all conflicts.
1227
tt.apply(no_conflicts=True)
1231
err = self.assertRaises(errors.FileExists, tt_helper)
1232
self.assertContainsRe(str(err),
1233
"^File exists: .+/foo")
1235
def test_two_directories_clash_finalize(self):
1237
wt = self.make_branch_and_tree('.')
1238
tt = TreeTransform(wt) # TreeTransform obtains write lock
1240
foo_1 = tt.new_directory('foo', tt.root)
1241
tt.new_directory('bar', foo_1)
1242
# Adding the same directory with a different content
1243
foo_2 = tt.new_directory('foo', tt.root)
1244
tt.new_directory('baz', foo_2)
1245
# Lie to tt that we've already resolved all conflicts.
1246
tt.apply(no_conflicts=True)
1250
err = self.assertRaises(errors.FileExists, tt_helper)
1251
self.assertContainsRe(str(err),
1252
"^File exists: .+/foo")
1254
def test_file_to_directory(self):
1255
wt = self.make_branch_and_tree('.')
1256
self.build_tree(['foo'])
1259
tt = TreeTransform(wt)
1260
self.addCleanup(tt.finalize)
1261
foo_trans_id = tt.trans_id_tree_path("foo")
1262
tt.delete_contents(foo_trans_id)
1263
tt.create_directory(foo_trans_id)
1264
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1265
tt.create_file(["aa\n"], bar_trans_id)
1266
tt.version_file("bar-1", bar_trans_id)
1268
self.failUnlessExists("foo/bar")
1271
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1276
changes = wt.changes_from(wt.basis_tree())
1277
self.assertFalse(changes.has_changed(), changes)
1279
def test_file_to_symlink(self):
1280
self.requireFeature(SymlinkFeature)
1281
wt = self.make_branch_and_tree('.')
1282
self.build_tree(['foo'])
1285
tt = TreeTransform(wt)
1286
self.addCleanup(tt.finalize)
1287
foo_trans_id = tt.trans_id_tree_path("foo")
1288
tt.delete_contents(foo_trans_id)
1289
tt.create_symlink("bar", foo_trans_id)
1291
self.failUnlessExists("foo")
1293
self.addCleanup(wt.unlock)
1294
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1297
def test_dir_to_file(self):
1298
wt = self.make_branch_and_tree('.')
1299
self.build_tree(['foo/', 'foo/bar'])
1300
wt.add(['foo', 'foo/bar'])
1302
tt = TreeTransform(wt)
1303
self.addCleanup(tt.finalize)
1304
foo_trans_id = tt.trans_id_tree_path("foo")
1305
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1306
tt.delete_contents(foo_trans_id)
1307
tt.delete_versioned(bar_trans_id)
1308
tt.create_file(["aa\n"], foo_trans_id)
1310
self.failUnlessExists("foo")
1312
self.addCleanup(wt.unlock)
1313
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1316
def test_dir_to_hardlink(self):
1317
self.requireFeature(HardlinkFeature)
1318
wt = self.make_branch_and_tree('.')
1319
self.build_tree(['foo/', 'foo/bar'])
1320
wt.add(['foo', 'foo/bar'])
1322
tt = TreeTransform(wt)
1323
self.addCleanup(tt.finalize)
1324
foo_trans_id = tt.trans_id_tree_path("foo")
1325
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1326
tt.delete_contents(foo_trans_id)
1327
tt.delete_versioned(bar_trans_id)
1328
self.build_tree(['baz'])
1329
tt.create_hardlink("baz", foo_trans_id)
1331
self.failUnlessExists("foo")
1332
self.failUnlessExists("baz")
1334
self.addCleanup(wt.unlock)
1335
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1338
def test_no_final_path(self):
1339
transform, root = self.get_transform()
1340
trans_id = transform.trans_id_file_id('foo')
1341
transform.create_file('bar', trans_id)
1342
transform.cancel_creation(trans_id)
1345
def test_create_from_tree(self):
1346
tree1 = self.make_branch_and_tree('tree1')
1347
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1348
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1349
tree2 = self.make_branch_and_tree('tree2')
1350
tt = TreeTransform(tree2)
1351
foo_trans_id = tt.create_path('foo', tt.root)
1352
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1353
bar_trans_id = tt.create_path('bar', tt.root)
1354
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1356
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1357
self.assertFileEqual('baz', 'tree2/bar')
1359
def test_create_from_tree_bytes(self):
1360
"""Provided lines are used instead of tree content."""
1361
tree1 = self.make_branch_and_tree('tree1')
1362
self.build_tree_contents([('tree1/foo', 'bar'),])
1363
tree1.add('foo', 'foo-id')
1364
tree2 = self.make_branch_and_tree('tree2')
1365
tt = TreeTransform(tree2)
1366
foo_trans_id = tt.create_path('foo', tt.root)
1367
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1369
self.assertFileEqual('qux', 'tree2/foo')
1371
def test_create_from_tree_symlink(self):
1372
self.requireFeature(SymlinkFeature)
1373
tree1 = self.make_branch_and_tree('tree1')
1374
os.symlink('bar', 'tree1/foo')
1375
tree1.add('foo', 'foo-id')
1376
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1377
foo_trans_id = tt.create_path('foo', tt.root)
1378
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1380
self.assertEqual('bar', os.readlink('tree2/foo'))
1383
class TransformGroup(object):
1385
def __init__(self, dirname, root_id):
1388
self.wt = BzrDir.create_standalone_workingtree(dirname)
1389
self.wt.set_root_id(root_id)
1390
self.b = self.wt.branch
1391
self.tt = TreeTransform(self.wt)
1392
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1395
def conflict_text(tree, merge):
1396
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1397
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1400
class TestTransformMerge(TestCaseInTempDir):
1402
def test_text_merge(self):
1403
root_id = generate_ids.gen_root_id()
1404
base = TransformGroup("base", root_id)
1405
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1406
base.tt.new_file('b', base.root, 'b1', 'b')
1407
base.tt.new_file('c', base.root, 'c', 'c')
1408
base.tt.new_file('d', base.root, 'd', 'd')
1409
base.tt.new_file('e', base.root, 'e', 'e')
1410
base.tt.new_file('f', base.root, 'f', 'f')
1411
base.tt.new_directory('g', base.root, 'g')
1412
base.tt.new_directory('h', base.root, 'h')
1414
other = TransformGroup("other", root_id)
1415
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1416
other.tt.new_file('b', other.root, 'b2', 'b')
1417
other.tt.new_file('c', other.root, 'c2', 'c')
1418
other.tt.new_file('d', other.root, 'd', 'd')
1419
other.tt.new_file('e', other.root, 'e2', 'e')
1420
other.tt.new_file('f', other.root, 'f', 'f')
1421
other.tt.new_file('g', other.root, 'g', 'g')
1422
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1423
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1425
this = TransformGroup("this", root_id)
1426
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1427
this.tt.new_file('b', this.root, 'b', 'b')
1428
this.tt.new_file('c', this.root, 'c', 'c')
1429
this.tt.new_file('d', this.root, 'd2', 'd')
1430
this.tt.new_file('e', this.root, 'e2', 'e')
1431
this.tt.new_file('f', this.root, 'f', 'f')
1432
this.tt.new_file('g', this.root, 'g', 'g')
1433
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1434
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1436
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1439
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1440
# three-way text conflict
1441
self.assertEqual(this.wt.get_file('b').read(),
1442
conflict_text('b', 'b2'))
1444
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1446
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1447
# Ambigious clean merge
1448
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1450
self.assertEqual(this.wt.get_file('f').read(), 'f')
1451
# Correct correct results when THIS == OTHER
1452
self.assertEqual(this.wt.get_file('g').read(), 'g')
1453
# Text conflict when THIS & OTHER are text and BASE is dir
1454
self.assertEqual(this.wt.get_file('h').read(),
1455
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1456
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1458
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1460
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1461
self.assertEqual(this.wt.get_file('i').read(),
1462
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1463
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1465
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1467
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1468
modified = ['a', 'b', 'c', 'h', 'i']
1469
merge_modified = this.wt.merge_modified()
1470
self.assertSubset(merge_modified, modified)
1471
self.assertEqual(len(merge_modified), len(modified))
1472
file(this.wt.id2abspath('a'), 'wb').write('booga')
1474
merge_modified = this.wt.merge_modified()
1475
self.assertSubset(merge_modified, modified)
1476
self.assertEqual(len(merge_modified), len(modified))
1480
def test_file_merge(self):
1481
self.requireFeature(SymlinkFeature)
1482
root_id = generate_ids.gen_root_id()
1483
base = TransformGroup("BASE", root_id)
1484
this = TransformGroup("THIS", root_id)
1485
other = TransformGroup("OTHER", root_id)
1486
for tg in this, base, other:
1487
tg.tt.new_directory('a', tg.root, 'a')
1488
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1489
tg.tt.new_file('c', tg.root, 'c', 'c')
1490
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1491
targets = ((base, 'base-e', 'base-f', None, None),
1492
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1493
(other, 'other-e', None, 'other-g', 'other-h'))
1494
for tg, e_target, f_target, g_target, h_target in targets:
1495
for link, target in (('e', e_target), ('f', f_target),
1496
('g', g_target), ('h', h_target)):
1497
if target is not None:
1498
tg.tt.new_symlink(link, tg.root, target, link)
1500
for tg in this, base, other:
1502
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1503
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1504
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1505
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1506
for suffix in ('THIS', 'BASE', 'OTHER'):
1507
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1508
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1509
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1510
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1511
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1512
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1513
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1514
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1515
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1516
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1517
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1518
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1519
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1520
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1522
def test_filename_merge(self):
1523
root_id = generate_ids.gen_root_id()
1524
base = TransformGroup("BASE", root_id)
1525
this = TransformGroup("THIS", root_id)
1526
other = TransformGroup("OTHER", root_id)
1527
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1528
for t in [base, this, other]]
1529
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1530
for t in [base, this, other]]
1531
base.tt.new_directory('c', base_a, 'c')
1532
this.tt.new_directory('c1', this_a, 'c')
1533
other.tt.new_directory('c', other_b, 'c')
1535
base.tt.new_directory('d', base_a, 'd')
1536
this.tt.new_directory('d1', this_b, 'd')
1537
other.tt.new_directory('d', other_a, 'd')
1539
base.tt.new_directory('e', base_a, 'e')
1540
this.tt.new_directory('e', this_a, 'e')
1541
other.tt.new_directory('e1', other_b, 'e')
1543
base.tt.new_directory('f', base_a, 'f')
1544
this.tt.new_directory('f1', this_b, 'f')
1545
other.tt.new_directory('f1', other_b, 'f')
1547
for tg in [this, base, other]:
1549
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1550
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1551
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1552
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1553
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1555
def test_filename_merge_conflicts(self):
1556
root_id = generate_ids.gen_root_id()
1557
base = TransformGroup("BASE", root_id)
1558
this = TransformGroup("THIS", root_id)
1559
other = TransformGroup("OTHER", root_id)
1560
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1561
for t in [base, this, other]]
1562
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1563
for t in [base, this, other]]
1565
base.tt.new_file('g', base_a, 'g', 'g')
1566
other.tt.new_file('g1', other_b, 'g1', 'g')
1568
base.tt.new_file('h', base_a, 'h', 'h')
1569
this.tt.new_file('h1', this_b, 'h1', 'h')
1571
base.tt.new_file('i', base.root, 'i', 'i')
1572
other.tt.new_directory('i1', this_b, 'i')
1574
for tg in [this, base, other]:
1576
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1578
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1579
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1580
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1581
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1582
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1583
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1584
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1587
class TestBuildTree(tests.TestCaseWithTransport):
1589
def test_build_tree_with_symlinks(self):
1590
self.requireFeature(SymlinkFeature)
1592
a = BzrDir.create_standalone_workingtree('a')
1594
file('a/foo/bar', 'wb').write('contents')
1595
os.symlink('a/foo/bar', 'a/foo/baz')
1596
a.add(['foo', 'foo/bar', 'foo/baz'])
1597
a.commit('initial commit')
1598
b = BzrDir.create_standalone_workingtree('b')
1599
basis = a.basis_tree()
1601
self.addCleanup(basis.unlock)
1602
build_tree(basis, b)
1603
self.assertIs(os.path.isdir('b/foo'), True)
1604
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1605
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1607
def test_build_with_references(self):
1608
tree = self.make_branch_and_tree('source',
1609
format='dirstate-with-subtree')
1610
subtree = self.make_branch_and_tree('source/subtree',
1611
format='dirstate-with-subtree')
1612
tree.add_reference(subtree)
1613
tree.commit('a revision')
1614
tree.branch.create_checkout('target')
1615
self.failUnlessExists('target')
1616
self.failUnlessExists('target/subtree')
1618
def test_file_conflict_handling(self):
1619
"""Ensure that when building trees, conflict handling is done"""
1620
source = self.make_branch_and_tree('source')
1621
target = self.make_branch_and_tree('target')
1622
self.build_tree(['source/file', 'target/file'])
1623
source.add('file', 'new-file')
1624
source.commit('added file')
1625
build_tree(source.basis_tree(), target)
1626
self.assertEqual([DuplicateEntry('Moved existing file to',
1627
'file.moved', 'file', None, 'new-file')],
1629
target2 = self.make_branch_and_tree('target2')
1630
target_file = file('target2/file', 'wb')
1632
source_file = file('source/file', 'rb')
1634
target_file.write(source_file.read())
1639
build_tree(source.basis_tree(), target2)
1640
self.assertEqual([], target2.conflicts())
1642
def test_symlink_conflict_handling(self):
1643
"""Ensure that when building trees, conflict handling is done"""
1644
self.requireFeature(SymlinkFeature)
1645
source = self.make_branch_and_tree('source')
1646
os.symlink('foo', 'source/symlink')
1647
source.add('symlink', 'new-symlink')
1648
source.commit('added file')
1649
target = self.make_branch_and_tree('target')
1650
os.symlink('bar', 'target/symlink')
1651
build_tree(source.basis_tree(), target)
1652
self.assertEqual([DuplicateEntry('Moved existing file to',
1653
'symlink.moved', 'symlink', None, 'new-symlink')],
1655
target = self.make_branch_and_tree('target2')
1656
os.symlink('foo', 'target2/symlink')
1657
build_tree(source.basis_tree(), target)
1658
self.assertEqual([], target.conflicts())
1660
def test_directory_conflict_handling(self):
1661
"""Ensure that when building trees, conflict handling is done"""
1662
source = self.make_branch_and_tree('source')
1663
target = self.make_branch_and_tree('target')
1664
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1665
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1666
source.commit('added file')
1667
build_tree(source.basis_tree(), target)
1668
self.assertEqual([], target.conflicts())
1669
self.failUnlessExists('target/dir1/file')
1671
# Ensure contents are merged
1672
target = self.make_branch_and_tree('target2')
1673
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1674
build_tree(source.basis_tree(), target)
1675
self.assertEqual([], target.conflicts())
1676
self.failUnlessExists('target2/dir1/file2')
1677
self.failUnlessExists('target2/dir1/file')
1679
# Ensure new contents are suppressed for existing branches
1680
target = self.make_branch_and_tree('target3')
1681
self.make_branch('target3/dir1')
1682
self.build_tree(['target3/dir1/file2'])
1683
build_tree(source.basis_tree(), target)
1684
self.failIfExists('target3/dir1/file')
1685
self.failUnlessExists('target3/dir1/file2')
1686
self.failUnlessExists('target3/dir1.diverted/file')
1687
self.assertEqual([DuplicateEntry('Diverted to',
1688
'dir1.diverted', 'dir1', 'new-dir1', None)],
1691
target = self.make_branch_and_tree('target4')
1692
self.build_tree(['target4/dir1/'])
1693
self.make_branch('target4/dir1/file')
1694
build_tree(source.basis_tree(), target)
1695
self.failUnlessExists('target4/dir1/file')
1696
self.assertEqual('directory', file_kind('target4/dir1/file'))
1697
self.failUnlessExists('target4/dir1/file.diverted')
1698
self.assertEqual([DuplicateEntry('Diverted to',
1699
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1702
def test_mixed_conflict_handling(self):
1703
"""Ensure that when building trees, conflict handling is done"""
1704
source = self.make_branch_and_tree('source')
1705
target = self.make_branch_and_tree('target')
1706
self.build_tree(['source/name', 'target/name/'])
1707
source.add('name', 'new-name')
1708
source.commit('added file')
1709
build_tree(source.basis_tree(), target)
1710
self.assertEqual([DuplicateEntry('Moved existing file to',
1711
'name.moved', 'name', None, 'new-name')], target.conflicts())
1713
def test_raises_in_populated(self):
1714
source = self.make_branch_and_tree('source')
1715
self.build_tree(['source/name'])
1717
source.commit('added name')
1718
target = self.make_branch_and_tree('target')
1719
self.build_tree(['target/name'])
1721
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1722
build_tree, source.basis_tree(), target)
1724
def test_build_tree_rename_count(self):
1725
source = self.make_branch_and_tree('source')
1726
self.build_tree(['source/file1', 'source/dir1/'])
1727
source.add(['file1', 'dir1'])
1728
source.commit('add1')
1729
target1 = self.make_branch_and_tree('target1')
1730
transform_result = build_tree(source.basis_tree(), target1)
1731
self.assertEqual(2, transform_result.rename_count)
1733
self.build_tree(['source/dir1/file2'])
1734
source.add(['dir1/file2'])
1735
source.commit('add3')
1736
target2 = self.make_branch_and_tree('target2')
1737
transform_result = build_tree(source.basis_tree(), target2)
1738
# children of non-root directories should not be renamed
1739
self.assertEqual(2, transform_result.rename_count)
1741
def create_ab_tree(self):
1742
"""Create a committed test tree with two files"""
1743
source = self.make_branch_and_tree('source')
1744
self.build_tree_contents([('source/file1', 'A')])
1745
self.build_tree_contents([('source/file2', 'B')])
1746
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1747
source.commit('commit files')
1749
self.addCleanup(source.unlock)
1752
def test_build_tree_accelerator_tree(self):
1753
source = self.create_ab_tree()
1754
self.build_tree_contents([('source/file2', 'C')])
1756
real_source_get_file = source.get_file
1757
def get_file(file_id, path=None):
1758
calls.append(file_id)
1759
return real_source_get_file(file_id, path)
1760
source.get_file = get_file
1761
target = self.make_branch_and_tree('target')
1762
revision_tree = source.basis_tree()
1763
revision_tree.lock_read()
1764
self.addCleanup(revision_tree.unlock)
1765
build_tree(revision_tree, target, source)
1766
self.assertEqual(['file1-id'], calls)
1768
self.addCleanup(target.unlock)
1769
self.assertEqual([], list(target.iter_changes(revision_tree)))
1771
def test_build_tree_accelerator_tree_missing_file(self):
1772
source = self.create_ab_tree()
1773
os.unlink('source/file1')
1774
source.remove(['file2'])
1775
target = self.make_branch_and_tree('target')
1776
revision_tree = source.basis_tree()
1777
revision_tree.lock_read()
1778
self.addCleanup(revision_tree.unlock)
1779
build_tree(revision_tree, target, source)
1781
self.addCleanup(target.unlock)
1782
self.assertEqual([], list(target.iter_changes(revision_tree)))
1784
def test_build_tree_accelerator_wrong_kind(self):
1785
self.requireFeature(SymlinkFeature)
1786
source = self.make_branch_and_tree('source')
1787
self.build_tree_contents([('source/file1', '')])
1788
self.build_tree_contents([('source/file2', '')])
1789
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1790
source.commit('commit files')
1791
os.unlink('source/file2')
1792
self.build_tree_contents([('source/file2/', 'C')])
1793
os.unlink('source/file1')
1794
os.symlink('file2', 'source/file1')
1796
real_source_get_file = source.get_file
1797
def get_file(file_id, path=None):
1798
calls.append(file_id)
1799
return real_source_get_file(file_id, path)
1800
source.get_file = get_file
1801
target = self.make_branch_and_tree('target')
1802
revision_tree = source.basis_tree()
1803
revision_tree.lock_read()
1804
self.addCleanup(revision_tree.unlock)
1805
build_tree(revision_tree, target, source)
1806
self.assertEqual([], calls)
1808
self.addCleanup(target.unlock)
1809
self.assertEqual([], list(target.iter_changes(revision_tree)))
1811
def test_build_tree_hardlink(self):
1812
self.requireFeature(HardlinkFeature)
1813
source = self.create_ab_tree()
1814
target = self.make_branch_and_tree('target')
1815
revision_tree = source.basis_tree()
1816
revision_tree.lock_read()
1817
self.addCleanup(revision_tree.unlock)
1818
build_tree(revision_tree, target, source, hardlink=True)
1820
self.addCleanup(target.unlock)
1821
self.assertEqual([], list(target.iter_changes(revision_tree)))
1822
source_stat = os.stat('source/file1')
1823
target_stat = os.stat('target/file1')
1824
self.assertEqual(source_stat, target_stat)
1826
# Explicitly disallowing hardlinks should prevent them.
1827
target2 = self.make_branch_and_tree('target2')
1828
build_tree(revision_tree, target2, source, hardlink=False)
1830
self.addCleanup(target2.unlock)
1831
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1832
source_stat = os.stat('source/file1')
1833
target2_stat = os.stat('target2/file1')
1834
self.assertNotEqual(source_stat, target2_stat)
1836
def test_build_tree_accelerator_tree_moved(self):
1837
source = self.make_branch_and_tree('source')
1838
self.build_tree_contents([('source/file1', 'A')])
1839
source.add(['file1'], ['file1-id'])
1840
source.commit('commit files')
1841
source.rename_one('file1', 'file2')
1843
self.addCleanup(source.unlock)
1844
target = self.make_branch_and_tree('target')
1845
revision_tree = source.basis_tree()
1846
revision_tree.lock_read()
1847
self.addCleanup(revision_tree.unlock)
1848
build_tree(revision_tree, target, source)
1850
self.addCleanup(target.unlock)
1851
self.assertEqual([], list(target.iter_changes(revision_tree)))
1853
def test_build_tree_hardlinks_preserve_execute(self):
1854
self.requireFeature(HardlinkFeature)
1855
source = self.create_ab_tree()
1856
tt = TreeTransform(source)
1857
trans_id = tt.trans_id_tree_file_id('file1-id')
1858
tt.set_executability(True, trans_id)
1860
self.assertTrue(source.is_executable('file1-id'))
1861
target = self.make_branch_and_tree('target')
1862
revision_tree = source.basis_tree()
1863
revision_tree.lock_read()
1864
self.addCleanup(revision_tree.unlock)
1865
build_tree(revision_tree, target, source, hardlink=True)
1867
self.addCleanup(target.unlock)
1868
self.assertEqual([], list(target.iter_changes(revision_tree)))
1869
self.assertTrue(source.is_executable('file1-id'))
1871
def test_case_insensitive_build_tree_inventory(self):
1872
if (tests.CaseInsensitiveFilesystemFeature.available()
1873
or tests.CaseInsCasePresFilenameFeature.available()):
1874
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1875
source = self.make_branch_and_tree('source')
1876
self.build_tree(['source/file', 'source/FILE'])
1877
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1878
source.commit('added files')
1879
# Don't try this at home, kids!
1880
# Force the tree to report that it is case insensitive
1881
target = self.make_branch_and_tree('target')
1882
target.case_sensitive = False
1883
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1884
self.assertEqual('file.moved', target.id2path('lower-id'))
1885
self.assertEqual('FILE', target.id2path('upper-id'))
1888
class TestCommitTransform(tests.TestCaseWithTransport):
1890
def get_branch(self):
1891
tree = self.make_branch_and_tree('tree')
1893
self.addCleanup(tree.unlock)
1894
tree.commit('empty commit')
1897
def get_branch_and_transform(self):
1898
branch = self.get_branch()
1899
tt = TransformPreview(branch.basis_tree())
1900
self.addCleanup(tt.finalize)
1903
def test_commit_wrong_basis(self):
1904
branch = self.get_branch()
1905
basis = branch.repository.revision_tree(
1906
_mod_revision.NULL_REVISION)
1907
tt = TransformPreview(basis)
1908
self.addCleanup(tt.finalize)
1909
e = self.assertRaises(ValueError, tt.commit, branch, '')
1910
self.assertEqual('TreeTransform not based on branch basis: null:',
1913
def test_empy_commit(self):
1914
branch, tt = self.get_branch_and_transform()
1915
rev = tt.commit(branch, 'my message')
1916
self.assertEqual(2, branch.revno())
1917
repo = branch.repository
1918
self.assertEqual('my message', repo.get_revision(rev).message)
1920
def test_merge_parents(self):
1921
branch, tt = self.get_branch_and_transform()
1922
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
1923
self.assertEqual(['rev1b', 'rev1c'],
1924
branch.basis_tree().get_parent_ids()[1:])
1926
def test_first_commit(self):
1927
branch = self.make_branch('branch')
1929
self.addCleanup(branch.unlock)
1930
tt = TransformPreview(branch.basis_tree())
1931
self.addCleanup(tt.finalize)
1932
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
1933
rev = tt.commit(branch, 'my message')
1934
self.assertEqual([], branch.basis_tree().get_parent_ids())
1935
self.assertNotEqual(_mod_revision.NULL_REVISION,
1936
branch.last_revision())
1938
def test_first_commit_with_merge_parents(self):
1939
branch = self.make_branch('branch')
1941
self.addCleanup(branch.unlock)
1942
tt = TransformPreview(branch.basis_tree())
1943
self.addCleanup(tt.finalize)
1944
e = self.assertRaises(ValueError, tt.commit, branch,
1945
'my message', ['rev1b-id'])
1946
self.assertEqual('Cannot supply merge parents for first commit.',
1948
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
1950
def test_add_files(self):
1951
branch, tt = self.get_branch_and_transform()
1952
tt.new_file('file', tt.root, 'contents', 'file-id')
1953
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
1954
if SymlinkFeature.available():
1955
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
1956
rev = tt.commit(branch, 'message')
1957
tree = branch.basis_tree()
1958
self.assertEqual('file', tree.id2path('file-id'))
1959
self.assertEqual('contents', tree.get_file_text('file-id'))
1960
self.assertEqual('dir', tree.id2path('dir-id'))
1961
if SymlinkFeature.available():
1962
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
1963
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
1965
def test_add_unversioned(self):
1966
branch, tt = self.get_branch_and_transform()
1967
tt.new_file('file', tt.root, 'contents')
1968
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
1969
'message', strict=True)
1971
def test_modify_strict(self):
1972
branch, tt = self.get_branch_and_transform()
1973
tt.new_file('file', tt.root, 'contents', 'file-id')
1974
tt.commit(branch, 'message', strict=True)
1975
tt = TransformPreview(branch.basis_tree())
1976
self.addCleanup(tt.finalize)
1977
trans_id = tt.trans_id_file_id('file-id')
1978
tt.delete_contents(trans_id)
1979
tt.create_file('contents', trans_id)
1980
tt.commit(branch, 'message', strict=True)
1982
def test_commit_malformed(self):
1983
"""Committing a malformed transform should raise an exception.
1985
In this case, we are adding a file without adding its parent.
1987
branch, tt = self.get_branch_and_transform()
1988
parent_id = tt.trans_id_file_id('parent-id')
1989
tt.new_file('file', parent_id, 'contents', 'file-id')
1990
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
1994
class MockTransform(object):
1996
def has_named_child(self, by_parent, parent_id, name):
1997
for child_id in by_parent[parent_id]:
2001
elif name == "name.~%s~" % child_id:
2006
class MockEntry(object):
2008
object.__init__(self)
2012
class TestGetBackupName(TestCase):
2013
def test_get_backup_name(self):
2014
tt = MockTransform()
2015
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2016
self.assertEqual(name, 'name.~1~')
2017
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2018
self.assertEqual(name, 'name.~2~')
2019
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2020
self.assertEqual(name, 'name.~1~')
2021
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2022
self.assertEqual(name, 'name.~1~')
2023
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2024
self.assertEqual(name, 'name.~4~')
2027
class TestFileMover(tests.TestCaseWithTransport):
2029
def test_file_mover(self):
2030
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2031
mover = _FileMover()
2032
mover.rename('a', 'q')
2033
self.failUnlessExists('q')
2034
self.failIfExists('a')
2035
self.failUnlessExists('q/b')
2036
self.failUnlessExists('c')
2037
self.failUnlessExists('c/d')
2039
def test_pre_delete_rollback(self):
2040
self.build_tree(['a/'])
2041
mover = _FileMover()
2042
mover.pre_delete('a', 'q')
2043
self.failUnlessExists('q')
2044
self.failIfExists('a')
2046
self.failIfExists('q')
2047
self.failUnlessExists('a')
2049
def test_apply_deletions(self):
2050
self.build_tree(['a/', 'b/'])
2051
mover = _FileMover()
2052
mover.pre_delete('a', 'q')
2053
mover.pre_delete('b', 'r')
2054
self.failUnlessExists('q')
2055
self.failUnlessExists('r')
2056
self.failIfExists('a')
2057
self.failIfExists('b')
2058
mover.apply_deletions()
2059
self.failIfExists('q')
2060
self.failIfExists('r')
2061
self.failIfExists('a')
2062
self.failIfExists('b')
2064
def test_file_mover_rollback(self):
2065
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2066
mover = _FileMover()
2067
mover.rename('c/d', 'c/f')
2068
mover.rename('c/e', 'c/d')
2070
mover.rename('a', 'c')
2071
except errors.FileExists, e:
2073
self.failUnlessExists('a')
2074
self.failUnlessExists('c/d')
2077
class Bogus(Exception):
2081
class TestTransformRollback(tests.TestCaseWithTransport):
2083
class ExceptionFileMover(_FileMover):
2085
def __init__(self, bad_source=None, bad_target=None):
2086
_FileMover.__init__(self)
2087
self.bad_source = bad_source
2088
self.bad_target = bad_target
2090
def rename(self, source, target):
2091
if (self.bad_source is not None and
2092
source.endswith(self.bad_source)):
2094
elif (self.bad_target is not None and
2095
target.endswith(self.bad_target)):
2098
_FileMover.rename(self, source, target)
2100
def test_rollback_rename(self):
2101
tree = self.make_branch_and_tree('.')
2102
self.build_tree(['a/', 'a/b'])
2103
tt = TreeTransform(tree)
2104
self.addCleanup(tt.finalize)
2105
a_id = tt.trans_id_tree_path('a')
2106
tt.adjust_path('c', tt.root, a_id)
2107
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2108
self.assertRaises(Bogus, tt.apply,
2109
_mover=self.ExceptionFileMover(bad_source='a'))
2110
self.failUnlessExists('a')
2111
self.failUnlessExists('a/b')
2113
self.failUnlessExists('c')
2114
self.failUnlessExists('c/d')
2116
def test_rollback_rename_into_place(self):
2117
tree = self.make_branch_and_tree('.')
2118
self.build_tree(['a/', 'a/b'])
2119
tt = TreeTransform(tree)
2120
self.addCleanup(tt.finalize)
2121
a_id = tt.trans_id_tree_path('a')
2122
tt.adjust_path('c', tt.root, a_id)
2123
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2124
self.assertRaises(Bogus, tt.apply,
2125
_mover=self.ExceptionFileMover(bad_target='c/d'))
2126
self.failUnlessExists('a')
2127
self.failUnlessExists('a/b')
2129
self.failUnlessExists('c')
2130
self.failUnlessExists('c/d')
2132
def test_rollback_deletion(self):
2133
tree = self.make_branch_and_tree('.')
2134
self.build_tree(['a/', 'a/b'])
2135
tt = TreeTransform(tree)
2136
self.addCleanup(tt.finalize)
2137
a_id = tt.trans_id_tree_path('a')
2138
tt.delete_contents(a_id)
2139
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2140
self.assertRaises(Bogus, tt.apply,
2141
_mover=self.ExceptionFileMover(bad_target='d'))
2142
self.failUnlessExists('a')
2143
self.failUnlessExists('a/b')
2145
def test_resolve_no_parent(self):
2146
wt = self.make_branch_and_tree('.')
2147
tt = TreeTransform(wt)
2148
self.addCleanup(tt.finalize)
2149
parent = tt.trans_id_file_id('parent-id')
2150
tt.new_file('file', parent, 'Contents')
2151
resolve_conflicts(tt)
2154
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2155
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2157
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2158
('', ''), ('directory', 'directory'), (False, None))
2161
class TestTransformPreview(tests.TestCaseWithTransport):
2163
def create_tree(self):
2164
tree = self.make_branch_and_tree('.')
2165
self.build_tree_contents([('a', 'content 1')])
2166
tree.set_root_id('TREE_ROOT')
2167
tree.add('a', 'a-id')
2168
tree.commit('rev1', rev_id='rev1')
2169
return tree.branch.repository.revision_tree('rev1')
2171
def get_empty_preview(self):
2172
repository = self.make_repository('repo')
2173
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2174
preview = TransformPreview(tree)
2175
self.addCleanup(preview.finalize)
2178
def test_transform_preview(self):
2179
revision_tree = self.create_tree()
2180
preview = TransformPreview(revision_tree)
2181
self.addCleanup(preview.finalize)
2183
def test_transform_preview_tree(self):
2184
revision_tree = self.create_tree()
2185
preview = TransformPreview(revision_tree)
2186
self.addCleanup(preview.finalize)
2187
preview.get_preview_tree()
2189
def test_transform_new_file(self):
2190
revision_tree = self.create_tree()
2191
preview = TransformPreview(revision_tree)
2192
self.addCleanup(preview.finalize)
2193
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2194
preview_tree = preview.get_preview_tree()
2195
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2197
preview_tree.get_file('file2-id').read(), 'content B\n')
2199
def test_diff_preview_tree(self):
2200
revision_tree = self.create_tree()
2201
preview = TransformPreview(revision_tree)
2202
self.addCleanup(preview.finalize)
2203
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2204
preview_tree = preview.get_preview_tree()
2206
show_diff_trees(revision_tree, preview_tree, out)
2207
lines = out.getvalue().splitlines()
2208
self.assertEqual(lines[0], "=== added file 'file2'")
2209
# 3 lines of diff administrivia
2210
self.assertEqual(lines[4], "+content B")
2212
def test_transform_conflicts(self):
2213
revision_tree = self.create_tree()
2214
preview = TransformPreview(revision_tree)
2215
self.addCleanup(preview.finalize)
2216
preview.new_file('a', preview.root, 'content 2')
2217
resolve_conflicts(preview)
2218
trans_id = preview.trans_id_file_id('a-id')
2219
self.assertEqual('a.moved', preview.final_name(trans_id))
2221
def get_tree_and_preview_tree(self):
2222
revision_tree = self.create_tree()
2223
preview = TransformPreview(revision_tree)
2224
self.addCleanup(preview.finalize)
2225
a_trans_id = preview.trans_id_file_id('a-id')
2226
preview.delete_contents(a_trans_id)
2227
preview.create_file('b content', a_trans_id)
2228
preview_tree = preview.get_preview_tree()
2229
return revision_tree, preview_tree
2231
def test_iter_changes(self):
2232
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2233
root = revision_tree.inventory.root.file_id
2234
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2235
(root, root), ('a', 'a'), ('file', 'file'),
2237
list(preview_tree.iter_changes(revision_tree)))
2239
def test_include_unchanged_succeeds(self):
2240
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2241
changes = preview_tree.iter_changes(revision_tree,
2242
include_unchanged=True)
2243
root = revision_tree.inventory.root.file_id
2245
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2247
def test_specific_files(self):
2248
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2249
changes = preview_tree.iter_changes(revision_tree,
2250
specific_files=[''])
2251
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2253
def test_want_unversioned(self):
2254
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2255
changes = preview_tree.iter_changes(revision_tree,
2256
want_unversioned=True)
2257
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2259
def test_ignore_extra_trees_no_specific_files(self):
2260
# extra_trees is harmless without specific_files, so we'll silently
2261
# accept it, even though we won't use it.
2262
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2263
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2265
def test_ignore_require_versioned_no_specific_files(self):
2266
# require_versioned is meaningless without specific_files.
2267
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2268
preview_tree.iter_changes(revision_tree, require_versioned=False)
2270
def test_ignore_pb(self):
2271
# pb could be supported, but TT.iter_changes doesn't support it.
2272
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2273
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2275
def test_kind(self):
2276
revision_tree = self.create_tree()
2277
preview = TransformPreview(revision_tree)
2278
self.addCleanup(preview.finalize)
2279
preview.new_file('file', preview.root, 'contents', 'file-id')
2280
preview.new_directory('directory', preview.root, 'dir-id')
2281
preview_tree = preview.get_preview_tree()
2282
self.assertEqual('file', preview_tree.kind('file-id'))
2283
self.assertEqual('directory', preview_tree.kind('dir-id'))
2285
def test_get_file_mtime(self):
2286
preview = self.get_empty_preview()
2287
file_trans_id = preview.new_file('file', preview.root, 'contents',
2289
limbo_path = preview._limbo_name(file_trans_id)
2290
preview_tree = preview.get_preview_tree()
2291
self.assertEqual(os.stat(limbo_path).st_mtime,
2292
preview_tree.get_file_mtime('file-id'))
2294
def test_get_file_mtime_renamed(self):
2295
work_tree = self.make_branch_and_tree('tree')
2296
self.build_tree(['tree/file'])
2297
work_tree.add('file', 'file-id')
2298
preview = TransformPreview(work_tree)
2299
self.addCleanup(preview.finalize)
2300
file_trans_id = preview.trans_id_tree_file_id('file-id')
2301
preview.adjust_path('renamed', preview.root, file_trans_id)
2302
preview_tree = preview.get_preview_tree()
2303
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2304
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2306
def test_get_file(self):
2307
preview = self.get_empty_preview()
2308
preview.new_file('file', preview.root, 'contents', 'file-id')
2309
preview_tree = preview.get_preview_tree()
2310
tree_file = preview_tree.get_file('file-id')
2312
self.assertEqual('contents', tree_file.read())
2316
def test_get_symlink_target(self):
2317
self.requireFeature(SymlinkFeature)
2318
preview = self.get_empty_preview()
2319
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2320
preview_tree = preview.get_preview_tree()
2321
self.assertEqual('target',
2322
preview_tree.get_symlink_target('symlink-id'))
2324
def test_all_file_ids(self):
2325
tree = self.make_branch_and_tree('tree')
2326
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2327
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2328
preview = TransformPreview(tree)
2329
self.addCleanup(preview.finalize)
2330
preview.unversion_file(preview.trans_id_file_id('b-id'))
2331
c_trans_id = preview.trans_id_file_id('c-id')
2332
preview.unversion_file(c_trans_id)
2333
preview.version_file('c-id', c_trans_id)
2334
preview_tree = preview.get_preview_tree()
2335
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2336
preview_tree.all_file_ids())
2338
def test_path2id_deleted_unchanged(self):
2339
tree = self.make_branch_and_tree('tree')
2340
self.build_tree(['tree/unchanged', 'tree/deleted'])
2341
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2342
preview = TransformPreview(tree)
2343
self.addCleanup(preview.finalize)
2344
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2345
preview_tree = preview.get_preview_tree()
2346
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2347
self.assertIs(None, preview_tree.path2id('deleted'))
2349
def test_path2id_created(self):
2350
tree = self.make_branch_and_tree('tree')
2351
self.build_tree(['tree/unchanged'])
2352
tree.add(['unchanged'], ['unchanged-id'])
2353
preview = TransformPreview(tree)
2354
self.addCleanup(preview.finalize)
2355
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2356
'contents', 'new-id')
2357
preview_tree = preview.get_preview_tree()
2358
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2360
def test_path2id_moved(self):
2361
tree = self.make_branch_and_tree('tree')
2362
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2363
tree.add(['old_parent', 'old_parent/child'],
2364
['old_parent-id', 'child-id'])
2365
preview = TransformPreview(tree)
2366
self.addCleanup(preview.finalize)
2367
new_parent = preview.new_directory('new_parent', preview.root,
2369
preview.adjust_path('child', new_parent,
2370
preview.trans_id_file_id('child-id'))
2371
preview_tree = preview.get_preview_tree()
2372
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2373
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2375
def test_path2id_renamed_parent(self):
2376
tree = self.make_branch_and_tree('tree')
2377
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2378
tree.add(['old_name', 'old_name/child'],
2379
['parent-id', 'child-id'])
2380
preview = TransformPreview(tree)
2381
self.addCleanup(preview.finalize)
2382
preview.adjust_path('new_name', preview.root,
2383
preview.trans_id_file_id('parent-id'))
2384
preview_tree = preview.get_preview_tree()
2385
self.assertIs(None, preview_tree.path2id('old_name/child'))
2386
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2388
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2389
preview_tree = tt.get_preview_tree()
2390
preview_result = list(preview_tree.iter_entries_by_dir(
2394
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2395
self.assertEqual(actual_result, preview_result)
2397
def test_iter_entries_by_dir_new(self):
2398
tree = self.make_branch_and_tree('tree')
2399
tt = TreeTransform(tree)
2400
tt.new_file('new', tt.root, 'contents', 'new-id')
2401
self.assertMatchingIterEntries(tt)
2403
def test_iter_entries_by_dir_deleted(self):
2404
tree = self.make_branch_and_tree('tree')
2405
self.build_tree(['tree/deleted'])
2406
tree.add('deleted', 'deleted-id')
2407
tt = TreeTransform(tree)
2408
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2409
self.assertMatchingIterEntries(tt)
2411
def test_iter_entries_by_dir_unversioned(self):
2412
tree = self.make_branch_and_tree('tree')
2413
self.build_tree(['tree/removed'])
2414
tree.add('removed', 'removed-id')
2415
tt = TreeTransform(tree)
2416
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2417
self.assertMatchingIterEntries(tt)
2419
def test_iter_entries_by_dir_moved(self):
2420
tree = self.make_branch_and_tree('tree')
2421
self.build_tree(['tree/moved', 'tree/new_parent/'])
2422
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2423
tt = TreeTransform(tree)
2424
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2425
tt.trans_id_file_id('moved-id'))
2426
self.assertMatchingIterEntries(tt)
2428
def test_iter_entries_by_dir_specific_file_ids(self):
2429
tree = self.make_branch_and_tree('tree')
2430
tree.set_root_id('tree-root-id')
2431
self.build_tree(['tree/parent/', 'tree/parent/child'])
2432
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2433
tt = TreeTransform(tree)
2434
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2436
def test_symlink_content_summary(self):
2437
self.requireFeature(SymlinkFeature)
2438
preview = self.get_empty_preview()
2439
preview.new_symlink('path', preview.root, 'target', 'path-id')
2440
summary = preview.get_preview_tree().path_content_summary('path')
2441
self.assertEqual(('symlink', None, None, 'target'), summary)
2443
def test_missing_content_summary(self):
2444
preview = self.get_empty_preview()
2445
summary = preview.get_preview_tree().path_content_summary('path')
2446
self.assertEqual(('missing', None, None, None), summary)
2448
def test_deleted_content_summary(self):
2449
tree = self.make_branch_and_tree('tree')
2450
self.build_tree(['tree/path/'])
2452
preview = TransformPreview(tree)
2453
self.addCleanup(preview.finalize)
2454
preview.delete_contents(preview.trans_id_tree_path('path'))
2455
summary = preview.get_preview_tree().path_content_summary('path')
2456
self.assertEqual(('missing', None, None, None), summary)
2458
def test_file_content_summary_executable(self):
2459
preview = self.get_empty_preview()
2460
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2461
preview.set_executability(True, path_id)
2462
summary = preview.get_preview_tree().path_content_summary('path')
2463
self.assertEqual(4, len(summary))
2464
self.assertEqual('file', summary[0])
2465
# size must be known
2466
self.assertEqual(len('contents'), summary[1])
2468
self.assertEqual(True, summary[2])
2469
# will not have hash (not cheap to determine)
2470
self.assertIs(None, summary[3])
2472
def test_change_executability(self):
2473
tree = self.make_branch_and_tree('tree')
2474
self.build_tree(['tree/path'])
2476
preview = TransformPreview(tree)
2477
self.addCleanup(preview.finalize)
2478
path_id = preview.trans_id_tree_path('path')
2479
preview.set_executability(True, path_id)
2480
summary = preview.get_preview_tree().path_content_summary('path')
2481
self.assertEqual(True, summary[2])
2483
def test_file_content_summary_non_exec(self):
2484
preview = self.get_empty_preview()
2485
preview.new_file('path', preview.root, 'contents', 'path-id')
2486
summary = preview.get_preview_tree().path_content_summary('path')
2487
self.assertEqual(4, len(summary))
2488
self.assertEqual('file', summary[0])
2489
# size must be known
2490
self.assertEqual(len('contents'), summary[1])
2492
self.assertEqual(False, summary[2])
2493
# will not have hash (not cheap to determine)
2494
self.assertIs(None, summary[3])
2496
def test_dir_content_summary(self):
2497
preview = self.get_empty_preview()
2498
preview.new_directory('path', preview.root, 'path-id')
2499
summary = preview.get_preview_tree().path_content_summary('path')
2500
self.assertEqual(('directory', None, None, None), summary)
2502
def test_tree_content_summary(self):
2503
preview = self.get_empty_preview()
2504
path = preview.new_directory('path', preview.root, 'path-id')
2505
preview.set_tree_reference('rev-1', path)
2506
summary = preview.get_preview_tree().path_content_summary('path')
2507
self.assertEqual(4, len(summary))
2508
self.assertEqual('tree-reference', summary[0])
2510
def test_annotate(self):
2511
tree = self.make_branch_and_tree('tree')
2512
self.build_tree_contents([('tree/file', 'a\n')])
2513
tree.add('file', 'file-id')
2514
tree.commit('a', rev_id='one')
2515
self.build_tree_contents([('tree/file', 'a\nb\n')])
2516
preview = TransformPreview(tree)
2517
self.addCleanup(preview.finalize)
2518
file_trans_id = preview.trans_id_file_id('file-id')
2519
preview.delete_contents(file_trans_id)
2520
preview.create_file('a\nb\nc\n', file_trans_id)
2521
preview_tree = preview.get_preview_tree()
2527
annotation = preview_tree.annotate_iter('file-id', 'me:')
2528
self.assertEqual(expected, annotation)
2530
def test_annotate_missing(self):
2531
preview = self.get_empty_preview()
2532
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2533
preview_tree = preview.get_preview_tree()
2539
annotation = preview_tree.annotate_iter('file-id', 'me:')
2540
self.assertEqual(expected, annotation)
2542
def test_annotate_rename(self):
2543
tree = self.make_branch_and_tree('tree')
2544
self.build_tree_contents([('tree/file', 'a\n')])
2545
tree.add('file', 'file-id')
2546
tree.commit('a', rev_id='one')
2547
preview = TransformPreview(tree)
2548
self.addCleanup(preview.finalize)
2549
file_trans_id = preview.trans_id_file_id('file-id')
2550
preview.adjust_path('newname', preview.root, file_trans_id)
2551
preview_tree = preview.get_preview_tree()
2555
annotation = preview_tree.annotate_iter('file-id', 'me:')
2556
self.assertEqual(expected, annotation)
2558
def test_annotate_deleted(self):
2559
tree = self.make_branch_and_tree('tree')
2560
self.build_tree_contents([('tree/file', 'a\n')])
2561
tree.add('file', 'file-id')
2562
tree.commit('a', rev_id='one')
2563
self.build_tree_contents([('tree/file', 'a\nb\n')])
2564
preview = TransformPreview(tree)
2565
self.addCleanup(preview.finalize)
2566
file_trans_id = preview.trans_id_file_id('file-id')
2567
preview.delete_contents(file_trans_id)
2568
preview_tree = preview.get_preview_tree()
2569
annotation = preview_tree.annotate_iter('file-id', 'me:')
2570
self.assertIs(None, annotation)
2572
def test_stored_kind(self):
2573
preview = self.get_empty_preview()
2574
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2575
preview_tree = preview.get_preview_tree()
2576
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2578
def test_is_executable(self):
2579
preview = self.get_empty_preview()
2580
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2581
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2582
preview_tree = preview.get_preview_tree()
2583
self.assertEqual(True, preview_tree.is_executable('file-id'))
2585
def test_get_set_parent_ids(self):
2586
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2587
self.assertEqual([], preview_tree.get_parent_ids())
2588
preview_tree.set_parent_ids(['rev-1'])
2589
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2591
def test_plan_file_merge(self):
2592
work_a = self.make_branch_and_tree('wta')
2593
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2594
work_a.add('file', 'file-id')
2595
base_id = work_a.commit('base version')
2596
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2597
preview = TransformPreview(work_a)
2598
self.addCleanup(preview.finalize)
2599
trans_id = preview.trans_id_file_id('file-id')
2600
preview.delete_contents(trans_id)
2601
preview.create_file('b\nc\nd\ne\n', trans_id)
2602
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2603
tree_a = preview.get_preview_tree()
2604
tree_a.set_parent_ids([base_id])
2606
('killed-a', 'a\n'),
2607
('killed-b', 'b\n'),
2608
('unchanged', 'c\n'),
2609
('unchanged', 'd\n'),
2612
], list(tree_a.plan_file_merge('file-id', tree_b)))
2614
def test_plan_file_merge_revision_tree(self):
2615
work_a = self.make_branch_and_tree('wta')
2616
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2617
work_a.add('file', 'file-id')
2618
base_id = work_a.commit('base version')
2619
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2620
preview = TransformPreview(work_a.basis_tree())
2621
self.addCleanup(preview.finalize)
2622
trans_id = preview.trans_id_file_id('file-id')
2623
preview.delete_contents(trans_id)
2624
preview.create_file('b\nc\nd\ne\n', trans_id)
2625
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2626
tree_a = preview.get_preview_tree()
2627
tree_a.set_parent_ids([base_id])
2629
('killed-a', 'a\n'),
2630
('killed-b', 'b\n'),
2631
('unchanged', 'c\n'),
2632
('unchanged', 'd\n'),
2635
], list(tree_a.plan_file_merge('file-id', tree_b)))
2637
def test_walkdirs(self):
2638
preview = self.get_empty_preview()
2639
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2640
# FIXME: new_directory should mark root.
2641
preview.adjust_path('', ROOT_PARENT, root)
2642
preview_tree = preview.get_preview_tree()
2643
file_trans_id = preview.new_file('a', preview.root, 'contents',
2645
expected = [(('', 'tree-root'),
2646
[('a', 'a', 'file', None, 'a-id', 'file')])]
2647
self.assertEqual(expected, list(preview_tree.walkdirs()))
2649
def test_extras(self):
2650
work_tree = self.make_branch_and_tree('tree')
2651
self.build_tree(['tree/removed-file', 'tree/existing-file',
2652
'tree/not-removed-file'])
2653
work_tree.add(['removed-file', 'not-removed-file'])
2654
preview = TransformPreview(work_tree)
2655
self.addCleanup(preview.finalize)
2656
preview.new_file('new-file', preview.root, 'contents')
2657
preview.new_file('new-versioned-file', preview.root, 'contents',
2659
tree = preview.get_preview_tree()
2660
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2661
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2664
def test_merge_into_preview(self):
2665
work_tree = self.make_branch_and_tree('tree')
2666
self.build_tree_contents([('tree/file','b\n')])
2667
work_tree.add('file', 'file-id')
2668
work_tree.commit('first commit')
2669
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2670
self.build_tree_contents([('child/file','b\nc\n')])
2671
child_tree.commit('child commit')
2672
child_tree.lock_write()
2673
self.addCleanup(child_tree.unlock)
2674
work_tree.lock_write()
2675
self.addCleanup(work_tree.unlock)
2676
preview = TransformPreview(work_tree)
2677
self.addCleanup(preview.finalize)
2678
file_trans_id = preview.trans_id_file_id('file-id')
2679
preview.delete_contents(file_trans_id)
2680
preview.create_file('a\nb\n', file_trans_id)
2681
pb = progress.DummyProgress()
2682
preview_tree = preview.get_preview_tree()
2683
merger = Merger.from_revision_ids(pb, preview_tree,
2684
child_tree.branch.last_revision(),
2685
other_branch=child_tree.branch,
2686
tree_branch=work_tree.branch)
2687
merger.merge_type = Merge3Merger
2688
tt = merger.make_merger().make_preview_transform()
2689
self.addCleanup(tt.finalize)
2690
final_tree = tt.get_preview_tree()
2691
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2693
def test_merge_preview_into_workingtree(self):
2694
tree = self.make_branch_and_tree('tree')
2695
tree.set_root_id('TREE_ROOT')
2696
tt = TransformPreview(tree)
2697
self.addCleanup(tt.finalize)
2698
tt.new_file('name', tt.root, 'content', 'file-id')
2699
tree2 = self.make_branch_and_tree('tree2')
2700
tree2.set_root_id('TREE_ROOT')
2701
pb = progress.DummyProgress()
2702
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2703
pb, tree.basis_tree())
2704
merger.merge_type = Merge3Merger
2707
def test_merge_preview_into_workingtree_handles_conflicts(self):
2708
tree = self.make_branch_and_tree('tree')
2709
self.build_tree_contents([('tree/foo', 'bar')])
2710
tree.add('foo', 'foo-id')
2712
tt = TransformPreview(tree)
2713
self.addCleanup(tt.finalize)
2714
trans_id = tt.trans_id_file_id('foo-id')
2715
tt.delete_contents(trans_id)
2716
tt.create_file('baz', trans_id)
2717
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2718
self.build_tree_contents([('tree2/foo', 'qux')])
2719
pb = progress.DummyProgress()
2720
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2721
pb, tree.basis_tree())
2722
merger.merge_type = Merge3Merger
2725
def test_is_executable(self):
2726
tree = self.make_branch_and_tree('tree')
2727
preview = TransformPreview(tree)
2728
self.addCleanup(preview.finalize)
2729
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2730
preview_tree = preview.get_preview_tree()
2731
self.assertEqual(False, preview_tree.is_executable('baz-id',
2733
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2735
def test_commit_preview_tree(self):
2736
tree = self.make_branch_and_tree('tree')
2737
rev_id = tree.commit('rev1')
2738
tree.branch.lock_write()
2739
self.addCleanup(tree.branch.unlock)
2740
tt = TransformPreview(tree)
2741
tt.new_file('file', tt.root, 'contents', 'file_id')
2742
self.addCleanup(tt.finalize)
2743
preview = tt.get_preview_tree()
2744
preview.set_parent_ids([rev_id])
2745
builder = tree.branch.get_commit_builder([rev_id])
2746
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2747
builder.finish_inventory()
2748
rev2_id = builder.commit('rev2')
2749
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2750
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2752
def test_ascii_limbo_paths(self):
2753
self.requireFeature(tests.UnicodeFilenameFeature)
2754
branch = self.make_branch('any')
2755
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2756
tt = TransformPreview(tree)
2757
self.addCleanup(tt.finalize)
2758
foo_id = tt.new_directory('', ROOT_PARENT)
2759
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2760
limbo_path = tt._limbo_name(bar_id)
2761
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2764
class FakeSerializer(object):
2765
"""Serializer implementation that simply returns the input.
2767
The input is returned in the order used by pack.ContainerPushParser.
2770
def bytes_record(bytes, names):
2774
class TestSerializeTransform(tests.TestCaseWithTransport):
2776
_test_needs_features = [tests.UnicodeFilenameFeature]
2778
def get_preview(self, tree=None):
2780
tree = self.make_branch_and_tree('tree')
2781
tt = TransformPreview(tree)
2782
self.addCleanup(tt.finalize)
2785
def assertSerializesTo(self, expected, tt):
2786
records = list(tt.serialize(FakeSerializer()))
2787
self.assertEqual(expected, records)
2790
def default_attribs():
2795
'_new_executability': {},
2797
'_tree_path_ids': {'': 'new-0'},
2799
'_removed_contents': [],
2800
'_non_present_ids': {},
2803
def make_records(self, attribs, contents):
2805
(((('attribs'),),), bencode.bencode(attribs))]
2806
records.extend([(((n, k),), c) for n, k, c in contents])
2809
def creation_records(self):
2810
attribs = self.default_attribs()
2811
attribs['_id_number'] = 3
2812
attribs['_new_name'] = {
2813
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2814
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2815
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2816
attribs['_new_executability'] = {'new-1': 1}
2818
('new-1', 'file', 'i 1\nbar\n'),
2819
('new-2', 'directory', ''),
2821
return self.make_records(attribs, contents)
2823
def test_serialize_creation(self):
2824
tt = self.get_preview()
2825
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2826
tt.new_directory('qux', tt.root, 'quxx')
2827
self.assertSerializesTo(self.creation_records(), tt)
2829
def test_deserialize_creation(self):
2830
tt = self.get_preview()
2831
tt.deserialize(iter(self.creation_records()))
2832
self.assertEqual(3, tt._id_number)
2833
self.assertEqual({'new-1': u'foo\u1234',
2834
'new-2': 'qux'}, tt._new_name)
2835
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2836
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2837
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2838
self.assertEqual({'new-1': True}, tt._new_executability)
2839
self.assertEqual({'new-1': 'file',
2840
'new-2': 'directory'}, tt._new_contents)
2841
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2843
foo_content = foo_limbo.read()
2846
self.assertEqual('bar', foo_content)
2848
def symlink_creation_records(self):
2849
attribs = self.default_attribs()
2850
attribs['_id_number'] = 2
2851
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2852
attribs['_new_parent'] = {'new-1': 'new-0'}
2853
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2854
return self.make_records(attribs, contents)
2856
def test_serialize_symlink_creation(self):
2857
self.requireFeature(tests.SymlinkFeature)
2858
tt = self.get_preview()
2859
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2860
self.assertSerializesTo(self.symlink_creation_records(), tt)
2862
def test_deserialize_symlink_creation(self):
2863
self.requireFeature(tests.SymlinkFeature)
2864
tt = self.get_preview()
2865
tt.deserialize(iter(self.symlink_creation_records()))
2866
abspath = tt._limbo_name('new-1')
2867
foo_content = osutils.readlink(abspath)
2868
self.assertEqual(u'bar\u1234', foo_content)
2870
def make_destruction_preview(self):
2871
tree = self.make_branch_and_tree('.')
2872
self.build_tree([u'foo\u1234', 'bar'])
2873
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2874
return self.get_preview(tree)
2876
def destruction_records(self):
2877
attribs = self.default_attribs()
2878
attribs['_id_number'] = 3
2879
attribs['_removed_id'] = ['new-1']
2880
attribs['_removed_contents'] = ['new-2']
2881
attribs['_tree_path_ids'] = {
2883
u'foo\u1234'.encode('utf-8'): 'new-1',
2886
return self.make_records(attribs, [])
2888
def test_serialize_destruction(self):
2889
tt = self.make_destruction_preview()
2890
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2891
tt.unversion_file(foo_trans_id)
2892
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2893
tt.delete_contents(bar_trans_id)
2894
self.assertSerializesTo(self.destruction_records(), tt)
2896
def test_deserialize_destruction(self):
2897
tt = self.make_destruction_preview()
2898
tt.deserialize(iter(self.destruction_records()))
2899
self.assertEqual({u'foo\u1234': 'new-1',
2901
'': tt.root}, tt._tree_path_ids)
2902
self.assertEqual({'new-1': u'foo\u1234',
2904
tt.root: ''}, tt._tree_id_paths)
2905
self.assertEqual(set(['new-1']), tt._removed_id)
2906
self.assertEqual(set(['new-2']), tt._removed_contents)
2908
def missing_records(self):
2909
attribs = self.default_attribs()
2910
attribs['_id_number'] = 2
2911
attribs['_non_present_ids'] = {
2913
return self.make_records(attribs, [])
2915
def test_serialize_missing(self):
2916
tt = self.get_preview()
2917
boo_trans_id = tt.trans_id_file_id('boo')
2918
self.assertSerializesTo(self.missing_records(), tt)
2920
def test_deserialize_missing(self):
2921
tt = self.get_preview()
2922
tt.deserialize(iter(self.missing_records()))
2923
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2925
def make_modification_preview(self):
2926
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2927
LINES_TWO = 'z\nbb\nx\ndd\n'
2928
tree = self.make_branch_and_tree('tree')
2929
self.build_tree_contents([('tree/file', LINES_ONE)])
2930
tree.add('file', 'file-id')
2931
return self.get_preview(tree), LINES_TWO
2933
def modification_records(self):
2934
attribs = self.default_attribs()
2935
attribs['_id_number'] = 2
2936
attribs['_tree_path_ids'] = {
2939
attribs['_removed_contents'] = ['new-1']
2940
contents = [('new-1', 'file',
2941
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2942
return self.make_records(attribs, contents)
2944
def test_serialize_modification(self):
2945
tt, LINES = self.make_modification_preview()
2946
trans_id = tt.trans_id_file_id('file-id')
2947
tt.delete_contents(trans_id)
2948
tt.create_file(LINES, trans_id)
2949
self.assertSerializesTo(self.modification_records(), tt)
2951
def test_deserialize_modification(self):
2952
tt, LINES = self.make_modification_preview()
2953
tt.deserialize(iter(self.modification_records()))
2954
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2956
def make_kind_change_preview(self):
2957
LINES = 'a\nb\nc\nd\n'
2958
tree = self.make_branch_and_tree('tree')
2959
self.build_tree(['tree/foo/'])
2960
tree.add('foo', 'foo-id')
2961
return self.get_preview(tree), LINES
2963
def kind_change_records(self):
2964
attribs = self.default_attribs()
2965
attribs['_id_number'] = 2
2966
attribs['_tree_path_ids'] = {
2969
attribs['_removed_contents'] = ['new-1']
2970
contents = [('new-1', 'file',
2971
'i 4\na\nb\nc\nd\n\n')]
2972
return self.make_records(attribs, contents)
2974
def test_serialize_kind_change(self):
2975
tt, LINES = self.make_kind_change_preview()
2976
trans_id = tt.trans_id_file_id('foo-id')
2977
tt.delete_contents(trans_id)
2978
tt.create_file(LINES, trans_id)
2979
self.assertSerializesTo(self.kind_change_records(), tt)
2981
def test_deserialize_kind_change(self):
2982
tt, LINES = self.make_kind_change_preview()
2983
tt.deserialize(iter(self.kind_change_records()))
2984
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2986
def make_add_contents_preview(self):
2987
LINES = 'a\nb\nc\nd\n'
2988
tree = self.make_branch_and_tree('tree')
2989
self.build_tree(['tree/foo'])
2991
os.unlink('tree/foo')
2992
return self.get_preview(tree), LINES
2994
def add_contents_records(self):
2995
attribs = self.default_attribs()
2996
attribs['_id_number'] = 2
2997
attribs['_tree_path_ids'] = {
3000
contents = [('new-1', 'file',
3001
'i 4\na\nb\nc\nd\n\n')]
3002
return self.make_records(attribs, contents)
3004
def test_serialize_add_contents(self):
3005
tt, LINES = self.make_add_contents_preview()
3006
trans_id = tt.trans_id_tree_path('foo')
3007
tt.create_file(LINES, trans_id)
3008
self.assertSerializesTo(self.add_contents_records(), tt)
3010
def test_deserialize_add_contents(self):
3011
tt, LINES = self.make_add_contents_preview()
3012
tt.deserialize(iter(self.add_contents_records()))
3013
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3015
def test_get_parents_lines(self):
3016
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3017
LINES_TWO = 'z\nbb\nx\ndd\n'
3018
tree = self.make_branch_and_tree('tree')
3019
self.build_tree_contents([('tree/file', LINES_ONE)])
3020
tree.add('file', 'file-id')
3021
tt = self.get_preview(tree)
3022
trans_id = tt.trans_id_tree_path('file')
3023
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3024
tt._get_parents_lines(trans_id))
3026
def test_get_parents_texts(self):
3027
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3028
LINES_TWO = 'z\nbb\nx\ndd\n'
3029
tree = self.make_branch_and_tree('tree')
3030
self.build_tree_contents([('tree/file', LINES_ONE)])
3031
tree.add('file', 'file-id')
3032
tt = self.get_preview(tree)
3033
trans_id = tt.trans_id_tree_path('file')
3034
self.assertEqual((LINES_ONE,),
3035
tt._get_parents_texts(trans_id))