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
18
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 install_rot13_content_filter(self, pattern):
1872
original_registry = filters._reset_registry()
1873
def restore_registry():
1874
filters._reset_registry(original_registry)
1875
self.addCleanup(restore_registry)
1876
def rot13(chunks, context=None):
1877
return [''.join(chunks).encode('rot13')]
1878
rot13filter = filters.ContentFilter(rot13, rot13)
1879
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1880
os.mkdir(self.test_home_dir + '/.bazaar')
1881
rules_filename = self.test_home_dir + '/.bazaar/rules'
1882
f = open(rules_filename, 'wb')
1883
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1885
def uninstall_rules():
1886
os.remove(rules_filename)
1888
self.addCleanup(uninstall_rules)
1891
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1892
"""build_tree will not hardlink files that have content filtering rules
1893
applied to them (but will still hardlink other files from the same tree
1896
self.requireFeature(HardlinkFeature)
1897
self.install_rot13_content_filter('file1')
1898
source = self.create_ab_tree()
1899
target = self.make_branch_and_tree('target')
1900
revision_tree = source.basis_tree()
1901
revision_tree.lock_read()
1902
self.addCleanup(revision_tree.unlock)
1903
build_tree(revision_tree, target, source, hardlink=True)
1905
self.addCleanup(target.unlock)
1906
self.assertEqual([], list(target.iter_changes(revision_tree)))
1907
source_stat = os.stat('source/file1')
1908
target_stat = os.stat('target/file1')
1909
self.assertNotEqual(source_stat, target_stat)
1910
source_stat = os.stat('source/file2')
1911
target_stat = os.stat('target/file2')
1912
self.assertEqualStat(source_stat, target_stat)
1914
def test_case_insensitive_build_tree_inventory(self):
1915
if (tests.CaseInsensitiveFilesystemFeature.available()
1916
or tests.CaseInsCasePresFilenameFeature.available()):
1917
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1918
source = self.make_branch_and_tree('source')
1919
self.build_tree(['source/file', 'source/FILE'])
1920
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1921
source.commit('added files')
1922
# Don't try this at home, kids!
1923
# Force the tree to report that it is case insensitive
1924
target = self.make_branch_and_tree('target')
1925
target.case_sensitive = False
1926
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1927
self.assertEqual('file.moved', target.id2path('lower-id'))
1928
self.assertEqual('FILE', target.id2path('upper-id'))
1931
class TestCommitTransform(tests.TestCaseWithTransport):
1933
def get_branch(self):
1934
tree = self.make_branch_and_tree('tree')
1936
self.addCleanup(tree.unlock)
1937
tree.commit('empty commit')
1940
def get_branch_and_transform(self):
1941
branch = self.get_branch()
1942
tt = TransformPreview(branch.basis_tree())
1943
self.addCleanup(tt.finalize)
1946
def test_commit_wrong_basis(self):
1947
branch = self.get_branch()
1948
basis = branch.repository.revision_tree(
1949
_mod_revision.NULL_REVISION)
1950
tt = TransformPreview(basis)
1951
self.addCleanup(tt.finalize)
1952
e = self.assertRaises(ValueError, tt.commit, branch, '')
1953
self.assertEqual('TreeTransform not based on branch basis: null:',
1956
def test_empy_commit(self):
1957
branch, tt = self.get_branch_and_transform()
1958
rev = tt.commit(branch, 'my message')
1959
self.assertEqual(2, branch.revno())
1960
repo = branch.repository
1961
self.assertEqual('my message', repo.get_revision(rev).message)
1963
def test_merge_parents(self):
1964
branch, tt = self.get_branch_and_transform()
1965
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
1966
self.assertEqual(['rev1b', 'rev1c'],
1967
branch.basis_tree().get_parent_ids()[1:])
1969
def test_first_commit(self):
1970
branch = self.make_branch('branch')
1972
self.addCleanup(branch.unlock)
1973
tt = TransformPreview(branch.basis_tree())
1974
self.addCleanup(tt.finalize)
1975
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
1976
rev = tt.commit(branch, 'my message')
1977
self.assertEqual([], branch.basis_tree().get_parent_ids())
1978
self.assertNotEqual(_mod_revision.NULL_REVISION,
1979
branch.last_revision())
1981
def test_first_commit_with_merge_parents(self):
1982
branch = self.make_branch('branch')
1984
self.addCleanup(branch.unlock)
1985
tt = TransformPreview(branch.basis_tree())
1986
self.addCleanup(tt.finalize)
1987
e = self.assertRaises(ValueError, tt.commit, branch,
1988
'my message', ['rev1b-id'])
1989
self.assertEqual('Cannot supply merge parents for first commit.',
1991
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
1993
def test_add_files(self):
1994
branch, tt = self.get_branch_and_transform()
1995
tt.new_file('file', tt.root, 'contents', 'file-id')
1996
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
1997
if SymlinkFeature.available():
1998
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
1999
rev = tt.commit(branch, 'message')
2000
tree = branch.basis_tree()
2001
self.assertEqual('file', tree.id2path('file-id'))
2002
self.assertEqual('contents', tree.get_file_text('file-id'))
2003
self.assertEqual('dir', tree.id2path('dir-id'))
2004
if SymlinkFeature.available():
2005
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2006
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2008
def test_add_unversioned(self):
2009
branch, tt = self.get_branch_and_transform()
2010
tt.new_file('file', tt.root, 'contents')
2011
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2012
'message', strict=True)
2014
def test_modify_strict(self):
2015
branch, tt = self.get_branch_and_transform()
2016
tt.new_file('file', tt.root, 'contents', 'file-id')
2017
tt.commit(branch, 'message', strict=True)
2018
tt = TransformPreview(branch.basis_tree())
2019
self.addCleanup(tt.finalize)
2020
trans_id = tt.trans_id_file_id('file-id')
2021
tt.delete_contents(trans_id)
2022
tt.create_file('contents', trans_id)
2023
tt.commit(branch, 'message', strict=True)
2025
def test_commit_malformed(self):
2026
"""Committing a malformed transform should raise an exception.
2028
In this case, we are adding a file without adding its parent.
2030
branch, tt = self.get_branch_and_transform()
2031
parent_id = tt.trans_id_file_id('parent-id')
2032
tt.new_file('file', parent_id, 'contents', 'file-id')
2033
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2037
class MockTransform(object):
2039
def has_named_child(self, by_parent, parent_id, name):
2040
for child_id in by_parent[parent_id]:
2044
elif name == "name.~%s~" % child_id:
2049
class MockEntry(object):
2051
object.__init__(self)
2055
class TestGetBackupName(TestCase):
2056
def test_get_backup_name(self):
2057
tt = MockTransform()
2058
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2059
self.assertEqual(name, 'name.~1~')
2060
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2061
self.assertEqual(name, 'name.~2~')
2062
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2063
self.assertEqual(name, 'name.~1~')
2064
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2065
self.assertEqual(name, 'name.~1~')
2066
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2067
self.assertEqual(name, 'name.~4~')
2070
class TestFileMover(tests.TestCaseWithTransport):
2072
def test_file_mover(self):
2073
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2074
mover = _FileMover()
2075
mover.rename('a', 'q')
2076
self.failUnlessExists('q')
2077
self.failIfExists('a')
2078
self.failUnlessExists('q/b')
2079
self.failUnlessExists('c')
2080
self.failUnlessExists('c/d')
2082
def test_pre_delete_rollback(self):
2083
self.build_tree(['a/'])
2084
mover = _FileMover()
2085
mover.pre_delete('a', 'q')
2086
self.failUnlessExists('q')
2087
self.failIfExists('a')
2089
self.failIfExists('q')
2090
self.failUnlessExists('a')
2092
def test_apply_deletions(self):
2093
self.build_tree(['a/', 'b/'])
2094
mover = _FileMover()
2095
mover.pre_delete('a', 'q')
2096
mover.pre_delete('b', 'r')
2097
self.failUnlessExists('q')
2098
self.failUnlessExists('r')
2099
self.failIfExists('a')
2100
self.failIfExists('b')
2101
mover.apply_deletions()
2102
self.failIfExists('q')
2103
self.failIfExists('r')
2104
self.failIfExists('a')
2105
self.failIfExists('b')
2107
def test_file_mover_rollback(self):
2108
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2109
mover = _FileMover()
2110
mover.rename('c/d', 'c/f')
2111
mover.rename('c/e', 'c/d')
2113
mover.rename('a', 'c')
2114
except errors.FileExists, e:
2116
self.failUnlessExists('a')
2117
self.failUnlessExists('c/d')
2120
class Bogus(Exception):
2124
class TestTransformRollback(tests.TestCaseWithTransport):
2126
class ExceptionFileMover(_FileMover):
2128
def __init__(self, bad_source=None, bad_target=None):
2129
_FileMover.__init__(self)
2130
self.bad_source = bad_source
2131
self.bad_target = bad_target
2133
def rename(self, source, target):
2134
if (self.bad_source is not None and
2135
source.endswith(self.bad_source)):
2137
elif (self.bad_target is not None and
2138
target.endswith(self.bad_target)):
2141
_FileMover.rename(self, source, target)
2143
def test_rollback_rename(self):
2144
tree = self.make_branch_and_tree('.')
2145
self.build_tree(['a/', 'a/b'])
2146
tt = TreeTransform(tree)
2147
self.addCleanup(tt.finalize)
2148
a_id = tt.trans_id_tree_path('a')
2149
tt.adjust_path('c', tt.root, a_id)
2150
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2151
self.assertRaises(Bogus, tt.apply,
2152
_mover=self.ExceptionFileMover(bad_source='a'))
2153
self.failUnlessExists('a')
2154
self.failUnlessExists('a/b')
2156
self.failUnlessExists('c')
2157
self.failUnlessExists('c/d')
2159
def test_rollback_rename_into_place(self):
2160
tree = self.make_branch_and_tree('.')
2161
self.build_tree(['a/', 'a/b'])
2162
tt = TreeTransform(tree)
2163
self.addCleanup(tt.finalize)
2164
a_id = tt.trans_id_tree_path('a')
2165
tt.adjust_path('c', tt.root, a_id)
2166
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2167
self.assertRaises(Bogus, tt.apply,
2168
_mover=self.ExceptionFileMover(bad_target='c/d'))
2169
self.failUnlessExists('a')
2170
self.failUnlessExists('a/b')
2172
self.failUnlessExists('c')
2173
self.failUnlessExists('c/d')
2175
def test_rollback_deletion(self):
2176
tree = self.make_branch_and_tree('.')
2177
self.build_tree(['a/', 'a/b'])
2178
tt = TreeTransform(tree)
2179
self.addCleanup(tt.finalize)
2180
a_id = tt.trans_id_tree_path('a')
2181
tt.delete_contents(a_id)
2182
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2183
self.assertRaises(Bogus, tt.apply,
2184
_mover=self.ExceptionFileMover(bad_target='d'))
2185
self.failUnlessExists('a')
2186
self.failUnlessExists('a/b')
2188
def test_resolve_no_parent(self):
2189
wt = self.make_branch_and_tree('.')
2190
tt = TreeTransform(wt)
2191
self.addCleanup(tt.finalize)
2192
parent = tt.trans_id_file_id('parent-id')
2193
tt.new_file('file', parent, 'Contents')
2194
resolve_conflicts(tt)
2197
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2198
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2200
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2201
('', ''), ('directory', 'directory'), (False, None))
2204
class TestTransformPreview(tests.TestCaseWithTransport):
2206
def create_tree(self):
2207
tree = self.make_branch_and_tree('.')
2208
self.build_tree_contents([('a', 'content 1')])
2209
tree.set_root_id('TREE_ROOT')
2210
tree.add('a', 'a-id')
2211
tree.commit('rev1', rev_id='rev1')
2212
return tree.branch.repository.revision_tree('rev1')
2214
def get_empty_preview(self):
2215
repository = self.make_repository('repo')
2216
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2217
preview = TransformPreview(tree)
2218
self.addCleanup(preview.finalize)
2221
def test_transform_preview(self):
2222
revision_tree = self.create_tree()
2223
preview = TransformPreview(revision_tree)
2224
self.addCleanup(preview.finalize)
2226
def test_transform_preview_tree(self):
2227
revision_tree = self.create_tree()
2228
preview = TransformPreview(revision_tree)
2229
self.addCleanup(preview.finalize)
2230
preview.get_preview_tree()
2232
def test_transform_new_file(self):
2233
revision_tree = self.create_tree()
2234
preview = TransformPreview(revision_tree)
2235
self.addCleanup(preview.finalize)
2236
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2237
preview_tree = preview.get_preview_tree()
2238
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2240
preview_tree.get_file('file2-id').read(), 'content B\n')
2242
def test_diff_preview_tree(self):
2243
revision_tree = self.create_tree()
2244
preview = TransformPreview(revision_tree)
2245
self.addCleanup(preview.finalize)
2246
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2247
preview_tree = preview.get_preview_tree()
2249
show_diff_trees(revision_tree, preview_tree, out)
2250
lines = out.getvalue().splitlines()
2251
self.assertEqual(lines[0], "=== added file 'file2'")
2252
# 3 lines of diff administrivia
2253
self.assertEqual(lines[4], "+content B")
2255
def test_transform_conflicts(self):
2256
revision_tree = self.create_tree()
2257
preview = TransformPreview(revision_tree)
2258
self.addCleanup(preview.finalize)
2259
preview.new_file('a', preview.root, 'content 2')
2260
resolve_conflicts(preview)
2261
trans_id = preview.trans_id_file_id('a-id')
2262
self.assertEqual('a.moved', preview.final_name(trans_id))
2264
def get_tree_and_preview_tree(self):
2265
revision_tree = self.create_tree()
2266
preview = TransformPreview(revision_tree)
2267
self.addCleanup(preview.finalize)
2268
a_trans_id = preview.trans_id_file_id('a-id')
2269
preview.delete_contents(a_trans_id)
2270
preview.create_file('b content', a_trans_id)
2271
preview_tree = preview.get_preview_tree()
2272
return revision_tree, preview_tree
2274
def test_iter_changes(self):
2275
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2276
root = revision_tree.inventory.root.file_id
2277
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2278
(root, root), ('a', 'a'), ('file', 'file'),
2280
list(preview_tree.iter_changes(revision_tree)))
2282
def test_include_unchanged_succeeds(self):
2283
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2284
changes = preview_tree.iter_changes(revision_tree,
2285
include_unchanged=True)
2286
root = revision_tree.inventory.root.file_id
2288
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2290
def test_specific_files(self):
2291
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2292
changes = preview_tree.iter_changes(revision_tree,
2293
specific_files=[''])
2294
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2296
def test_want_unversioned(self):
2297
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2298
changes = preview_tree.iter_changes(revision_tree,
2299
want_unversioned=True)
2300
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2302
def test_ignore_extra_trees_no_specific_files(self):
2303
# extra_trees is harmless without specific_files, so we'll silently
2304
# accept it, even though we won't use it.
2305
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2306
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2308
def test_ignore_require_versioned_no_specific_files(self):
2309
# require_versioned is meaningless without specific_files.
2310
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2311
preview_tree.iter_changes(revision_tree, require_versioned=False)
2313
def test_ignore_pb(self):
2314
# pb could be supported, but TT.iter_changes doesn't support it.
2315
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2316
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2318
def test_kind(self):
2319
revision_tree = self.create_tree()
2320
preview = TransformPreview(revision_tree)
2321
self.addCleanup(preview.finalize)
2322
preview.new_file('file', preview.root, 'contents', 'file-id')
2323
preview.new_directory('directory', preview.root, 'dir-id')
2324
preview_tree = preview.get_preview_tree()
2325
self.assertEqual('file', preview_tree.kind('file-id'))
2326
self.assertEqual('directory', preview_tree.kind('dir-id'))
2328
def test_get_file_mtime(self):
2329
preview = self.get_empty_preview()
2330
file_trans_id = preview.new_file('file', preview.root, 'contents',
2332
limbo_path = preview._limbo_name(file_trans_id)
2333
preview_tree = preview.get_preview_tree()
2334
self.assertEqual(os.stat(limbo_path).st_mtime,
2335
preview_tree.get_file_mtime('file-id'))
2337
def test_get_file_mtime_renamed(self):
2338
work_tree = self.make_branch_and_tree('tree')
2339
self.build_tree(['tree/file'])
2340
work_tree.add('file', 'file-id')
2341
preview = TransformPreview(work_tree)
2342
self.addCleanup(preview.finalize)
2343
file_trans_id = preview.trans_id_tree_file_id('file-id')
2344
preview.adjust_path('renamed', preview.root, file_trans_id)
2345
preview_tree = preview.get_preview_tree()
2346
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2347
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2349
def test_get_file(self):
2350
preview = self.get_empty_preview()
2351
preview.new_file('file', preview.root, 'contents', 'file-id')
2352
preview_tree = preview.get_preview_tree()
2353
tree_file = preview_tree.get_file('file-id')
2355
self.assertEqual('contents', tree_file.read())
2359
def test_get_symlink_target(self):
2360
self.requireFeature(SymlinkFeature)
2361
preview = self.get_empty_preview()
2362
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2363
preview_tree = preview.get_preview_tree()
2364
self.assertEqual('target',
2365
preview_tree.get_symlink_target('symlink-id'))
2367
def test_all_file_ids(self):
2368
tree = self.make_branch_and_tree('tree')
2369
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2370
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2371
preview = TransformPreview(tree)
2372
self.addCleanup(preview.finalize)
2373
preview.unversion_file(preview.trans_id_file_id('b-id'))
2374
c_trans_id = preview.trans_id_file_id('c-id')
2375
preview.unversion_file(c_trans_id)
2376
preview.version_file('c-id', c_trans_id)
2377
preview_tree = preview.get_preview_tree()
2378
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2379
preview_tree.all_file_ids())
2381
def test_path2id_deleted_unchanged(self):
2382
tree = self.make_branch_and_tree('tree')
2383
self.build_tree(['tree/unchanged', 'tree/deleted'])
2384
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2385
preview = TransformPreview(tree)
2386
self.addCleanup(preview.finalize)
2387
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2388
preview_tree = preview.get_preview_tree()
2389
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2390
self.assertIs(None, preview_tree.path2id('deleted'))
2392
def test_path2id_created(self):
2393
tree = self.make_branch_and_tree('tree')
2394
self.build_tree(['tree/unchanged'])
2395
tree.add(['unchanged'], ['unchanged-id'])
2396
preview = TransformPreview(tree)
2397
self.addCleanup(preview.finalize)
2398
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2399
'contents', 'new-id')
2400
preview_tree = preview.get_preview_tree()
2401
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2403
def test_path2id_moved(self):
2404
tree = self.make_branch_and_tree('tree')
2405
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2406
tree.add(['old_parent', 'old_parent/child'],
2407
['old_parent-id', 'child-id'])
2408
preview = TransformPreview(tree)
2409
self.addCleanup(preview.finalize)
2410
new_parent = preview.new_directory('new_parent', preview.root,
2412
preview.adjust_path('child', new_parent,
2413
preview.trans_id_file_id('child-id'))
2414
preview_tree = preview.get_preview_tree()
2415
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2416
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2418
def test_path2id_renamed_parent(self):
2419
tree = self.make_branch_and_tree('tree')
2420
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2421
tree.add(['old_name', 'old_name/child'],
2422
['parent-id', 'child-id'])
2423
preview = TransformPreview(tree)
2424
self.addCleanup(preview.finalize)
2425
preview.adjust_path('new_name', preview.root,
2426
preview.trans_id_file_id('parent-id'))
2427
preview_tree = preview.get_preview_tree()
2428
self.assertIs(None, preview_tree.path2id('old_name/child'))
2429
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2431
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2432
preview_tree = tt.get_preview_tree()
2433
preview_result = list(preview_tree.iter_entries_by_dir(
2437
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2438
self.assertEqual(actual_result, preview_result)
2440
def test_iter_entries_by_dir_new(self):
2441
tree = self.make_branch_and_tree('tree')
2442
tt = TreeTransform(tree)
2443
tt.new_file('new', tt.root, 'contents', 'new-id')
2444
self.assertMatchingIterEntries(tt)
2446
def test_iter_entries_by_dir_deleted(self):
2447
tree = self.make_branch_and_tree('tree')
2448
self.build_tree(['tree/deleted'])
2449
tree.add('deleted', 'deleted-id')
2450
tt = TreeTransform(tree)
2451
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2452
self.assertMatchingIterEntries(tt)
2454
def test_iter_entries_by_dir_unversioned(self):
2455
tree = self.make_branch_and_tree('tree')
2456
self.build_tree(['tree/removed'])
2457
tree.add('removed', 'removed-id')
2458
tt = TreeTransform(tree)
2459
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2460
self.assertMatchingIterEntries(tt)
2462
def test_iter_entries_by_dir_moved(self):
2463
tree = self.make_branch_and_tree('tree')
2464
self.build_tree(['tree/moved', 'tree/new_parent/'])
2465
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2466
tt = TreeTransform(tree)
2467
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2468
tt.trans_id_file_id('moved-id'))
2469
self.assertMatchingIterEntries(tt)
2471
def test_iter_entries_by_dir_specific_file_ids(self):
2472
tree = self.make_branch_and_tree('tree')
2473
tree.set_root_id('tree-root-id')
2474
self.build_tree(['tree/parent/', 'tree/parent/child'])
2475
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2476
tt = TreeTransform(tree)
2477
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2479
def test_symlink_content_summary(self):
2480
self.requireFeature(SymlinkFeature)
2481
preview = self.get_empty_preview()
2482
preview.new_symlink('path', preview.root, 'target', 'path-id')
2483
summary = preview.get_preview_tree().path_content_summary('path')
2484
self.assertEqual(('symlink', None, None, 'target'), summary)
2486
def test_missing_content_summary(self):
2487
preview = self.get_empty_preview()
2488
summary = preview.get_preview_tree().path_content_summary('path')
2489
self.assertEqual(('missing', None, None, None), summary)
2491
def test_deleted_content_summary(self):
2492
tree = self.make_branch_and_tree('tree')
2493
self.build_tree(['tree/path/'])
2495
preview = TransformPreview(tree)
2496
self.addCleanup(preview.finalize)
2497
preview.delete_contents(preview.trans_id_tree_path('path'))
2498
summary = preview.get_preview_tree().path_content_summary('path')
2499
self.assertEqual(('missing', None, None, None), summary)
2501
def test_file_content_summary_executable(self):
2502
preview = self.get_empty_preview()
2503
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2504
preview.set_executability(True, path_id)
2505
summary = preview.get_preview_tree().path_content_summary('path')
2506
self.assertEqual(4, len(summary))
2507
self.assertEqual('file', summary[0])
2508
# size must be known
2509
self.assertEqual(len('contents'), summary[1])
2511
self.assertEqual(True, summary[2])
2512
# will not have hash (not cheap to determine)
2513
self.assertIs(None, summary[3])
2515
def test_change_executability(self):
2516
tree = self.make_branch_and_tree('tree')
2517
self.build_tree(['tree/path'])
2519
preview = TransformPreview(tree)
2520
self.addCleanup(preview.finalize)
2521
path_id = preview.trans_id_tree_path('path')
2522
preview.set_executability(True, path_id)
2523
summary = preview.get_preview_tree().path_content_summary('path')
2524
self.assertEqual(True, summary[2])
2526
def test_file_content_summary_non_exec(self):
2527
preview = self.get_empty_preview()
2528
preview.new_file('path', preview.root, 'contents', 'path-id')
2529
summary = preview.get_preview_tree().path_content_summary('path')
2530
self.assertEqual(4, len(summary))
2531
self.assertEqual('file', summary[0])
2532
# size must be known
2533
self.assertEqual(len('contents'), summary[1])
2535
self.assertEqual(False, summary[2])
2536
# will not have hash (not cheap to determine)
2537
self.assertIs(None, summary[3])
2539
def test_dir_content_summary(self):
2540
preview = self.get_empty_preview()
2541
preview.new_directory('path', preview.root, 'path-id')
2542
summary = preview.get_preview_tree().path_content_summary('path')
2543
self.assertEqual(('directory', None, None, None), summary)
2545
def test_tree_content_summary(self):
2546
preview = self.get_empty_preview()
2547
path = preview.new_directory('path', preview.root, 'path-id')
2548
preview.set_tree_reference('rev-1', path)
2549
summary = preview.get_preview_tree().path_content_summary('path')
2550
self.assertEqual(4, len(summary))
2551
self.assertEqual('tree-reference', summary[0])
2553
def test_annotate(self):
2554
tree = self.make_branch_and_tree('tree')
2555
self.build_tree_contents([('tree/file', 'a\n')])
2556
tree.add('file', 'file-id')
2557
tree.commit('a', rev_id='one')
2558
self.build_tree_contents([('tree/file', 'a\nb\n')])
2559
preview = TransformPreview(tree)
2560
self.addCleanup(preview.finalize)
2561
file_trans_id = preview.trans_id_file_id('file-id')
2562
preview.delete_contents(file_trans_id)
2563
preview.create_file('a\nb\nc\n', file_trans_id)
2564
preview_tree = preview.get_preview_tree()
2570
annotation = preview_tree.annotate_iter('file-id', 'me:')
2571
self.assertEqual(expected, annotation)
2573
def test_annotate_missing(self):
2574
preview = self.get_empty_preview()
2575
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2576
preview_tree = preview.get_preview_tree()
2582
annotation = preview_tree.annotate_iter('file-id', 'me:')
2583
self.assertEqual(expected, annotation)
2585
def test_annotate_rename(self):
2586
tree = self.make_branch_and_tree('tree')
2587
self.build_tree_contents([('tree/file', 'a\n')])
2588
tree.add('file', 'file-id')
2589
tree.commit('a', rev_id='one')
2590
preview = TransformPreview(tree)
2591
self.addCleanup(preview.finalize)
2592
file_trans_id = preview.trans_id_file_id('file-id')
2593
preview.adjust_path('newname', preview.root, file_trans_id)
2594
preview_tree = preview.get_preview_tree()
2598
annotation = preview_tree.annotate_iter('file-id', 'me:')
2599
self.assertEqual(expected, annotation)
2601
def test_annotate_deleted(self):
2602
tree = self.make_branch_and_tree('tree')
2603
self.build_tree_contents([('tree/file', 'a\n')])
2604
tree.add('file', 'file-id')
2605
tree.commit('a', rev_id='one')
2606
self.build_tree_contents([('tree/file', 'a\nb\n')])
2607
preview = TransformPreview(tree)
2608
self.addCleanup(preview.finalize)
2609
file_trans_id = preview.trans_id_file_id('file-id')
2610
preview.delete_contents(file_trans_id)
2611
preview_tree = preview.get_preview_tree()
2612
annotation = preview_tree.annotate_iter('file-id', 'me:')
2613
self.assertIs(None, annotation)
2615
def test_stored_kind(self):
2616
preview = self.get_empty_preview()
2617
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2618
preview_tree = preview.get_preview_tree()
2619
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2621
def test_is_executable(self):
2622
preview = self.get_empty_preview()
2623
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2624
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2625
preview_tree = preview.get_preview_tree()
2626
self.assertEqual(True, preview_tree.is_executable('file-id'))
2628
def test_get_set_parent_ids(self):
2629
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2630
self.assertEqual([], preview_tree.get_parent_ids())
2631
preview_tree.set_parent_ids(['rev-1'])
2632
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2634
def test_plan_file_merge(self):
2635
work_a = self.make_branch_and_tree('wta')
2636
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2637
work_a.add('file', 'file-id')
2638
base_id = work_a.commit('base version')
2639
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2640
preview = TransformPreview(work_a)
2641
self.addCleanup(preview.finalize)
2642
trans_id = preview.trans_id_file_id('file-id')
2643
preview.delete_contents(trans_id)
2644
preview.create_file('b\nc\nd\ne\n', trans_id)
2645
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2646
tree_a = preview.get_preview_tree()
2647
tree_a.set_parent_ids([base_id])
2649
('killed-a', 'a\n'),
2650
('killed-b', 'b\n'),
2651
('unchanged', 'c\n'),
2652
('unchanged', 'd\n'),
2655
], list(tree_a.plan_file_merge('file-id', tree_b)))
2657
def test_plan_file_merge_revision_tree(self):
2658
work_a = self.make_branch_and_tree('wta')
2659
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2660
work_a.add('file', 'file-id')
2661
base_id = work_a.commit('base version')
2662
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2663
preview = TransformPreview(work_a.basis_tree())
2664
self.addCleanup(preview.finalize)
2665
trans_id = preview.trans_id_file_id('file-id')
2666
preview.delete_contents(trans_id)
2667
preview.create_file('b\nc\nd\ne\n', trans_id)
2668
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2669
tree_a = preview.get_preview_tree()
2670
tree_a.set_parent_ids([base_id])
2672
('killed-a', 'a\n'),
2673
('killed-b', 'b\n'),
2674
('unchanged', 'c\n'),
2675
('unchanged', 'd\n'),
2678
], list(tree_a.plan_file_merge('file-id', tree_b)))
2680
def test_walkdirs(self):
2681
preview = self.get_empty_preview()
2682
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2683
# FIXME: new_directory should mark root.
2684
preview.adjust_path('', ROOT_PARENT, root)
2685
preview_tree = preview.get_preview_tree()
2686
file_trans_id = preview.new_file('a', preview.root, 'contents',
2688
expected = [(('', 'tree-root'),
2689
[('a', 'a', 'file', None, 'a-id', 'file')])]
2690
self.assertEqual(expected, list(preview_tree.walkdirs()))
2692
def test_extras(self):
2693
work_tree = self.make_branch_and_tree('tree')
2694
self.build_tree(['tree/removed-file', 'tree/existing-file',
2695
'tree/not-removed-file'])
2696
work_tree.add(['removed-file', 'not-removed-file'])
2697
preview = TransformPreview(work_tree)
2698
self.addCleanup(preview.finalize)
2699
preview.new_file('new-file', preview.root, 'contents')
2700
preview.new_file('new-versioned-file', preview.root, 'contents',
2702
tree = preview.get_preview_tree()
2703
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2704
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2707
def test_merge_into_preview(self):
2708
work_tree = self.make_branch_and_tree('tree')
2709
self.build_tree_contents([('tree/file','b\n')])
2710
work_tree.add('file', 'file-id')
2711
work_tree.commit('first commit')
2712
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2713
self.build_tree_contents([('child/file','b\nc\n')])
2714
child_tree.commit('child commit')
2715
child_tree.lock_write()
2716
self.addCleanup(child_tree.unlock)
2717
work_tree.lock_write()
2718
self.addCleanup(work_tree.unlock)
2719
preview = TransformPreview(work_tree)
2720
self.addCleanup(preview.finalize)
2721
file_trans_id = preview.trans_id_file_id('file-id')
2722
preview.delete_contents(file_trans_id)
2723
preview.create_file('a\nb\n', file_trans_id)
2724
pb = progress.DummyProgress()
2725
preview_tree = preview.get_preview_tree()
2726
merger = Merger.from_revision_ids(pb, preview_tree,
2727
child_tree.branch.last_revision(),
2728
other_branch=child_tree.branch,
2729
tree_branch=work_tree.branch)
2730
merger.merge_type = Merge3Merger
2731
tt = merger.make_merger().make_preview_transform()
2732
self.addCleanup(tt.finalize)
2733
final_tree = tt.get_preview_tree()
2734
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2736
def test_merge_preview_into_workingtree(self):
2737
tree = self.make_branch_and_tree('tree')
2738
tree.set_root_id('TREE_ROOT')
2739
tt = TransformPreview(tree)
2740
self.addCleanup(tt.finalize)
2741
tt.new_file('name', tt.root, 'content', 'file-id')
2742
tree2 = self.make_branch_and_tree('tree2')
2743
tree2.set_root_id('TREE_ROOT')
2744
pb = progress.DummyProgress()
2745
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2746
pb, tree.basis_tree())
2747
merger.merge_type = Merge3Merger
2750
def test_merge_preview_into_workingtree_handles_conflicts(self):
2751
tree = self.make_branch_and_tree('tree')
2752
self.build_tree_contents([('tree/foo', 'bar')])
2753
tree.add('foo', 'foo-id')
2755
tt = TransformPreview(tree)
2756
self.addCleanup(tt.finalize)
2757
trans_id = tt.trans_id_file_id('foo-id')
2758
tt.delete_contents(trans_id)
2759
tt.create_file('baz', trans_id)
2760
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2761
self.build_tree_contents([('tree2/foo', 'qux')])
2762
pb = progress.DummyProgress()
2763
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2764
pb, tree.basis_tree())
2765
merger.merge_type = Merge3Merger
2768
def test_is_executable(self):
2769
tree = self.make_branch_and_tree('tree')
2770
preview = TransformPreview(tree)
2771
self.addCleanup(preview.finalize)
2772
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2773
preview_tree = preview.get_preview_tree()
2774
self.assertEqual(False, preview_tree.is_executable('baz-id',
2776
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2778
def test_commit_preview_tree(self):
2779
tree = self.make_branch_and_tree('tree')
2780
rev_id = tree.commit('rev1')
2781
tree.branch.lock_write()
2782
self.addCleanup(tree.branch.unlock)
2783
tt = TransformPreview(tree)
2784
tt.new_file('file', tt.root, 'contents', 'file_id')
2785
self.addCleanup(tt.finalize)
2786
preview = tt.get_preview_tree()
2787
preview.set_parent_ids([rev_id])
2788
builder = tree.branch.get_commit_builder([rev_id])
2789
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2790
builder.finish_inventory()
2791
rev2_id = builder.commit('rev2')
2792
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2793
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2795
def test_ascii_limbo_paths(self):
2796
self.requireFeature(tests.UnicodeFilenameFeature)
2797
branch = self.make_branch('any')
2798
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2799
tt = TransformPreview(tree)
2800
self.addCleanup(tt.finalize)
2801
foo_id = tt.new_directory('', ROOT_PARENT)
2802
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2803
limbo_path = tt._limbo_name(bar_id)
2804
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2807
class FakeSerializer(object):
2808
"""Serializer implementation that simply returns the input.
2810
The input is returned in the order used by pack.ContainerPushParser.
2813
def bytes_record(bytes, names):
2817
class TestSerializeTransform(tests.TestCaseWithTransport):
2819
_test_needs_features = [tests.UnicodeFilenameFeature]
2821
def get_preview(self, tree=None):
2823
tree = self.make_branch_and_tree('tree')
2824
tt = TransformPreview(tree)
2825
self.addCleanup(tt.finalize)
2828
def assertSerializesTo(self, expected, tt):
2829
records = list(tt.serialize(FakeSerializer()))
2830
self.assertEqual(expected, records)
2833
def default_attribs():
2838
'_new_executability': {},
2840
'_tree_path_ids': {'': 'new-0'},
2842
'_removed_contents': [],
2843
'_non_present_ids': {},
2846
def make_records(self, attribs, contents):
2848
(((('attribs'),),), bencode.bencode(attribs))]
2849
records.extend([(((n, k),), c) for n, k, c in contents])
2852
def creation_records(self):
2853
attribs = self.default_attribs()
2854
attribs['_id_number'] = 3
2855
attribs['_new_name'] = {
2856
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2857
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2858
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2859
attribs['_new_executability'] = {'new-1': 1}
2861
('new-1', 'file', 'i 1\nbar\n'),
2862
('new-2', 'directory', ''),
2864
return self.make_records(attribs, contents)
2866
def test_serialize_creation(self):
2867
tt = self.get_preview()
2868
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2869
tt.new_directory('qux', tt.root, 'quxx')
2870
self.assertSerializesTo(self.creation_records(), tt)
2872
def test_deserialize_creation(self):
2873
tt = self.get_preview()
2874
tt.deserialize(iter(self.creation_records()))
2875
self.assertEqual(3, tt._id_number)
2876
self.assertEqual({'new-1': u'foo\u1234',
2877
'new-2': 'qux'}, tt._new_name)
2878
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2879
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2880
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2881
self.assertEqual({'new-1': True}, tt._new_executability)
2882
self.assertEqual({'new-1': 'file',
2883
'new-2': 'directory'}, tt._new_contents)
2884
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2886
foo_content = foo_limbo.read()
2889
self.assertEqual('bar', foo_content)
2891
def symlink_creation_records(self):
2892
attribs = self.default_attribs()
2893
attribs['_id_number'] = 2
2894
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2895
attribs['_new_parent'] = {'new-1': 'new-0'}
2896
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2897
return self.make_records(attribs, contents)
2899
def test_serialize_symlink_creation(self):
2900
self.requireFeature(tests.SymlinkFeature)
2901
tt = self.get_preview()
2902
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2903
self.assertSerializesTo(self.symlink_creation_records(), tt)
2905
def test_deserialize_symlink_creation(self):
2906
self.requireFeature(tests.SymlinkFeature)
2907
tt = self.get_preview()
2908
tt.deserialize(iter(self.symlink_creation_records()))
2909
abspath = tt._limbo_name('new-1')
2910
foo_content = osutils.readlink(abspath)
2911
self.assertEqual(u'bar\u1234', foo_content)
2913
def make_destruction_preview(self):
2914
tree = self.make_branch_and_tree('.')
2915
self.build_tree([u'foo\u1234', 'bar'])
2916
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2917
return self.get_preview(tree)
2919
def destruction_records(self):
2920
attribs = self.default_attribs()
2921
attribs['_id_number'] = 3
2922
attribs['_removed_id'] = ['new-1']
2923
attribs['_removed_contents'] = ['new-2']
2924
attribs['_tree_path_ids'] = {
2926
u'foo\u1234'.encode('utf-8'): 'new-1',
2929
return self.make_records(attribs, [])
2931
def test_serialize_destruction(self):
2932
tt = self.make_destruction_preview()
2933
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2934
tt.unversion_file(foo_trans_id)
2935
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2936
tt.delete_contents(bar_trans_id)
2937
self.assertSerializesTo(self.destruction_records(), tt)
2939
def test_deserialize_destruction(self):
2940
tt = self.make_destruction_preview()
2941
tt.deserialize(iter(self.destruction_records()))
2942
self.assertEqual({u'foo\u1234': 'new-1',
2944
'': tt.root}, tt._tree_path_ids)
2945
self.assertEqual({'new-1': u'foo\u1234',
2947
tt.root: ''}, tt._tree_id_paths)
2948
self.assertEqual(set(['new-1']), tt._removed_id)
2949
self.assertEqual(set(['new-2']), tt._removed_contents)
2951
def missing_records(self):
2952
attribs = self.default_attribs()
2953
attribs['_id_number'] = 2
2954
attribs['_non_present_ids'] = {
2956
return self.make_records(attribs, [])
2958
def test_serialize_missing(self):
2959
tt = self.get_preview()
2960
boo_trans_id = tt.trans_id_file_id('boo')
2961
self.assertSerializesTo(self.missing_records(), tt)
2963
def test_deserialize_missing(self):
2964
tt = self.get_preview()
2965
tt.deserialize(iter(self.missing_records()))
2966
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2968
def make_modification_preview(self):
2969
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2970
LINES_TWO = 'z\nbb\nx\ndd\n'
2971
tree = self.make_branch_and_tree('tree')
2972
self.build_tree_contents([('tree/file', LINES_ONE)])
2973
tree.add('file', 'file-id')
2974
return self.get_preview(tree), LINES_TWO
2976
def modification_records(self):
2977
attribs = self.default_attribs()
2978
attribs['_id_number'] = 2
2979
attribs['_tree_path_ids'] = {
2982
attribs['_removed_contents'] = ['new-1']
2983
contents = [('new-1', 'file',
2984
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2985
return self.make_records(attribs, contents)
2987
def test_serialize_modification(self):
2988
tt, LINES = self.make_modification_preview()
2989
trans_id = tt.trans_id_file_id('file-id')
2990
tt.delete_contents(trans_id)
2991
tt.create_file(LINES, trans_id)
2992
self.assertSerializesTo(self.modification_records(), tt)
2994
def test_deserialize_modification(self):
2995
tt, LINES = self.make_modification_preview()
2996
tt.deserialize(iter(self.modification_records()))
2997
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2999
def make_kind_change_preview(self):
3000
LINES = 'a\nb\nc\nd\n'
3001
tree = self.make_branch_and_tree('tree')
3002
self.build_tree(['tree/foo/'])
3003
tree.add('foo', 'foo-id')
3004
return self.get_preview(tree), LINES
3006
def kind_change_records(self):
3007
attribs = self.default_attribs()
3008
attribs['_id_number'] = 2
3009
attribs['_tree_path_ids'] = {
3012
attribs['_removed_contents'] = ['new-1']
3013
contents = [('new-1', 'file',
3014
'i 4\na\nb\nc\nd\n\n')]
3015
return self.make_records(attribs, contents)
3017
def test_serialize_kind_change(self):
3018
tt, LINES = self.make_kind_change_preview()
3019
trans_id = tt.trans_id_file_id('foo-id')
3020
tt.delete_contents(trans_id)
3021
tt.create_file(LINES, trans_id)
3022
self.assertSerializesTo(self.kind_change_records(), tt)
3024
def test_deserialize_kind_change(self):
3025
tt, LINES = self.make_kind_change_preview()
3026
tt.deserialize(iter(self.kind_change_records()))
3027
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3029
def make_add_contents_preview(self):
3030
LINES = 'a\nb\nc\nd\n'
3031
tree = self.make_branch_and_tree('tree')
3032
self.build_tree(['tree/foo'])
3034
os.unlink('tree/foo')
3035
return self.get_preview(tree), LINES
3037
def add_contents_records(self):
3038
attribs = self.default_attribs()
3039
attribs['_id_number'] = 2
3040
attribs['_tree_path_ids'] = {
3043
contents = [('new-1', 'file',
3044
'i 4\na\nb\nc\nd\n\n')]
3045
return self.make_records(attribs, contents)
3047
def test_serialize_add_contents(self):
3048
tt, LINES = self.make_add_contents_preview()
3049
trans_id = tt.trans_id_tree_path('foo')
3050
tt.create_file(LINES, trans_id)
3051
self.assertSerializesTo(self.add_contents_records(), tt)
3053
def test_deserialize_add_contents(self):
3054
tt, LINES = self.make_add_contents_preview()
3055
tt.deserialize(iter(self.add_contents_records()))
3056
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3058
def test_get_parents_lines(self):
3059
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3060
LINES_TWO = 'z\nbb\nx\ndd\n'
3061
tree = self.make_branch_and_tree('tree')
3062
self.build_tree_contents([('tree/file', LINES_ONE)])
3063
tree.add('file', 'file-id')
3064
tt = self.get_preview(tree)
3065
trans_id = tt.trans_id_tree_path('file')
3066
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3067
tt._get_parents_lines(trans_id))
3069
def test_get_parents_texts(self):
3070
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3071
LINES_TWO = 'z\nbb\nx\ndd\n'
3072
tree = self.make_branch_and_tree('tree')
3073
self.build_tree_contents([('tree/file', LINES_ONE)])
3074
tree.add('file', 'file-id')
3075
tt = self.get_preview(tree)
3076
trans_id = tt.trans_id_tree_path('file')
3077
self.assertEqual((LINES_ONE,),
3078
tt._get_parents_texts(trans_id))