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
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
1955
rev = tt.commit(branch, 'message')
1956
tree = branch.basis_tree()
1957
self.assertEqual('file', tree.id2path('file-id'))
1958
self.assertEqual('contents', tree.get_file_text('file-id'))
1959
self.assertEqual('dir', tree.id2path('dir-id'))
1960
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
1961
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
1963
def test_add_unversioned(self):
1964
branch, tt = self.get_branch_and_transform()
1965
tt.new_file('file', tt.root, 'contents')
1966
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
1967
'message', strict=True)
1969
def test_modify_strict(self):
1970
branch, tt = self.get_branch_and_transform()
1971
tt.new_file('file', tt.root, 'contents', 'file-id')
1972
tt.commit(branch, 'message', strict=True)
1973
tt = TransformPreview(branch.basis_tree())
1974
self.addCleanup(tt.finalize)
1975
trans_id = tt.trans_id_file_id('file-id')
1976
tt.delete_contents(trans_id)
1977
tt.create_file('contents', trans_id)
1978
tt.commit(branch, 'message', strict=True)
1980
def test_commit_malformed(self):
1981
"""Committing a malformed transform should raise an exception.
1983
In this case, we are adding a file without adding its parent.
1985
branch, tt = self.get_branch_and_transform()
1986
parent_id = tt.trans_id_file_id('parent-id')
1987
tt.new_file('file', parent_id, 'contents', 'file-id')
1988
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
1992
class MockTransform(object):
1994
def has_named_child(self, by_parent, parent_id, name):
1995
for child_id in by_parent[parent_id]:
1999
elif name == "name.~%s~" % child_id:
2004
class MockEntry(object):
2006
object.__init__(self)
2010
class TestGetBackupName(TestCase):
2011
def test_get_backup_name(self):
2012
tt = MockTransform()
2013
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2014
self.assertEqual(name, 'name.~1~')
2015
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2016
self.assertEqual(name, 'name.~2~')
2017
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2018
self.assertEqual(name, 'name.~1~')
2019
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2020
self.assertEqual(name, 'name.~1~')
2021
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2022
self.assertEqual(name, 'name.~4~')
2025
class TestFileMover(tests.TestCaseWithTransport):
2027
def test_file_mover(self):
2028
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2029
mover = _FileMover()
2030
mover.rename('a', 'q')
2031
self.failUnlessExists('q')
2032
self.failIfExists('a')
2033
self.failUnlessExists('q/b')
2034
self.failUnlessExists('c')
2035
self.failUnlessExists('c/d')
2037
def test_pre_delete_rollback(self):
2038
self.build_tree(['a/'])
2039
mover = _FileMover()
2040
mover.pre_delete('a', 'q')
2041
self.failUnlessExists('q')
2042
self.failIfExists('a')
2044
self.failIfExists('q')
2045
self.failUnlessExists('a')
2047
def test_apply_deletions(self):
2048
self.build_tree(['a/', 'b/'])
2049
mover = _FileMover()
2050
mover.pre_delete('a', 'q')
2051
mover.pre_delete('b', 'r')
2052
self.failUnlessExists('q')
2053
self.failUnlessExists('r')
2054
self.failIfExists('a')
2055
self.failIfExists('b')
2056
mover.apply_deletions()
2057
self.failIfExists('q')
2058
self.failIfExists('r')
2059
self.failIfExists('a')
2060
self.failIfExists('b')
2062
def test_file_mover_rollback(self):
2063
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2064
mover = _FileMover()
2065
mover.rename('c/d', 'c/f')
2066
mover.rename('c/e', 'c/d')
2068
mover.rename('a', 'c')
2069
except errors.FileExists, e:
2071
self.failUnlessExists('a')
2072
self.failUnlessExists('c/d')
2075
class Bogus(Exception):
2079
class TestTransformRollback(tests.TestCaseWithTransport):
2081
class ExceptionFileMover(_FileMover):
2083
def __init__(self, bad_source=None, bad_target=None):
2084
_FileMover.__init__(self)
2085
self.bad_source = bad_source
2086
self.bad_target = bad_target
2088
def rename(self, source, target):
2089
if (self.bad_source is not None and
2090
source.endswith(self.bad_source)):
2092
elif (self.bad_target is not None and
2093
target.endswith(self.bad_target)):
2096
_FileMover.rename(self, source, target)
2098
def test_rollback_rename(self):
2099
tree = self.make_branch_and_tree('.')
2100
self.build_tree(['a/', 'a/b'])
2101
tt = TreeTransform(tree)
2102
self.addCleanup(tt.finalize)
2103
a_id = tt.trans_id_tree_path('a')
2104
tt.adjust_path('c', tt.root, a_id)
2105
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2106
self.assertRaises(Bogus, tt.apply,
2107
_mover=self.ExceptionFileMover(bad_source='a'))
2108
self.failUnlessExists('a')
2109
self.failUnlessExists('a/b')
2111
self.failUnlessExists('c')
2112
self.failUnlessExists('c/d')
2114
def test_rollback_rename_into_place(self):
2115
tree = self.make_branch_and_tree('.')
2116
self.build_tree(['a/', 'a/b'])
2117
tt = TreeTransform(tree)
2118
self.addCleanup(tt.finalize)
2119
a_id = tt.trans_id_tree_path('a')
2120
tt.adjust_path('c', tt.root, a_id)
2121
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2122
self.assertRaises(Bogus, tt.apply,
2123
_mover=self.ExceptionFileMover(bad_target='c/d'))
2124
self.failUnlessExists('a')
2125
self.failUnlessExists('a/b')
2127
self.failUnlessExists('c')
2128
self.failUnlessExists('c/d')
2130
def test_rollback_deletion(self):
2131
tree = self.make_branch_and_tree('.')
2132
self.build_tree(['a/', 'a/b'])
2133
tt = TreeTransform(tree)
2134
self.addCleanup(tt.finalize)
2135
a_id = tt.trans_id_tree_path('a')
2136
tt.delete_contents(a_id)
2137
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2138
self.assertRaises(Bogus, tt.apply,
2139
_mover=self.ExceptionFileMover(bad_target='d'))
2140
self.failUnlessExists('a')
2141
self.failUnlessExists('a/b')
2143
def test_resolve_no_parent(self):
2144
wt = self.make_branch_and_tree('.')
2145
tt = TreeTransform(wt)
2146
self.addCleanup(tt.finalize)
2147
parent = tt.trans_id_file_id('parent-id')
2148
tt.new_file('file', parent, 'Contents')
2149
resolve_conflicts(tt)
2152
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2153
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2155
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2156
('', ''), ('directory', 'directory'), (False, None))
2159
class TestTransformPreview(tests.TestCaseWithTransport):
2161
def create_tree(self):
2162
tree = self.make_branch_and_tree('.')
2163
self.build_tree_contents([('a', 'content 1')])
2164
tree.set_root_id('TREE_ROOT')
2165
tree.add('a', 'a-id')
2166
tree.commit('rev1', rev_id='rev1')
2167
return tree.branch.repository.revision_tree('rev1')
2169
def get_empty_preview(self):
2170
repository = self.make_repository('repo')
2171
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2172
preview = TransformPreview(tree)
2173
self.addCleanup(preview.finalize)
2176
def test_transform_preview(self):
2177
revision_tree = self.create_tree()
2178
preview = TransformPreview(revision_tree)
2179
self.addCleanup(preview.finalize)
2181
def test_transform_preview_tree(self):
2182
revision_tree = self.create_tree()
2183
preview = TransformPreview(revision_tree)
2184
self.addCleanup(preview.finalize)
2185
preview.get_preview_tree()
2187
def test_transform_new_file(self):
2188
revision_tree = self.create_tree()
2189
preview = TransformPreview(revision_tree)
2190
self.addCleanup(preview.finalize)
2191
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2192
preview_tree = preview.get_preview_tree()
2193
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2195
preview_tree.get_file('file2-id').read(), 'content B\n')
2197
def test_diff_preview_tree(self):
2198
revision_tree = self.create_tree()
2199
preview = TransformPreview(revision_tree)
2200
self.addCleanup(preview.finalize)
2201
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2202
preview_tree = preview.get_preview_tree()
2204
show_diff_trees(revision_tree, preview_tree, out)
2205
lines = out.getvalue().splitlines()
2206
self.assertEqual(lines[0], "=== added file 'file2'")
2207
# 3 lines of diff administrivia
2208
self.assertEqual(lines[4], "+content B")
2210
def test_transform_conflicts(self):
2211
revision_tree = self.create_tree()
2212
preview = TransformPreview(revision_tree)
2213
self.addCleanup(preview.finalize)
2214
preview.new_file('a', preview.root, 'content 2')
2215
resolve_conflicts(preview)
2216
trans_id = preview.trans_id_file_id('a-id')
2217
self.assertEqual('a.moved', preview.final_name(trans_id))
2219
def get_tree_and_preview_tree(self):
2220
revision_tree = self.create_tree()
2221
preview = TransformPreview(revision_tree)
2222
self.addCleanup(preview.finalize)
2223
a_trans_id = preview.trans_id_file_id('a-id')
2224
preview.delete_contents(a_trans_id)
2225
preview.create_file('b content', a_trans_id)
2226
preview_tree = preview.get_preview_tree()
2227
return revision_tree, preview_tree
2229
def test_iter_changes(self):
2230
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2231
root = revision_tree.inventory.root.file_id
2232
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2233
(root, root), ('a', 'a'), ('file', 'file'),
2235
list(preview_tree.iter_changes(revision_tree)))
2237
def test_include_unchanged_succeeds(self):
2238
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2239
changes = preview_tree.iter_changes(revision_tree,
2240
include_unchanged=True)
2241
root = revision_tree.inventory.root.file_id
2243
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2245
def test_specific_files(self):
2246
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2247
changes = preview_tree.iter_changes(revision_tree,
2248
specific_files=[''])
2249
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2251
def test_want_unversioned(self):
2252
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2253
changes = preview_tree.iter_changes(revision_tree,
2254
want_unversioned=True)
2255
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2257
def test_ignore_extra_trees_no_specific_files(self):
2258
# extra_trees is harmless without specific_files, so we'll silently
2259
# accept it, even though we won't use it.
2260
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2261
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2263
def test_ignore_require_versioned_no_specific_files(self):
2264
# require_versioned is meaningless without specific_files.
2265
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2266
preview_tree.iter_changes(revision_tree, require_versioned=False)
2268
def test_ignore_pb(self):
2269
# pb could be supported, but TT.iter_changes doesn't support it.
2270
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2271
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2273
def test_kind(self):
2274
revision_tree = self.create_tree()
2275
preview = TransformPreview(revision_tree)
2276
self.addCleanup(preview.finalize)
2277
preview.new_file('file', preview.root, 'contents', 'file-id')
2278
preview.new_directory('directory', preview.root, 'dir-id')
2279
preview_tree = preview.get_preview_tree()
2280
self.assertEqual('file', preview_tree.kind('file-id'))
2281
self.assertEqual('directory', preview_tree.kind('dir-id'))
2283
def test_get_file_mtime(self):
2284
preview = self.get_empty_preview()
2285
file_trans_id = preview.new_file('file', preview.root, 'contents',
2287
limbo_path = preview._limbo_name(file_trans_id)
2288
preview_tree = preview.get_preview_tree()
2289
self.assertEqual(os.stat(limbo_path).st_mtime,
2290
preview_tree.get_file_mtime('file-id'))
2292
def test_get_file_mtime_renamed(self):
2293
work_tree = self.make_branch_and_tree('tree')
2294
self.build_tree(['tree/file'])
2295
work_tree.add('file', 'file-id')
2296
preview = TransformPreview(work_tree)
2297
self.addCleanup(preview.finalize)
2298
file_trans_id = preview.trans_id_tree_file_id('file-id')
2299
preview.adjust_path('renamed', preview.root, file_trans_id)
2300
preview_tree = preview.get_preview_tree()
2301
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2302
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2304
def test_get_file(self):
2305
preview = self.get_empty_preview()
2306
preview.new_file('file', preview.root, 'contents', 'file-id')
2307
preview_tree = preview.get_preview_tree()
2308
tree_file = preview_tree.get_file('file-id')
2310
self.assertEqual('contents', tree_file.read())
2314
def test_get_symlink_target(self):
2315
self.requireFeature(SymlinkFeature)
2316
preview = self.get_empty_preview()
2317
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2318
preview_tree = preview.get_preview_tree()
2319
self.assertEqual('target',
2320
preview_tree.get_symlink_target('symlink-id'))
2322
def test_all_file_ids(self):
2323
tree = self.make_branch_and_tree('tree')
2324
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2325
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2326
preview = TransformPreview(tree)
2327
self.addCleanup(preview.finalize)
2328
preview.unversion_file(preview.trans_id_file_id('b-id'))
2329
c_trans_id = preview.trans_id_file_id('c-id')
2330
preview.unversion_file(c_trans_id)
2331
preview.version_file('c-id', c_trans_id)
2332
preview_tree = preview.get_preview_tree()
2333
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2334
preview_tree.all_file_ids())
2336
def test_path2id_deleted_unchanged(self):
2337
tree = self.make_branch_and_tree('tree')
2338
self.build_tree(['tree/unchanged', 'tree/deleted'])
2339
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2340
preview = TransformPreview(tree)
2341
self.addCleanup(preview.finalize)
2342
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2343
preview_tree = preview.get_preview_tree()
2344
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2345
self.assertIs(None, preview_tree.path2id('deleted'))
2347
def test_path2id_created(self):
2348
tree = self.make_branch_and_tree('tree')
2349
self.build_tree(['tree/unchanged'])
2350
tree.add(['unchanged'], ['unchanged-id'])
2351
preview = TransformPreview(tree)
2352
self.addCleanup(preview.finalize)
2353
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2354
'contents', 'new-id')
2355
preview_tree = preview.get_preview_tree()
2356
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2358
def test_path2id_moved(self):
2359
tree = self.make_branch_and_tree('tree')
2360
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2361
tree.add(['old_parent', 'old_parent/child'],
2362
['old_parent-id', 'child-id'])
2363
preview = TransformPreview(tree)
2364
self.addCleanup(preview.finalize)
2365
new_parent = preview.new_directory('new_parent', preview.root,
2367
preview.adjust_path('child', new_parent,
2368
preview.trans_id_file_id('child-id'))
2369
preview_tree = preview.get_preview_tree()
2370
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2371
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2373
def test_path2id_renamed_parent(self):
2374
tree = self.make_branch_and_tree('tree')
2375
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2376
tree.add(['old_name', 'old_name/child'],
2377
['parent-id', 'child-id'])
2378
preview = TransformPreview(tree)
2379
self.addCleanup(preview.finalize)
2380
preview.adjust_path('new_name', preview.root,
2381
preview.trans_id_file_id('parent-id'))
2382
preview_tree = preview.get_preview_tree()
2383
self.assertIs(None, preview_tree.path2id('old_name/child'))
2384
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2386
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2387
preview_tree = tt.get_preview_tree()
2388
preview_result = list(preview_tree.iter_entries_by_dir(
2392
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2393
self.assertEqual(actual_result, preview_result)
2395
def test_iter_entries_by_dir_new(self):
2396
tree = self.make_branch_and_tree('tree')
2397
tt = TreeTransform(tree)
2398
tt.new_file('new', tt.root, 'contents', 'new-id')
2399
self.assertMatchingIterEntries(tt)
2401
def test_iter_entries_by_dir_deleted(self):
2402
tree = self.make_branch_and_tree('tree')
2403
self.build_tree(['tree/deleted'])
2404
tree.add('deleted', 'deleted-id')
2405
tt = TreeTransform(tree)
2406
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2407
self.assertMatchingIterEntries(tt)
2409
def test_iter_entries_by_dir_unversioned(self):
2410
tree = self.make_branch_and_tree('tree')
2411
self.build_tree(['tree/removed'])
2412
tree.add('removed', 'removed-id')
2413
tt = TreeTransform(tree)
2414
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2415
self.assertMatchingIterEntries(tt)
2417
def test_iter_entries_by_dir_moved(self):
2418
tree = self.make_branch_and_tree('tree')
2419
self.build_tree(['tree/moved', 'tree/new_parent/'])
2420
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2421
tt = TreeTransform(tree)
2422
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2423
tt.trans_id_file_id('moved-id'))
2424
self.assertMatchingIterEntries(tt)
2426
def test_iter_entries_by_dir_specific_file_ids(self):
2427
tree = self.make_branch_and_tree('tree')
2428
tree.set_root_id('tree-root-id')
2429
self.build_tree(['tree/parent/', 'tree/parent/child'])
2430
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2431
tt = TreeTransform(tree)
2432
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2434
def test_symlink_content_summary(self):
2435
self.requireFeature(SymlinkFeature)
2436
preview = self.get_empty_preview()
2437
preview.new_symlink('path', preview.root, 'target', 'path-id')
2438
summary = preview.get_preview_tree().path_content_summary('path')
2439
self.assertEqual(('symlink', None, None, 'target'), summary)
2441
def test_missing_content_summary(self):
2442
preview = self.get_empty_preview()
2443
summary = preview.get_preview_tree().path_content_summary('path')
2444
self.assertEqual(('missing', None, None, None), summary)
2446
def test_deleted_content_summary(self):
2447
tree = self.make_branch_and_tree('tree')
2448
self.build_tree(['tree/path/'])
2450
preview = TransformPreview(tree)
2451
self.addCleanup(preview.finalize)
2452
preview.delete_contents(preview.trans_id_tree_path('path'))
2453
summary = preview.get_preview_tree().path_content_summary('path')
2454
self.assertEqual(('missing', None, None, None), summary)
2456
def test_file_content_summary_executable(self):
2457
if not osutils.supports_executable():
2458
raise TestNotApplicable()
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
if not osutils.supports_executable():
2474
raise TestNotApplicable()
2475
tree = self.make_branch_and_tree('tree')
2476
self.build_tree(['tree/path'])
2478
preview = TransformPreview(tree)
2479
self.addCleanup(preview.finalize)
2480
path_id = preview.trans_id_tree_path('path')
2481
preview.set_executability(True, path_id)
2482
summary = preview.get_preview_tree().path_content_summary('path')
2483
self.assertEqual(True, summary[2])
2485
def test_file_content_summary_non_exec(self):
2486
preview = self.get_empty_preview()
2487
preview.new_file('path', preview.root, 'contents', 'path-id')
2488
summary = preview.get_preview_tree().path_content_summary('path')
2489
self.assertEqual(4, len(summary))
2490
self.assertEqual('file', summary[0])
2491
# size must be known
2492
self.assertEqual(len('contents'), summary[1])
2494
if osutils.supports_executable():
2495
self.assertEqual(False, summary[2])
2497
self.assertEqual(None, summary[2])
2498
# will not have hash (not cheap to determine)
2499
self.assertIs(None, summary[3])
2501
def test_dir_content_summary(self):
2502
preview = self.get_empty_preview()
2503
preview.new_directory('path', preview.root, 'path-id')
2504
summary = preview.get_preview_tree().path_content_summary('path')
2505
self.assertEqual(('directory', None, None, None), summary)
2507
def test_tree_content_summary(self):
2508
preview = self.get_empty_preview()
2509
path = preview.new_directory('path', preview.root, 'path-id')
2510
preview.set_tree_reference('rev-1', path)
2511
summary = preview.get_preview_tree().path_content_summary('path')
2512
self.assertEqual(4, len(summary))
2513
self.assertEqual('tree-reference', summary[0])
2515
def test_annotate(self):
2516
tree = self.make_branch_and_tree('tree')
2517
self.build_tree_contents([('tree/file', 'a\n')])
2518
tree.add('file', 'file-id')
2519
tree.commit('a', rev_id='one')
2520
self.build_tree_contents([('tree/file', 'a\nb\n')])
2521
preview = TransformPreview(tree)
2522
self.addCleanup(preview.finalize)
2523
file_trans_id = preview.trans_id_file_id('file-id')
2524
preview.delete_contents(file_trans_id)
2525
preview.create_file('a\nb\nc\n', file_trans_id)
2526
preview_tree = preview.get_preview_tree()
2532
annotation = preview_tree.annotate_iter('file-id', 'me:')
2533
self.assertEqual(expected, annotation)
2535
def test_annotate_missing(self):
2536
preview = self.get_empty_preview()
2537
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2538
preview_tree = preview.get_preview_tree()
2544
annotation = preview_tree.annotate_iter('file-id', 'me:')
2545
self.assertEqual(expected, annotation)
2547
def test_annotate_rename(self):
2548
tree = self.make_branch_and_tree('tree')
2549
self.build_tree_contents([('tree/file', 'a\n')])
2550
tree.add('file', 'file-id')
2551
tree.commit('a', rev_id='one')
2552
preview = TransformPreview(tree)
2553
self.addCleanup(preview.finalize)
2554
file_trans_id = preview.trans_id_file_id('file-id')
2555
preview.adjust_path('newname', preview.root, file_trans_id)
2556
preview_tree = preview.get_preview_tree()
2560
annotation = preview_tree.annotate_iter('file-id', 'me:')
2561
self.assertEqual(expected, annotation)
2563
def test_annotate_deleted(self):
2564
tree = self.make_branch_and_tree('tree')
2565
self.build_tree_contents([('tree/file', 'a\n')])
2566
tree.add('file', 'file-id')
2567
tree.commit('a', rev_id='one')
2568
self.build_tree_contents([('tree/file', 'a\nb\n')])
2569
preview = TransformPreview(tree)
2570
self.addCleanup(preview.finalize)
2571
file_trans_id = preview.trans_id_file_id('file-id')
2572
preview.delete_contents(file_trans_id)
2573
preview_tree = preview.get_preview_tree()
2574
annotation = preview_tree.annotate_iter('file-id', 'me:')
2575
self.assertIs(None, annotation)
2577
def test_stored_kind(self):
2578
preview = self.get_empty_preview()
2579
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2580
preview_tree = preview.get_preview_tree()
2581
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2583
def test_is_executable(self):
2584
preview = self.get_empty_preview()
2585
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2586
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2587
preview_tree = preview.get_preview_tree()
2588
self.assertEqual(True, preview_tree.is_executable('file-id'))
2590
def test_get_set_parent_ids(self):
2591
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2592
self.assertEqual([], preview_tree.get_parent_ids())
2593
preview_tree.set_parent_ids(['rev-1'])
2594
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2596
def test_plan_file_merge(self):
2597
work_a = self.make_branch_and_tree('wta')
2598
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2599
work_a.add('file', 'file-id')
2600
base_id = work_a.commit('base version')
2601
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2602
preview = TransformPreview(work_a)
2603
self.addCleanup(preview.finalize)
2604
trans_id = preview.trans_id_file_id('file-id')
2605
preview.delete_contents(trans_id)
2606
preview.create_file('b\nc\nd\ne\n', trans_id)
2607
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2608
tree_a = preview.get_preview_tree()
2609
tree_a.set_parent_ids([base_id])
2611
('killed-a', 'a\n'),
2612
('killed-b', 'b\n'),
2613
('unchanged', 'c\n'),
2614
('unchanged', 'd\n'),
2617
], list(tree_a.plan_file_merge('file-id', tree_b)))
2619
def test_plan_file_merge_revision_tree(self):
2620
work_a = self.make_branch_and_tree('wta')
2621
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2622
work_a.add('file', 'file-id')
2623
base_id = work_a.commit('base version')
2624
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2625
preview = TransformPreview(work_a.basis_tree())
2626
self.addCleanup(preview.finalize)
2627
trans_id = preview.trans_id_file_id('file-id')
2628
preview.delete_contents(trans_id)
2629
preview.create_file('b\nc\nd\ne\n', trans_id)
2630
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2631
tree_a = preview.get_preview_tree()
2632
tree_a.set_parent_ids([base_id])
2634
('killed-a', 'a\n'),
2635
('killed-b', 'b\n'),
2636
('unchanged', 'c\n'),
2637
('unchanged', 'd\n'),
2640
], list(tree_a.plan_file_merge('file-id', tree_b)))
2642
def test_walkdirs(self):
2643
preview = self.get_empty_preview()
2644
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2645
# FIXME: new_directory should mark root.
2646
preview.adjust_path('', ROOT_PARENT, root)
2647
preview_tree = preview.get_preview_tree()
2648
file_trans_id = preview.new_file('a', preview.root, 'contents',
2650
expected = [(('', 'tree-root'),
2651
[('a', 'a', 'file', None, 'a-id', 'file')])]
2652
self.assertEqual(expected, list(preview_tree.walkdirs()))
2654
def test_extras(self):
2655
work_tree = self.make_branch_and_tree('tree')
2656
self.build_tree(['tree/removed-file', 'tree/existing-file',
2657
'tree/not-removed-file'])
2658
work_tree.add(['removed-file', 'not-removed-file'])
2659
preview = TransformPreview(work_tree)
2660
self.addCleanup(preview.finalize)
2661
preview.new_file('new-file', preview.root, 'contents')
2662
preview.new_file('new-versioned-file', preview.root, 'contents',
2664
tree = preview.get_preview_tree()
2665
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2666
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2669
def test_merge_into_preview(self):
2670
work_tree = self.make_branch_and_tree('tree')
2671
self.build_tree_contents([('tree/file','b\n')])
2672
work_tree.add('file', 'file-id')
2673
work_tree.commit('first commit')
2674
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2675
self.build_tree_contents([('child/file','b\nc\n')])
2676
child_tree.commit('child commit')
2677
child_tree.lock_write()
2678
self.addCleanup(child_tree.unlock)
2679
work_tree.lock_write()
2680
self.addCleanup(work_tree.unlock)
2681
preview = TransformPreview(work_tree)
2682
self.addCleanup(preview.finalize)
2683
file_trans_id = preview.trans_id_file_id('file-id')
2684
preview.delete_contents(file_trans_id)
2685
preview.create_file('a\nb\n', file_trans_id)
2686
pb = progress.DummyProgress()
2687
preview_tree = preview.get_preview_tree()
2688
merger = Merger.from_revision_ids(pb, preview_tree,
2689
child_tree.branch.last_revision(),
2690
other_branch=child_tree.branch,
2691
tree_branch=work_tree.branch)
2692
merger.merge_type = Merge3Merger
2693
tt = merger.make_merger().make_preview_transform()
2694
self.addCleanup(tt.finalize)
2695
final_tree = tt.get_preview_tree()
2696
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2698
def test_merge_preview_into_workingtree(self):
2699
tree = self.make_branch_and_tree('tree')
2700
tree.set_root_id('TREE_ROOT')
2701
tt = TransformPreview(tree)
2702
self.addCleanup(tt.finalize)
2703
tt.new_file('name', tt.root, 'content', 'file-id')
2704
tree2 = self.make_branch_and_tree('tree2')
2705
tree2.set_root_id('TREE_ROOT')
2706
pb = progress.DummyProgress()
2707
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2708
pb, tree.basis_tree())
2709
merger.merge_type = Merge3Merger
2712
def test_merge_preview_into_workingtree_handles_conflicts(self):
2713
tree = self.make_branch_and_tree('tree')
2714
self.build_tree_contents([('tree/foo', 'bar')])
2715
tree.add('foo', 'foo-id')
2717
tt = TransformPreview(tree)
2718
self.addCleanup(tt.finalize)
2719
trans_id = tt.trans_id_file_id('foo-id')
2720
tt.delete_contents(trans_id)
2721
tt.create_file('baz', trans_id)
2722
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2723
self.build_tree_contents([('tree2/foo', 'qux')])
2724
pb = progress.DummyProgress()
2725
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2726
pb, tree.basis_tree())
2727
merger.merge_type = Merge3Merger
2730
def test_is_executable(self):
2731
tree = self.make_branch_and_tree('tree')
2732
preview = TransformPreview(tree)
2733
self.addCleanup(preview.finalize)
2734
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2735
preview_tree = preview.get_preview_tree()
2736
self.assertEqual(False, preview_tree.is_executable('baz-id',
2738
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2740
def test_commit_preview_tree(self):
2741
tree = self.make_branch_and_tree('tree')
2742
rev_id = tree.commit('rev1')
2743
tree.branch.lock_write()
2744
self.addCleanup(tree.branch.unlock)
2745
tt = TransformPreview(tree)
2746
tt.new_file('file', tt.root, 'contents', 'file_id')
2747
self.addCleanup(tt.finalize)
2748
preview = tt.get_preview_tree()
2749
preview.set_parent_ids([rev_id])
2750
builder = tree.branch.get_commit_builder([rev_id])
2751
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2752
builder.finish_inventory()
2753
rev2_id = builder.commit('rev2')
2754
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2755
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2757
def test_ascii_limbo_paths(self):
2758
self.requireFeature(tests.UnicodeFilenameFeature)
2759
branch = self.make_branch('any')
2760
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2761
tt = TransformPreview(tree)
2762
foo_id = tt.new_directory('', ROOT_PARENT)
2763
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2764
limbo_path = tt._limbo_name(bar_id)
2765
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2768
class FakeSerializer(object):
2769
"""Serializer implementation that simply returns the input.
2771
The input is returned in the order used by pack.ContainerPushParser.
2774
def bytes_record(bytes, names):
2778
class TestSerializeTransform(tests.TestCaseWithTransport):
2780
_test_needs_features = [tests.UnicodeFilenameFeature]
2782
def get_preview(self, tree=None):
2784
tree = self.make_branch_and_tree('tree')
2785
tt = TransformPreview(tree)
2786
self.addCleanup(tt.finalize)
2789
def assertSerializesTo(self, expected, tt):
2790
records = list(tt.serialize(FakeSerializer()))
2791
self.assertEqual(expected, records)
2794
def default_attribs():
2799
'_new_executability': {},
2801
'_tree_path_ids': {'': 'new-0'},
2803
'_removed_contents': [],
2804
'_non_present_ids': {},
2807
def make_records(self, attribs, contents):
2809
(((('attribs'),),), bencode.bencode(attribs))]
2810
records.extend([(((n, k),), c) for n, k, c in contents])
2813
def creation_records(self):
2814
attribs = self.default_attribs()
2815
attribs['_id_number'] = 3
2816
attribs['_new_name'] = {
2817
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2818
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2819
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2820
attribs['_new_executability'] = {'new-1': 1}
2822
('new-1', 'file', 'i 1\nbar\n'),
2823
('new-2', 'directory', ''),
2825
return self.make_records(attribs, contents)
2827
def test_serialize_creation(self):
2828
tt = self.get_preview()
2829
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2830
tt.new_directory('qux', tt.root, 'quxx')
2831
self.assertSerializesTo(self.creation_records(), tt)
2833
def test_deserialize_creation(self):
2834
tt = self.get_preview()
2835
tt.deserialize(iter(self.creation_records()))
2836
self.assertEqual(3, tt._id_number)
2837
self.assertEqual({'new-1': u'foo\u1234',
2838
'new-2': 'qux'}, tt._new_name)
2839
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2840
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2841
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2842
self.assertEqual({'new-1': True}, tt._new_executability)
2843
self.assertEqual({'new-1': 'file',
2844
'new-2': 'directory'}, tt._new_contents)
2845
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2847
foo_content = foo_limbo.read()
2850
self.assertEqual('bar', foo_content)
2852
def symlink_creation_records(self):
2853
attribs = self.default_attribs()
2854
attribs['_id_number'] = 2
2855
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2856
attribs['_new_parent'] = {'new-1': 'new-0'}
2857
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2858
return self.make_records(attribs, contents)
2860
def test_serialize_symlink_creation(self):
2861
self.requireFeature(tests.SymlinkFeature)
2862
tt = self.get_preview()
2863
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2864
self.assertSerializesTo(self.symlink_creation_records(), tt)
2866
def test_deserialize_symlink_creation(self):
2867
self.requireFeature(tests.SymlinkFeature)
2868
tt = self.get_preview()
2869
tt.deserialize(iter(self.symlink_creation_records()))
2870
abspath = tt._limbo_name('new-1')
2871
foo_content = osutils.readlink(abspath)
2872
self.assertEqual(u'bar\u1234', foo_content)
2874
def make_destruction_preview(self):
2875
tree = self.make_branch_and_tree('.')
2876
self.build_tree([u'foo\u1234', 'bar'])
2877
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2878
return self.get_preview(tree)
2880
def destruction_records(self):
2881
attribs = self.default_attribs()
2882
attribs['_id_number'] = 3
2883
attribs['_removed_id'] = ['new-1']
2884
attribs['_removed_contents'] = ['new-2']
2885
attribs['_tree_path_ids'] = {
2887
u'foo\u1234'.encode('utf-8'): 'new-1',
2890
return self.make_records(attribs, [])
2892
def test_serialize_destruction(self):
2893
tt = self.make_destruction_preview()
2894
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2895
tt.unversion_file(foo_trans_id)
2896
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2897
tt.delete_contents(bar_trans_id)
2898
self.assertSerializesTo(self.destruction_records(), tt)
2900
def test_deserialize_destruction(self):
2901
tt = self.make_destruction_preview()
2902
tt.deserialize(iter(self.destruction_records()))
2903
self.assertEqual({u'foo\u1234': 'new-1',
2905
'': tt.root}, tt._tree_path_ids)
2906
self.assertEqual({'new-1': u'foo\u1234',
2908
tt.root: ''}, tt._tree_id_paths)
2909
self.assertEqual(set(['new-1']), tt._removed_id)
2910
self.assertEqual(set(['new-2']), tt._removed_contents)
2912
def missing_records(self):
2913
attribs = self.default_attribs()
2914
attribs['_id_number'] = 2
2915
attribs['_non_present_ids'] = {
2917
return self.make_records(attribs, [])
2919
def test_serialize_missing(self):
2920
tt = self.get_preview()
2921
boo_trans_id = tt.trans_id_file_id('boo')
2922
self.assertSerializesTo(self.missing_records(), tt)
2924
def test_deserialize_missing(self):
2925
tt = self.get_preview()
2926
tt.deserialize(iter(self.missing_records()))
2927
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2929
def make_modification_preview(self):
2930
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2931
LINES_TWO = 'z\nbb\nx\ndd\n'
2932
tree = self.make_branch_and_tree('tree')
2933
self.build_tree_contents([('tree/file', LINES_ONE)])
2934
tree.add('file', 'file-id')
2935
return self.get_preview(tree), LINES_TWO
2937
def modification_records(self):
2938
attribs = self.default_attribs()
2939
attribs['_id_number'] = 2
2940
attribs['_tree_path_ids'] = {
2943
attribs['_removed_contents'] = ['new-1']
2944
contents = [('new-1', 'file',
2945
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2946
return self.make_records(attribs, contents)
2948
def test_serialize_modification(self):
2949
tt, LINES = self.make_modification_preview()
2950
trans_id = tt.trans_id_file_id('file-id')
2951
tt.delete_contents(trans_id)
2952
tt.create_file(LINES, trans_id)
2953
self.assertSerializesTo(self.modification_records(), tt)
2955
def test_deserialize_modification(self):
2956
tt, LINES = self.make_modification_preview()
2957
tt.deserialize(iter(self.modification_records()))
2958
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2960
def make_kind_change_preview(self):
2961
LINES = 'a\nb\nc\nd\n'
2962
tree = self.make_branch_and_tree('tree')
2963
self.build_tree(['tree/foo/'])
2964
tree.add('foo', 'foo-id')
2965
return self.get_preview(tree), LINES
2967
def kind_change_records(self):
2968
attribs = self.default_attribs()
2969
attribs['_id_number'] = 2
2970
attribs['_tree_path_ids'] = {
2973
attribs['_removed_contents'] = ['new-1']
2974
contents = [('new-1', 'file',
2975
'i 4\na\nb\nc\nd\n\n')]
2976
return self.make_records(attribs, contents)
2978
def test_serialize_kind_change(self):
2979
tt, LINES = self.make_kind_change_preview()
2980
trans_id = tt.trans_id_file_id('foo-id')
2981
tt.delete_contents(trans_id)
2982
tt.create_file(LINES, trans_id)
2983
self.assertSerializesTo(self.kind_change_records(), tt)
2985
def test_deserialize_kind_change(self):
2986
tt, LINES = self.make_kind_change_preview()
2987
tt.deserialize(iter(self.kind_change_records()))
2988
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2990
def make_add_contents_preview(self):
2991
LINES = 'a\nb\nc\nd\n'
2992
tree = self.make_branch_and_tree('tree')
2993
self.build_tree(['tree/foo'])
2995
os.unlink('tree/foo')
2996
return self.get_preview(tree), LINES
2998
def add_contents_records(self):
2999
attribs = self.default_attribs()
3000
attribs['_id_number'] = 2
3001
attribs['_tree_path_ids'] = {
3004
contents = [('new-1', 'file',
3005
'i 4\na\nb\nc\nd\n\n')]
3006
return self.make_records(attribs, contents)
3008
def test_serialize_add_contents(self):
3009
tt, LINES = self.make_add_contents_preview()
3010
trans_id = tt.trans_id_tree_path('foo')
3011
tt.create_file(LINES, trans_id)
3012
self.assertSerializesTo(self.add_contents_records(), tt)
3014
def test_deserialize_add_contents(self):
3015
tt, LINES = self.make_add_contents_preview()
3016
tt.deserialize(iter(self.add_contents_records()))
3017
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3019
def test_get_parents_lines(self):
3020
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3021
LINES_TWO = 'z\nbb\nx\ndd\n'
3022
tree = self.make_branch_and_tree('tree')
3023
self.build_tree_contents([('tree/file', LINES_ONE)])
3024
tree.add('file', 'file-id')
3025
tt = self.get_preview(tree)
3026
trans_id = tt.trans_id_tree_path('file')
3027
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3028
tt._get_parents_lines(trans_id))
3030
def test_get_parents_texts(self):
3031
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3032
LINES_TWO = 'z\nbb\nx\ndd\n'
3033
tree = self.make_branch_and_tree('tree')
3034
self.build_tree_contents([('tree/file', LINES_ONE)])
3035
tree.add('file', 'file-id')
3036
tt = self.get_preview(tree)
3037
trans_id = tt.trans_id_tree_path('file')
3038
self.assertEqual((LINES_ONE,),
3039
tt._get_parents_texts(trans_id))