1
# Copyright (C) 2006-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from StringIO import StringIO
28
revision as _mod_revision,
33
from bzrlib.bzrdir import BzrDir
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
35
UnversionedParent, ParentLoop, DeletingParent,
37
from bzrlib.diff import show_diff_trees
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
40
PathsNotVersionedError, ExistingLimbo,
41
ExistingPendingDeletion, ImmortalLimbo,
42
ImmortalPendingDeletion, LockError)
43
from bzrlib.osutils import file_kind, pathjoin
44
from bzrlib.merge import Merge3Merger, Merger
45
from bzrlib.tests import (
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
resolve_conflicts, cook_conflicts,
54
build_tree, get_backup_name,
55
_FileMover, resolve_checkout,
56
TransformPreview, create_from_tree)
59
class TestTreeTransform(tests.TestCaseWithTransport):
62
super(TestTreeTransform, self).setUp()
63
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
66
def get_transform(self):
67
transform = TreeTransform(self.wt)
68
self.addCleanup(transform.finalize)
69
return transform, transform.root
71
def test_existing_limbo(self):
72
transform, root = self.get_transform()
73
limbo_name = transform._limbodir
74
deletion_path = transform._deletiondir
75
os.mkdir(pathjoin(limbo_name, 'hehe'))
76
self.assertRaises(ImmortalLimbo, transform.apply)
77
self.assertRaises(LockError, self.wt.unlock)
78
self.assertRaises(ExistingLimbo, self.get_transform)
79
self.assertRaises(LockError, self.wt.unlock)
80
os.rmdir(pathjoin(limbo_name, 'hehe'))
82
os.rmdir(deletion_path)
83
transform, root = self.get_transform()
86
def test_existing_pending_deletion(self):
87
transform, root = self.get_transform()
88
deletion_path = self._limbodir = urlutils.local_path_from_url(
89
transform._tree._transport.abspath('pending-deletion'))
90
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
91
self.assertRaises(ImmortalPendingDeletion, transform.apply)
92
self.assertRaises(LockError, self.wt.unlock)
93
self.assertRaises(ExistingPendingDeletion, self.get_transform)
96
transform, root = self.get_transform()
97
self.wt.lock_tree_write()
98
self.addCleanup(self.wt.unlock)
99
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
100
imaginary_id = transform.trans_id_tree_path('imaginary')
101
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
102
self.assertEqual(imaginary_id, imaginary_id2)
103
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
104
self.assertEqual(transform.final_kind(root), 'directory')
105
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
106
trans_id = transform.create_path('name', root)
107
self.assertIs(transform.final_file_id(trans_id), None)
108
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
109
transform.create_file('contents', trans_id)
110
transform.set_executability(True, trans_id)
111
transform.version_file('my_pretties', trans_id)
112
self.assertRaises(DuplicateKey, transform.version_file,
113
'my_pretties', trans_id)
114
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
115
self.assertEqual(transform.final_parent(trans_id), root)
116
self.assertIs(transform.final_parent(root), ROOT_PARENT)
117
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
118
oz_id = transform.create_path('oz', root)
119
transform.create_directory(oz_id)
120
transform.version_file('ozzie', oz_id)
121
trans_id2 = transform.create_path('name2', root)
122
transform.create_file('contents', trans_id2)
123
transform.set_executability(False, trans_id2)
124
transform.version_file('my_pretties2', trans_id2)
125
modified_paths = transform.apply().modified_paths
126
self.assertEqual('contents', self.wt.get_file_byname('name').read())
127
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
128
self.assertIs(self.wt.is_executable('my_pretties'), True)
129
self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
self.assertEqual(len(modified_paths), 3)
132
tree_mod_paths = [self.wt.id2abspath(f) for f in
133
('ozzie', 'my_pretties', 'my_pretties2')]
134
self.assertSubset(tree_mod_paths, modified_paths)
135
# is it safe to finalize repeatedly?
139
def test_change_root_id(self):
140
transform, root = self.get_transform()
141
self.assertNotEqual('new-root-id', self.wt.get_root_id())
142
transform.new_directory('', ROOT_PARENT, 'new-root-id')
143
transform.delete_contents(root)
144
transform.unversion_file(root)
145
transform.fixup_new_roots()
147
self.assertEqual('new-root-id', self.wt.get_root_id())
149
def test_change_root_id_add_files(self):
150
transform, root = self.get_transform()
151
self.assertNotEqual('new-root-id', self.wt.get_root_id())
152
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
153
transform.new_file('file', new_trans_id, ['new-contents\n'],
155
transform.delete_contents(root)
156
transform.unversion_file(root)
157
transform.fixup_new_roots()
159
self.assertEqual('new-root-id', self.wt.get_root_id())
160
self.assertEqual('new-file-id', self.wt.path2id('file'))
161
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
163
def test_add_two_roots(self):
164
transform, root = self.get_transform()
165
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
166
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
167
self.assertRaises(ValueError, transform.fixup_new_roots)
169
def test_hardlink(self):
170
self.requireFeature(HardlinkFeature)
171
transform, root = self.get_transform()
172
transform.new_file('file1', root, 'contents')
174
target = self.make_branch_and_tree('target')
175
target_transform = TreeTransform(target)
176
trans_id = target_transform.create_path('file1', target_transform.root)
177
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
178
target_transform.apply()
179
self.failUnlessExists('target/file1')
180
source_stat = os.stat(self.wt.abspath('file1'))
181
target_stat = os.stat('target/file1')
182
self.assertEqual(source_stat, target_stat)
184
def test_convenience(self):
185
transform, root = self.get_transform()
186
self.wt.lock_tree_write()
187
self.addCleanup(self.wt.unlock)
188
trans_id = transform.new_file('name', root, 'contents',
190
oz = transform.new_directory('oz', root, 'oz-id')
191
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
192
toto = transform.new_file('toto', dorothy, 'toto-contents',
195
self.assertEqual(len(transform.find_conflicts()), 0)
197
self.assertRaises(ReusingTransform, transform.find_conflicts)
198
self.assertEqual('contents', file(self.wt.abspath('name')).read())
199
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
200
self.assertIs(self.wt.is_executable('my_pretties'), True)
201
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
202
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
203
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
205
self.assertEqual('toto-contents',
206
self.wt.get_file_byname('oz/dorothy/toto').read())
207
self.assertIs(self.wt.is_executable('toto-id'), False)
209
def test_tree_reference(self):
210
transform, root = self.get_transform()
211
tree = transform._tree
212
trans_id = transform.new_directory('reference', root, 'subtree-id')
213
transform.set_tree_reference('subtree-revision', trans_id)
216
self.addCleanup(tree.unlock)
217
self.assertEqual('subtree-revision',
218
tree.inventory['subtree-id'].reference_revision)
220
def test_conflicts(self):
221
transform, root = self.get_transform()
222
trans_id = transform.new_file('name', root, 'contents',
224
self.assertEqual(len(transform.find_conflicts()), 0)
225
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
226
self.assertEqual(transform.find_conflicts(),
227
[('duplicate', trans_id, trans_id2, 'name')])
228
self.assertRaises(MalformedTransform, transform.apply)
229
transform.adjust_path('name', trans_id, trans_id2)
230
self.assertEqual(transform.find_conflicts(),
231
[('non-directory parent', trans_id)])
232
tinman_id = transform.trans_id_tree_path('tinman')
233
transform.adjust_path('name', tinman_id, trans_id2)
234
self.assertEqual(transform.find_conflicts(),
235
[('unversioned parent', tinman_id),
236
('missing parent', tinman_id)])
237
lion_id = transform.create_path('lion', root)
238
self.assertEqual(transform.find_conflicts(),
239
[('unversioned parent', tinman_id),
240
('missing parent', tinman_id)])
241
transform.adjust_path('name', lion_id, trans_id2)
242
self.assertEqual(transform.find_conflicts(),
243
[('unversioned parent', lion_id),
244
('missing parent', lion_id)])
245
transform.version_file("Courage", lion_id)
246
self.assertEqual(transform.find_conflicts(),
247
[('missing parent', lion_id),
248
('versioning no contents', lion_id)])
249
transform.adjust_path('name2', root, trans_id2)
250
self.assertEqual(transform.find_conflicts(),
251
[('versioning no contents', lion_id)])
252
transform.create_file('Contents, okay?', lion_id)
253
transform.adjust_path('name2', trans_id2, trans_id2)
254
self.assertEqual(transform.find_conflicts(),
255
[('parent loop', trans_id2),
256
('non-directory parent', trans_id2)])
257
transform.adjust_path('name2', root, trans_id2)
258
oz_id = transform.new_directory('oz', root)
259
transform.set_executability(True, oz_id)
260
self.assertEqual(transform.find_conflicts(),
261
[('unversioned executability', oz_id)])
262
transform.version_file('oz-id', oz_id)
263
self.assertEqual(transform.find_conflicts(),
264
[('non-file executability', oz_id)])
265
transform.set_executability(None, oz_id)
266
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
268
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
269
self.assertEqual('contents', file(self.wt.abspath('name')).read())
270
transform2, root = self.get_transform()
271
oz_id = transform2.trans_id_tree_file_id('oz-id')
272
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
273
result = transform2.find_conflicts()
274
fp = FinalPaths(transform2)
275
self.assert_('oz/tip' in transform2._tree_path_ids)
276
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
277
self.assertEqual(len(result), 2)
278
self.assertEqual((result[0][0], result[0][1]),
279
('duplicate', newtip))
280
self.assertEqual((result[1][0], result[1][2]),
281
('duplicate id', newtip))
282
transform2.finalize()
283
transform3 = TreeTransform(self.wt)
284
self.addCleanup(transform3.finalize)
285
oz_id = transform3.trans_id_tree_file_id('oz-id')
286
transform3.delete_contents(oz_id)
287
self.assertEqual(transform3.find_conflicts(),
288
[('missing parent', oz_id)])
289
root_id = transform3.root
290
tip_id = transform3.trans_id_tree_file_id('tip-id')
291
transform3.adjust_path('tip', root_id, tip_id)
294
def test_conflict_on_case_insensitive(self):
295
tree = self.make_branch_and_tree('tree')
296
# Don't try this at home, kids!
297
# Force the tree to report that it is case sensitive, for conflict
299
tree.case_sensitive = True
300
transform = TreeTransform(tree)
301
self.addCleanup(transform.finalize)
302
transform.new_file('file', transform.root, 'content')
303
transform.new_file('FiLe', transform.root, 'content')
304
result = transform.find_conflicts()
305
self.assertEqual([], result)
307
# Force the tree to report that it is case insensitive, for conflict
309
tree.case_sensitive = False
310
transform = TreeTransform(tree)
311
self.addCleanup(transform.finalize)
312
transform.new_file('file', transform.root, 'content')
313
transform.new_file('FiLe', transform.root, 'content')
314
result = transform.find_conflicts()
315
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
317
def test_conflict_on_case_insensitive_existing(self):
318
tree = self.make_branch_and_tree('tree')
319
self.build_tree(['tree/FiLe'])
320
# Don't try this at home, kids!
321
# Force the tree to report that it is case sensitive, for conflict
323
tree.case_sensitive = True
324
transform = TreeTransform(tree)
325
self.addCleanup(transform.finalize)
326
transform.new_file('file', transform.root, 'content')
327
result = transform.find_conflicts()
328
self.assertEqual([], result)
330
# Force the tree to report that it is case insensitive, for conflict
332
tree.case_sensitive = False
333
transform = TreeTransform(tree)
334
self.addCleanup(transform.finalize)
335
transform.new_file('file', transform.root, 'content')
336
result = transform.find_conflicts()
337
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
339
def test_resolve_case_insensitive_conflict(self):
340
tree = self.make_branch_and_tree('tree')
341
# Don't try this at home, kids!
342
# Force the tree to report that it is case insensitive, for conflict
344
tree.case_sensitive = False
345
transform = TreeTransform(tree)
346
self.addCleanup(transform.finalize)
347
transform.new_file('file', transform.root, 'content')
348
transform.new_file('FiLe', transform.root, 'content')
349
resolve_conflicts(transform)
351
self.failUnlessExists('tree/file')
352
self.failUnlessExists('tree/FiLe.moved')
354
def test_resolve_checkout_case_conflict(self):
355
tree = self.make_branch_and_tree('tree')
356
# Don't try this at home, kids!
357
# Force the tree to report that it is case insensitive, for conflict
359
tree.case_sensitive = False
360
transform = TreeTransform(tree)
361
self.addCleanup(transform.finalize)
362
transform.new_file('file', transform.root, 'content')
363
transform.new_file('FiLe', transform.root, 'content')
364
resolve_conflicts(transform,
365
pass_func=lambda t, c: resolve_checkout(t, c, []))
367
self.failUnlessExists('tree/file')
368
self.failUnlessExists('tree/FiLe.moved')
370
def test_apply_case_conflict(self):
371
"""Ensure that a transform with case conflicts can always be applied"""
372
tree = self.make_branch_and_tree('tree')
373
transform = TreeTransform(tree)
374
self.addCleanup(transform.finalize)
375
transform.new_file('file', transform.root, 'content')
376
transform.new_file('FiLe', transform.root, 'content')
377
dir = transform.new_directory('dir', transform.root)
378
transform.new_file('dirfile', dir, 'content')
379
transform.new_file('dirFiLe', dir, 'content')
380
resolve_conflicts(transform)
382
self.failUnlessExists('tree/file')
383
if not os.path.exists('tree/FiLe.moved'):
384
self.failUnlessExists('tree/FiLe')
385
self.failUnlessExists('tree/dir/dirfile')
386
if not os.path.exists('tree/dir/dirFiLe.moved'):
387
self.failUnlessExists('tree/dir/dirFiLe')
389
def test_case_insensitive_limbo(self):
390
tree = self.make_branch_and_tree('tree')
391
# Don't try this at home, kids!
392
# Force the tree to report that it is case insensitive
393
tree.case_sensitive = False
394
transform = TreeTransform(tree)
395
self.addCleanup(transform.finalize)
396
dir = transform.new_directory('dir', transform.root)
397
first = transform.new_file('file', dir, 'content')
398
second = transform.new_file('FiLe', dir, 'content')
399
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
400
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
402
def test_add_del(self):
403
start, root = self.get_transform()
404
start.new_directory('a', root, 'a')
406
transform, root = self.get_transform()
407
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
408
transform.new_directory('a', root, 'a')
411
def test_unversioning(self):
412
create_tree, root = self.get_transform()
413
parent_id = create_tree.new_directory('parent', root, 'parent-id')
414
create_tree.new_file('child', parent_id, 'child', 'child-id')
416
unversion = TreeTransform(self.wt)
417
self.addCleanup(unversion.finalize)
418
parent = unversion.trans_id_tree_path('parent')
419
unversion.unversion_file(parent)
420
self.assertEqual(unversion.find_conflicts(),
421
[('unversioned parent', parent_id)])
422
file_id = unversion.trans_id_tree_file_id('child-id')
423
unversion.unversion_file(file_id)
426
def test_name_invariants(self):
427
create_tree, root = self.get_transform()
429
root = create_tree.root
430
create_tree.new_file('name1', root, 'hello1', 'name1')
431
create_tree.new_file('name2', root, 'hello2', 'name2')
432
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
433
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
434
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
435
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
438
mangle_tree,root = self.get_transform()
439
root = mangle_tree.root
441
name1 = mangle_tree.trans_id_tree_file_id('name1')
442
name2 = mangle_tree.trans_id_tree_file_id('name2')
443
mangle_tree.adjust_path('name2', root, name1)
444
mangle_tree.adjust_path('name1', root, name2)
446
#tests for deleting parent directories
447
ddir = mangle_tree.trans_id_tree_file_id('ddir')
448
mangle_tree.delete_contents(ddir)
449
dfile = mangle_tree.trans_id_tree_file_id('dfile')
450
mangle_tree.delete_versioned(dfile)
451
mangle_tree.unversion_file(dfile)
452
mfile = mangle_tree.trans_id_tree_file_id('mfile')
453
mangle_tree.adjust_path('mfile', root, mfile)
455
#tests for adding parent directories
456
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
457
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
458
mangle_tree.adjust_path('mfile2', newdir, mfile2)
459
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
460
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
461
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
462
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
464
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
465
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
466
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
467
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
468
self.assertEqual(file(mfile2_path).read(), 'later2')
469
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
470
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
471
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
472
self.assertEqual(file(newfile_path).read(), 'hello3')
473
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
474
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
475
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
477
def test_both_rename(self):
478
create_tree,root = self.get_transform()
479
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
480
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
482
mangle_tree,root = self.get_transform()
483
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
484
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
485
mangle_tree.adjust_path('test', root, selftest)
486
mangle_tree.adjust_path('test_too_much', root, selftest)
487
mangle_tree.set_executability(True, blackbox)
490
def test_both_rename2(self):
491
create_tree,root = self.get_transform()
492
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
493
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
494
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
495
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
498
mangle_tree,root = self.get_transform()
499
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
500
tests = mangle_tree.trans_id_tree_file_id('tests-id')
501
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
502
mangle_tree.adjust_path('selftest', bzrlib, tests)
503
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
504
mangle_tree.set_executability(True, test_too_much)
507
def test_both_rename3(self):
508
create_tree,root = self.get_transform()
509
tests = create_tree.new_directory('tests', root, 'tests-id')
510
create_tree.new_file('test_too_much.py', tests, 'hello1',
513
mangle_tree,root = self.get_transform()
514
tests = mangle_tree.trans_id_tree_file_id('tests-id')
515
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
516
mangle_tree.adjust_path('selftest', root, tests)
517
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
518
mangle_tree.set_executability(True, test_too_much)
521
def test_move_dangling_ie(self):
522
create_tree, root = self.get_transform()
524
root = create_tree.root
525
create_tree.new_file('name1', root, 'hello1', 'name1')
527
delete_contents, root = self.get_transform()
528
file = delete_contents.trans_id_tree_file_id('name1')
529
delete_contents.delete_contents(file)
530
delete_contents.apply()
531
move_id, root = self.get_transform()
532
name1 = move_id.trans_id_tree_file_id('name1')
533
newdir = move_id.new_directory('dir', root, 'newdir')
534
move_id.adjust_path('name2', newdir, name1)
537
def test_replace_dangling_ie(self):
538
create_tree, root = self.get_transform()
540
root = create_tree.root
541
create_tree.new_file('name1', root, 'hello1', 'name1')
543
delete_contents = TreeTransform(self.wt)
544
self.addCleanup(delete_contents.finalize)
545
file = delete_contents.trans_id_tree_file_id('name1')
546
delete_contents.delete_contents(file)
547
delete_contents.apply()
548
delete_contents.finalize()
549
replace = TreeTransform(self.wt)
550
self.addCleanup(replace.finalize)
551
name2 = replace.new_file('name2', root, 'hello2', 'name1')
552
conflicts = replace.find_conflicts()
553
name1 = replace.trans_id_tree_file_id('name1')
554
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
555
resolve_conflicts(replace)
558
def _test_symlinks(self, link_name1,link_target1,
559
link_name2, link_target2):
561
def ozpath(p): return 'oz/' + p
563
self.requireFeature(SymlinkFeature)
564
transform, root = self.get_transform()
565
oz_id = transform.new_directory('oz', root, 'oz-id')
566
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
568
wiz_id = transform.create_path(link_name2, oz_id)
569
transform.create_symlink(link_target2, wiz_id)
570
transform.version_file('wiz-id2', wiz_id)
571
transform.set_executability(True, wiz_id)
572
self.assertEqual(transform.find_conflicts(),
573
[('non-file executability', wiz_id)])
574
transform.set_executability(None, wiz_id)
576
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
577
self.assertEqual('symlink',
578
file_kind(self.wt.abspath(ozpath(link_name1))))
579
self.assertEqual(link_target2,
580
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
581
self.assertEqual(link_target1,
582
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
584
def test_symlinks(self):
585
self._test_symlinks('wizard', 'wizard-target',
586
'wizard2', 'behind_curtain')
588
def test_symlinks_unicode(self):
589
self.requireFeature(tests.UnicodeFilenameFeature)
590
self._test_symlinks(u'\N{Euro Sign}wizard',
591
u'wizard-targ\N{Euro Sign}t',
592
u'\N{Euro Sign}wizard2',
593
u'b\N{Euro Sign}hind_curtain')
595
def test_unable_create_symlink(self):
597
wt = self.make_branch_and_tree('.')
598
tt = TreeTransform(wt) # TreeTransform obtains write lock
600
tt.new_symlink('foo', tt.root, 'bar')
604
os_symlink = getattr(os, 'symlink', None)
607
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
609
"Unable to create symlink 'foo' on this platform",
613
os.symlink = os_symlink
615
def get_conflicted(self):
616
create,root = self.get_transform()
617
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
618
oz = create.new_directory('oz', root, 'oz-id')
619
create.new_directory('emeraldcity', oz, 'emerald-id')
621
conflicts,root = self.get_transform()
622
# set up duplicate entry, duplicate id
623
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
625
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
626
oz = conflicts.trans_id_tree_file_id('oz-id')
627
# set up DeletedParent parent conflict
628
conflicts.delete_versioned(oz)
629
emerald = conflicts.trans_id_tree_file_id('emerald-id')
630
# set up MissingParent conflict
631
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
632
conflicts.adjust_path('munchkincity', root, munchkincity)
633
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
635
conflicts.adjust_path('emeraldcity', emerald, emerald)
636
return conflicts, emerald, oz, old_dorothy, new_dorothy
638
def test_conflict_resolution(self):
639
conflicts, emerald, oz, old_dorothy, new_dorothy =\
640
self.get_conflicted()
641
resolve_conflicts(conflicts)
642
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
643
self.assertIs(conflicts.final_file_id(old_dorothy), None)
644
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
645
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
646
self.assertEqual(conflicts.final_parent(emerald), oz)
649
def test_cook_conflicts(self):
650
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
651
raw_conflicts = resolve_conflicts(tt)
652
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
653
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
654
'dorothy', None, 'dorothy-id')
655
self.assertEqual(cooked_conflicts[0], duplicate)
656
duplicate_id = DuplicateID('Unversioned existing file',
657
'dorothy.moved', 'dorothy', None,
659
self.assertEqual(cooked_conflicts[1], duplicate_id)
660
missing_parent = MissingParent('Created directory', 'munchkincity',
662
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
663
self.assertEqual(cooked_conflicts[2], missing_parent)
664
unversioned_parent = UnversionedParent('Versioned directory',
667
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
669
self.assertEqual(cooked_conflicts[3], unversioned_parent)
670
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
671
'oz/emeraldcity', 'emerald-id', 'emerald-id')
672
self.assertEqual(cooked_conflicts[4], deleted_parent)
673
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
674
self.assertEqual(cooked_conflicts[6], parent_loop)
675
self.assertEqual(len(cooked_conflicts), 7)
678
def test_string_conflicts(self):
679
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
680
raw_conflicts = resolve_conflicts(tt)
681
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
683
conflicts_s = [str(c) for c in cooked_conflicts]
684
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
685
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
686
'Moved existing file to '
688
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
689
'Unversioned existing file '
691
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
692
' munchkincity. Created directory.')
693
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
694
' versioned, but has versioned'
695
' children. Versioned directory.')
696
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
697
" is not empty. Not deleting.")
698
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
699
' versioned, but has versioned'
700
' children. Versioned directory.')
701
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
702
' oz/emeraldcity. Cancelled move.')
704
def prepare_wrong_parent_kind(self):
705
tt, root = self.get_transform()
706
tt.new_file('parent', root, 'contents', 'parent-id')
708
tt, root = self.get_transform()
709
parent_id = tt.trans_id_file_id('parent-id')
710
tt.new_file('child,', parent_id, 'contents2', 'file-id')
713
def test_find_conflicts_wrong_parent_kind(self):
714
tt = self.prepare_wrong_parent_kind()
717
def test_resolve_conflicts_wrong_existing_parent_kind(self):
718
tt = self.prepare_wrong_parent_kind()
719
raw_conflicts = resolve_conflicts(tt)
720
self.assertEqual(set([('non-directory parent', 'Created directory',
721
'new-3')]), raw_conflicts)
722
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
723
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
724
'parent-id')], cooked_conflicts)
726
self.assertEqual(None, self.wt.path2id('parent'))
727
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
729
def test_resolve_conflicts_wrong_new_parent_kind(self):
730
tt, root = self.get_transform()
731
parent_id = tt.new_directory('parent', root, 'parent-id')
732
tt.new_file('child,', parent_id, 'contents2', 'file-id')
734
tt, root = self.get_transform()
735
parent_id = tt.trans_id_file_id('parent-id')
736
tt.delete_contents(parent_id)
737
tt.create_file('contents', parent_id)
738
raw_conflicts = resolve_conflicts(tt)
739
self.assertEqual(set([('non-directory parent', 'Created directory',
740
'new-3')]), raw_conflicts)
742
self.assertEqual(None, self.wt.path2id('parent'))
743
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
745
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
746
tt, root = self.get_transform()
747
parent_id = tt.new_directory('parent', root)
748
tt.new_file('child,', parent_id, 'contents2')
750
tt, root = self.get_transform()
751
parent_id = tt.trans_id_tree_path('parent')
752
tt.delete_contents(parent_id)
753
tt.create_file('contents', parent_id)
754
resolve_conflicts(tt)
756
self.assertIs(None, self.wt.path2id('parent'))
757
self.assertIs(None, self.wt.path2id('parent.new'))
759
def test_moving_versioned_directories(self):
760
create, root = self.get_transform()
761
kansas = create.new_directory('kansas', root, 'kansas-id')
762
create.new_directory('house', kansas, 'house-id')
763
create.new_directory('oz', root, 'oz-id')
765
cyclone, root = self.get_transform()
766
oz = cyclone.trans_id_tree_file_id('oz-id')
767
house = cyclone.trans_id_tree_file_id('house-id')
768
cyclone.adjust_path('house', oz, house)
771
def test_moving_root(self):
772
create, root = self.get_transform()
773
fun = create.new_directory('fun', root, 'fun-id')
774
create.new_directory('sun', root, 'sun-id')
775
create.new_directory('moon', root, 'moon')
777
transform, root = self.get_transform()
778
transform.adjust_root_path('oldroot', fun)
779
new_root = transform.trans_id_tree_path('')
780
transform.version_file('new-root', new_root)
783
def test_renames(self):
784
create, root = self.get_transform()
785
old = create.new_directory('old-parent', root, 'old-id')
786
intermediate = create.new_directory('intermediate', old, 'im-id')
787
myfile = create.new_file('myfile', intermediate, 'myfile-text',
790
rename, root = self.get_transform()
791
old = rename.trans_id_file_id('old-id')
792
rename.adjust_path('new', root, old)
793
myfile = rename.trans_id_file_id('myfile-id')
794
rename.set_executability(True, myfile)
797
def test_set_executability_order(self):
798
"""Ensure that executability behaves the same, no matter what order.
800
- create file and set executability simultaneously
801
- create file and set executability afterward
802
- unsetting the executability of a file whose executability has not been
803
declared should throw an exception (this may happen when a
804
merge attempts to create a file with a duplicate ID)
806
transform, root = self.get_transform()
809
self.addCleanup(wt.unlock)
810
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
812
sac = transform.new_file('set_after_creation', root,
813
'Set after creation', 'sac')
814
transform.set_executability(True, sac)
815
uws = transform.new_file('unset_without_set', root, 'Unset badly',
817
self.assertRaises(KeyError, transform.set_executability, None, uws)
819
self.assertTrue(wt.is_executable('soc'))
820
self.assertTrue(wt.is_executable('sac'))
822
def test_preserve_mode(self):
823
"""File mode is preserved when replacing content"""
824
if sys.platform == 'win32':
825
raise TestSkipped('chmod has no effect on win32')
826
transform, root = self.get_transform()
827
transform.new_file('file1', root, 'contents', 'file1-id', True)
830
self.addCleanup(self.wt.unlock)
831
self.assertTrue(self.wt.is_executable('file1-id'))
832
transform, root = self.get_transform()
833
file1_id = transform.trans_id_tree_file_id('file1-id')
834
transform.delete_contents(file1_id)
835
transform.create_file('contents2', file1_id)
837
self.assertTrue(self.wt.is_executable('file1-id'))
839
def test__set_mode_stats_correctly(self):
840
"""_set_mode stats to determine file mode."""
841
if sys.platform == 'win32':
842
raise TestSkipped('chmod has no effect on win32')
846
def instrumented_stat(path):
847
stat_paths.append(path)
848
return real_stat(path)
850
transform, root = self.get_transform()
852
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
853
file_id='bar-id-1', executable=False)
856
transform, root = self.get_transform()
857
bar1_id = transform.trans_id_tree_path('bar')
858
bar2_id = transform.trans_id_tree_path('bar2')
860
os.stat = instrumented_stat
861
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
866
bar1_abspath = self.wt.abspath('bar')
867
self.assertEqual([bar1_abspath], stat_paths)
869
def test_iter_changes(self):
870
self.wt.set_root_id('eert_toor')
871
transform, root = self.get_transform()
872
transform.new_file('old', root, 'blah', 'id-1', True)
874
transform, root = self.get_transform()
876
self.assertEqual([], list(transform.iter_changes()))
877
old = transform.trans_id_tree_file_id('id-1')
878
transform.unversion_file(old)
879
self.assertEqual([('id-1', ('old', None), False, (True, False),
880
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
881
(True, True))], list(transform.iter_changes()))
882
transform.new_directory('new', root, 'id-1')
883
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
884
('eert_toor', 'eert_toor'), ('old', 'new'),
885
('file', 'directory'),
886
(True, False))], list(transform.iter_changes()))
890
def test_iter_changes_new(self):
891
self.wt.set_root_id('eert_toor')
892
transform, root = self.get_transform()
893
transform.new_file('old', root, 'blah')
895
transform, root = self.get_transform()
897
old = transform.trans_id_tree_path('old')
898
transform.version_file('id-1', old)
899
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
900
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
901
(False, False))], list(transform.iter_changes()))
905
def test_iter_changes_modifications(self):
906
self.wt.set_root_id('eert_toor')
907
transform, root = self.get_transform()
908
transform.new_file('old', root, 'blah', 'id-1')
909
transform.new_file('new', root, 'blah')
910
transform.new_directory('subdir', root, 'subdir-id')
912
transform, root = self.get_transform()
914
old = transform.trans_id_tree_path('old')
915
subdir = transform.trans_id_tree_file_id('subdir-id')
916
new = transform.trans_id_tree_path('new')
917
self.assertEqual([], list(transform.iter_changes()))
920
transform.delete_contents(old)
921
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
922
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
923
(False, False))], list(transform.iter_changes()))
926
transform.create_file('blah', old)
927
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
928
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
929
(False, False))], list(transform.iter_changes()))
930
transform.cancel_deletion(old)
931
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
932
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
933
(False, False))], list(transform.iter_changes()))
934
transform.cancel_creation(old)
936
# move file_id to a different file
937
self.assertEqual([], list(transform.iter_changes()))
938
transform.unversion_file(old)
939
transform.version_file('id-1', new)
940
transform.adjust_path('old', root, new)
941
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
942
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
943
(False, False))], list(transform.iter_changes()))
944
transform.cancel_versioning(new)
945
transform._removed_id = set()
948
self.assertEqual([], list(transform.iter_changes()))
949
transform.set_executability(True, old)
950
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
951
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
952
(False, True))], list(transform.iter_changes()))
953
transform.set_executability(None, old)
956
self.assertEqual([], list(transform.iter_changes()))
957
transform.adjust_path('new', root, old)
958
transform._new_parent = {}
959
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
960
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
961
(False, False))], list(transform.iter_changes()))
962
transform._new_name = {}
965
self.assertEqual([], list(transform.iter_changes()))
966
transform.adjust_path('new', subdir, old)
967
transform._new_name = {}
968
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
969
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
970
('file', 'file'), (False, False))],
971
list(transform.iter_changes()))
972
transform._new_path = {}
977
def test_iter_changes_modified_bleed(self):
978
self.wt.set_root_id('eert_toor')
979
"""Modified flag should not bleed from one change to another"""
980
# unfortunately, we have no guarantee that file1 (which is modified)
981
# will be applied before file2. And if it's applied after file2, it
982
# obviously can't bleed into file2's change output. But for now, it
984
transform, root = self.get_transform()
985
transform.new_file('file1', root, 'blah', 'id-1')
986
transform.new_file('file2', root, 'blah', 'id-2')
988
transform, root = self.get_transform()
990
transform.delete_contents(transform.trans_id_file_id('id-1'))
991
transform.set_executability(True,
992
transform.trans_id_file_id('id-2'))
993
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
994
('eert_toor', 'eert_toor'), ('file1', u'file1'),
995
('file', None), (False, False)),
996
('id-2', (u'file2', u'file2'), False, (True, True),
997
('eert_toor', 'eert_toor'), ('file2', u'file2'),
998
('file', 'file'), (False, True))],
999
list(transform.iter_changes()))
1001
transform.finalize()
1003
def test_iter_changes_move_missing(self):
1004
"""Test moving ids with no files around"""
1005
self.wt.set_root_id('toor_eert')
1006
# Need two steps because versioning a non-existant file is a conflict.
1007
transform, root = self.get_transform()
1008
transform.new_directory('floater', root, 'floater-id')
1010
transform, root = self.get_transform()
1011
transform.delete_contents(transform.trans_id_tree_path('floater'))
1013
transform, root = self.get_transform()
1014
floater = transform.trans_id_tree_path('floater')
1016
transform.adjust_path('flitter', root, floater)
1017
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1018
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1019
(None, None), (False, False))], list(transform.iter_changes()))
1021
transform.finalize()
1023
def test_iter_changes_pointless(self):
1024
"""Ensure that no-ops are not treated as modifications"""
1025
self.wt.set_root_id('eert_toor')
1026
transform, root = self.get_transform()
1027
transform.new_file('old', root, 'blah', 'id-1')
1028
transform.new_directory('subdir', root, 'subdir-id')
1030
transform, root = self.get_transform()
1032
old = transform.trans_id_tree_path('old')
1033
subdir = transform.trans_id_tree_file_id('subdir-id')
1034
self.assertEqual([], list(transform.iter_changes()))
1035
transform.delete_contents(subdir)
1036
transform.create_directory(subdir)
1037
transform.set_executability(False, old)
1038
transform.unversion_file(old)
1039
transform.version_file('id-1', old)
1040
transform.adjust_path('old', root, old)
1041
self.assertEqual([], list(transform.iter_changes()))
1043
transform.finalize()
1045
def test_rename_count(self):
1046
transform, root = self.get_transform()
1047
transform.new_file('name1', root, 'contents')
1048
self.assertEqual(transform.rename_count, 0)
1050
self.assertEqual(transform.rename_count, 1)
1051
transform2, root = self.get_transform()
1052
transform2.adjust_path('name2', root,
1053
transform2.trans_id_tree_path('name1'))
1054
self.assertEqual(transform2.rename_count, 0)
1056
self.assertEqual(transform2.rename_count, 2)
1058
def test_change_parent(self):
1059
"""Ensure that after we change a parent, the results are still right.
1061
Renames and parent changes on pending transforms can happen as part
1062
of conflict resolution, and are explicitly permitted by the
1065
This test ensures they work correctly with the rename-avoidance
1068
transform, root = self.get_transform()
1069
parent1 = transform.new_directory('parent1', root)
1070
child1 = transform.new_file('child1', parent1, 'contents')
1071
parent2 = transform.new_directory('parent2', root)
1072
transform.adjust_path('child1', parent2, child1)
1074
self.failIfExists(self.wt.abspath('parent1/child1'))
1075
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1076
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1077
# no rename for child1 (counting only renames during apply)
1078
self.failUnlessEqual(2, transform.rename_count)
1080
def test_cancel_parent(self):
1081
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1083
This is like the test_change_parent, except that we cancel the parent
1084
before adjusting the path. The transform must detect that the
1085
directory is non-empty, and move children to safe locations.
1087
transform, root = self.get_transform()
1088
parent1 = transform.new_directory('parent1', root)
1089
child1 = transform.new_file('child1', parent1, 'contents')
1090
child2 = transform.new_file('child2', parent1, 'contents')
1092
transform.cancel_creation(parent1)
1094
self.fail('Failed to move child1 before deleting parent1')
1095
transform.cancel_creation(child2)
1096
transform.create_directory(parent1)
1098
transform.cancel_creation(parent1)
1099
# If the transform incorrectly believes that child2 is still in
1100
# parent1's limbo directory, it will try to rename it and fail
1101
# because was already moved by the first cancel_creation.
1103
self.fail('Transform still thinks child2 is a child of parent1')
1104
parent2 = transform.new_directory('parent2', root)
1105
transform.adjust_path('child1', parent2, child1)
1107
self.failIfExists(self.wt.abspath('parent1'))
1108
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1109
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1110
self.failUnlessEqual(2, transform.rename_count)
1112
def test_adjust_and_cancel(self):
1113
"""Make sure adjust_path keeps track of limbo children properly"""
1114
transform, root = self.get_transform()
1115
parent1 = transform.new_directory('parent1', root)
1116
child1 = transform.new_file('child1', parent1, 'contents')
1117
parent2 = transform.new_directory('parent2', root)
1118
transform.adjust_path('child1', parent2, child1)
1119
transform.cancel_creation(child1)
1121
transform.cancel_creation(parent1)
1122
# if the transform thinks child1 is still in parent1's limbo
1123
# directory, it will attempt to move it and fail.
1125
self.fail('Transform still thinks child1 is a child of parent1')
1126
transform.finalize()
1128
def test_noname_contents(self):
1129
"""TreeTransform should permit deferring naming files."""
1130
transform, root = self.get_transform()
1131
parent = transform.trans_id_file_id('parent-id')
1133
transform.create_directory(parent)
1135
self.fail("Can't handle contents with no name")
1136
transform.finalize()
1138
def test_noname_contents_nested(self):
1139
"""TreeTransform should permit deferring naming files."""
1140
transform, root = self.get_transform()
1141
parent = transform.trans_id_file_id('parent-id')
1143
transform.create_directory(parent)
1145
self.fail("Can't handle contents with no name")
1146
child = transform.new_directory('child', parent)
1147
transform.adjust_path('parent', root, parent)
1149
self.failUnlessExists(self.wt.abspath('parent/child'))
1150
self.assertEqual(1, transform.rename_count)
1152
def test_reuse_name(self):
1153
"""Avoid reusing the same limbo name for different files"""
1154
transform, root = self.get_transform()
1155
parent = transform.new_directory('parent', root)
1156
child1 = transform.new_directory('child', parent)
1158
child2 = transform.new_directory('child', parent)
1160
self.fail('Tranform tried to use the same limbo name twice')
1161
transform.adjust_path('child2', parent, child2)
1163
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1164
# child2 is put into top-level limbo because child1 has already
1165
# claimed the direct limbo path when child2 is created. There is no
1166
# advantage in renaming files once they're in top-level limbo, except
1168
self.assertEqual(2, transform.rename_count)
1170
def test_reuse_when_first_moved(self):
1171
"""Don't avoid direct paths when it is safe to use them"""
1172
transform, root = self.get_transform()
1173
parent = transform.new_directory('parent', root)
1174
child1 = transform.new_directory('child', parent)
1175
transform.adjust_path('child1', parent, child1)
1176
child2 = transform.new_directory('child', parent)
1178
# limbo/new-1 => parent
1179
self.assertEqual(1, transform.rename_count)
1181
def test_reuse_after_cancel(self):
1182
"""Don't avoid direct paths when it is safe to use them"""
1183
transform, root = self.get_transform()
1184
parent2 = transform.new_directory('parent2', root)
1185
child1 = transform.new_directory('child1', parent2)
1186
transform.cancel_creation(parent2)
1187
transform.create_directory(parent2)
1188
child2 = transform.new_directory('child1', parent2)
1189
transform.adjust_path('child2', parent2, child1)
1191
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1192
self.assertEqual(2, transform.rename_count)
1194
def test_finalize_order(self):
1195
"""Finalize must be done in child-to-parent order"""
1196
transform, root = self.get_transform()
1197
parent = transform.new_directory('parent', root)
1198
child = transform.new_directory('child', parent)
1200
transform.finalize()
1202
self.fail('Tried to remove parent before child1')
1204
def test_cancel_with_cancelled_child_should_succeed(self):
1205
transform, root = self.get_transform()
1206
parent = transform.new_directory('parent', root)
1207
child = transform.new_directory('child', parent)
1208
transform.cancel_creation(child)
1209
transform.cancel_creation(parent)
1210
transform.finalize()
1212
def test_rollback_on_directory_clash(self):
1214
wt = self.make_branch_and_tree('.')
1215
tt = TreeTransform(wt) # TreeTransform obtains write lock
1217
foo = tt.new_directory('foo', tt.root)
1218
tt.new_file('bar', foo, 'foobar')
1219
baz = tt.new_directory('baz', tt.root)
1220
tt.new_file('qux', baz, 'quux')
1221
# Ask for a rename 'foo' -> 'baz'
1222
tt.adjust_path('baz', tt.root, foo)
1223
# Lie to tt that we've already resolved all conflicts.
1224
tt.apply(no_conflicts=True)
1228
# The rename will fail because the target directory is not empty (but
1229
# raises FileExists anyway).
1230
err = self.assertRaises(errors.FileExists, tt_helper)
1231
self.assertContainsRe(str(err),
1232
"^File exists: .+/baz")
1234
def test_two_directories_clash(self):
1236
wt = self.make_branch_and_tree('.')
1237
tt = TreeTransform(wt) # TreeTransform obtains write lock
1239
foo_1 = tt.new_directory('foo', tt.root)
1240
tt.new_directory('bar', foo_1)
1241
# Adding the same directory with a different content
1242
foo_2 = tt.new_directory('foo', tt.root)
1243
tt.new_directory('baz', foo_2)
1244
# Lie to tt that we've already resolved all conflicts.
1245
tt.apply(no_conflicts=True)
1249
err = self.assertRaises(errors.FileExists, tt_helper)
1250
self.assertContainsRe(str(err),
1251
"^File exists: .+/foo")
1253
def test_two_directories_clash_finalize(self):
1255
wt = self.make_branch_and_tree('.')
1256
tt = TreeTransform(wt) # TreeTransform obtains write lock
1258
foo_1 = tt.new_directory('foo', tt.root)
1259
tt.new_directory('bar', foo_1)
1260
# Adding the same directory with a different content
1261
foo_2 = tt.new_directory('foo', tt.root)
1262
tt.new_directory('baz', foo_2)
1263
# Lie to tt that we've already resolved all conflicts.
1264
tt.apply(no_conflicts=True)
1268
err = self.assertRaises(errors.FileExists, tt_helper)
1269
self.assertContainsRe(str(err),
1270
"^File exists: .+/foo")
1272
def test_file_to_directory(self):
1273
wt = self.make_branch_and_tree('.')
1274
self.build_tree(['foo'])
1277
tt = TreeTransform(wt)
1278
self.addCleanup(tt.finalize)
1279
foo_trans_id = tt.trans_id_tree_path("foo")
1280
tt.delete_contents(foo_trans_id)
1281
tt.create_directory(foo_trans_id)
1282
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1283
tt.create_file(["aa\n"], bar_trans_id)
1284
tt.version_file("bar-1", bar_trans_id)
1286
self.failUnlessExists("foo/bar")
1289
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1294
changes = wt.changes_from(wt.basis_tree())
1295
self.assertFalse(changes.has_changed(), changes)
1297
def test_file_to_symlink(self):
1298
self.requireFeature(SymlinkFeature)
1299
wt = self.make_branch_and_tree('.')
1300
self.build_tree(['foo'])
1303
tt = TreeTransform(wt)
1304
self.addCleanup(tt.finalize)
1305
foo_trans_id = tt.trans_id_tree_path("foo")
1306
tt.delete_contents(foo_trans_id)
1307
tt.create_symlink("bar", foo_trans_id)
1309
self.failUnlessExists("foo")
1311
self.addCleanup(wt.unlock)
1312
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1315
def test_dir_to_file(self):
1316
wt = self.make_branch_and_tree('.')
1317
self.build_tree(['foo/', 'foo/bar'])
1318
wt.add(['foo', 'foo/bar'])
1320
tt = TreeTransform(wt)
1321
self.addCleanup(tt.finalize)
1322
foo_trans_id = tt.trans_id_tree_path("foo")
1323
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1324
tt.delete_contents(foo_trans_id)
1325
tt.delete_versioned(bar_trans_id)
1326
tt.create_file(["aa\n"], foo_trans_id)
1328
self.failUnlessExists("foo")
1330
self.addCleanup(wt.unlock)
1331
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1334
def test_dir_to_hardlink(self):
1335
self.requireFeature(HardlinkFeature)
1336
wt = self.make_branch_and_tree('.')
1337
self.build_tree(['foo/', 'foo/bar'])
1338
wt.add(['foo', 'foo/bar'])
1340
tt = TreeTransform(wt)
1341
self.addCleanup(tt.finalize)
1342
foo_trans_id = tt.trans_id_tree_path("foo")
1343
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1344
tt.delete_contents(foo_trans_id)
1345
tt.delete_versioned(bar_trans_id)
1346
self.build_tree(['baz'])
1347
tt.create_hardlink("baz", foo_trans_id)
1349
self.failUnlessExists("foo")
1350
self.failUnlessExists("baz")
1352
self.addCleanup(wt.unlock)
1353
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1356
def test_no_final_path(self):
1357
transform, root = self.get_transform()
1358
trans_id = transform.trans_id_file_id('foo')
1359
transform.create_file('bar', trans_id)
1360
transform.cancel_creation(trans_id)
1363
def test_create_from_tree(self):
1364
tree1 = self.make_branch_and_tree('tree1')
1365
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1366
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1367
tree2 = self.make_branch_and_tree('tree2')
1368
tt = TreeTransform(tree2)
1369
foo_trans_id = tt.create_path('foo', tt.root)
1370
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1371
bar_trans_id = tt.create_path('bar', tt.root)
1372
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1374
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1375
self.assertFileEqual('baz', 'tree2/bar')
1377
def test_create_from_tree_bytes(self):
1378
"""Provided lines are used instead of tree content."""
1379
tree1 = self.make_branch_and_tree('tree1')
1380
self.build_tree_contents([('tree1/foo', 'bar'),])
1381
tree1.add('foo', 'foo-id')
1382
tree2 = self.make_branch_and_tree('tree2')
1383
tt = TreeTransform(tree2)
1384
foo_trans_id = tt.create_path('foo', tt.root)
1385
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1387
self.assertFileEqual('qux', 'tree2/foo')
1389
def test_create_from_tree_symlink(self):
1390
self.requireFeature(SymlinkFeature)
1391
tree1 = self.make_branch_and_tree('tree1')
1392
os.symlink('bar', 'tree1/foo')
1393
tree1.add('foo', 'foo-id')
1394
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1395
foo_trans_id = tt.create_path('foo', tt.root)
1396
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1398
self.assertEqual('bar', os.readlink('tree2/foo'))
1401
class TransformGroup(object):
1403
def __init__(self, dirname, root_id):
1406
self.wt = BzrDir.create_standalone_workingtree(dirname)
1407
self.wt.set_root_id(root_id)
1408
self.b = self.wt.branch
1409
self.tt = TreeTransform(self.wt)
1410
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1413
def conflict_text(tree, merge):
1414
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1415
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1418
class TestTransformMerge(TestCaseInTempDir):
1420
def test_text_merge(self):
1421
root_id = generate_ids.gen_root_id()
1422
base = TransformGroup("base", root_id)
1423
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1424
base.tt.new_file('b', base.root, 'b1', 'b')
1425
base.tt.new_file('c', base.root, 'c', 'c')
1426
base.tt.new_file('d', base.root, 'd', 'd')
1427
base.tt.new_file('e', base.root, 'e', 'e')
1428
base.tt.new_file('f', base.root, 'f', 'f')
1429
base.tt.new_directory('g', base.root, 'g')
1430
base.tt.new_directory('h', base.root, 'h')
1432
other = TransformGroup("other", root_id)
1433
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1434
other.tt.new_file('b', other.root, 'b2', 'b')
1435
other.tt.new_file('c', other.root, 'c2', 'c')
1436
other.tt.new_file('d', other.root, 'd', 'd')
1437
other.tt.new_file('e', other.root, 'e2', 'e')
1438
other.tt.new_file('f', other.root, 'f', 'f')
1439
other.tt.new_file('g', other.root, 'g', 'g')
1440
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1441
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1443
this = TransformGroup("this", root_id)
1444
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1445
this.tt.new_file('b', this.root, 'b', 'b')
1446
this.tt.new_file('c', this.root, 'c', 'c')
1447
this.tt.new_file('d', this.root, 'd2', 'd')
1448
this.tt.new_file('e', this.root, 'e2', 'e')
1449
this.tt.new_file('f', this.root, 'f', 'f')
1450
this.tt.new_file('g', this.root, 'g', 'g')
1451
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1452
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1454
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1457
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1458
# three-way text conflict
1459
self.assertEqual(this.wt.get_file('b').read(),
1460
conflict_text('b', 'b2'))
1462
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1464
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1465
# Ambigious clean merge
1466
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1468
self.assertEqual(this.wt.get_file('f').read(), 'f')
1469
# Correct correct results when THIS == OTHER
1470
self.assertEqual(this.wt.get_file('g').read(), 'g')
1471
# Text conflict when THIS & OTHER are text and BASE is dir
1472
self.assertEqual(this.wt.get_file('h').read(),
1473
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1474
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1476
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1478
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1479
self.assertEqual(this.wt.get_file('i').read(),
1480
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1481
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1483
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1485
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1486
modified = ['a', 'b', 'c', 'h', 'i']
1487
merge_modified = this.wt.merge_modified()
1488
self.assertSubset(merge_modified, modified)
1489
self.assertEqual(len(merge_modified), len(modified))
1490
file(this.wt.id2abspath('a'), 'wb').write('booga')
1492
merge_modified = this.wt.merge_modified()
1493
self.assertSubset(merge_modified, modified)
1494
self.assertEqual(len(merge_modified), len(modified))
1498
def test_file_merge(self):
1499
self.requireFeature(SymlinkFeature)
1500
root_id = generate_ids.gen_root_id()
1501
base = TransformGroup("BASE", root_id)
1502
this = TransformGroup("THIS", root_id)
1503
other = TransformGroup("OTHER", root_id)
1504
for tg in this, base, other:
1505
tg.tt.new_directory('a', tg.root, 'a')
1506
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1507
tg.tt.new_file('c', tg.root, 'c', 'c')
1508
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1509
targets = ((base, 'base-e', 'base-f', None, None),
1510
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1511
(other, 'other-e', None, 'other-g', 'other-h'))
1512
for tg, e_target, f_target, g_target, h_target in targets:
1513
for link, target in (('e', e_target), ('f', f_target),
1514
('g', g_target), ('h', h_target)):
1515
if target is not None:
1516
tg.tt.new_symlink(link, tg.root, target, link)
1518
for tg in this, base, other:
1520
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1521
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1522
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1523
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1524
for suffix in ('THIS', 'BASE', 'OTHER'):
1525
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1526
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1527
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1528
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1529
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1530
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1531
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1532
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1533
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1534
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1535
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1536
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1537
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1538
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1540
def test_filename_merge(self):
1541
root_id = generate_ids.gen_root_id()
1542
base = TransformGroup("BASE", root_id)
1543
this = TransformGroup("THIS", root_id)
1544
other = TransformGroup("OTHER", root_id)
1545
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1546
for t in [base, this, other]]
1547
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1548
for t in [base, this, other]]
1549
base.tt.new_directory('c', base_a, 'c')
1550
this.tt.new_directory('c1', this_a, 'c')
1551
other.tt.new_directory('c', other_b, 'c')
1553
base.tt.new_directory('d', base_a, 'd')
1554
this.tt.new_directory('d1', this_b, 'd')
1555
other.tt.new_directory('d', other_a, 'd')
1557
base.tt.new_directory('e', base_a, 'e')
1558
this.tt.new_directory('e', this_a, 'e')
1559
other.tt.new_directory('e1', other_b, 'e')
1561
base.tt.new_directory('f', base_a, 'f')
1562
this.tt.new_directory('f1', this_b, 'f')
1563
other.tt.new_directory('f1', other_b, 'f')
1565
for tg in [this, base, other]:
1567
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1568
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1569
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1570
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1571
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1573
def test_filename_merge_conflicts(self):
1574
root_id = generate_ids.gen_root_id()
1575
base = TransformGroup("BASE", root_id)
1576
this = TransformGroup("THIS", root_id)
1577
other = TransformGroup("OTHER", root_id)
1578
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1579
for t in [base, this, other]]
1580
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1581
for t in [base, this, other]]
1583
base.tt.new_file('g', base_a, 'g', 'g')
1584
other.tt.new_file('g1', other_b, 'g1', 'g')
1586
base.tt.new_file('h', base_a, 'h', 'h')
1587
this.tt.new_file('h1', this_b, 'h1', 'h')
1589
base.tt.new_file('i', base.root, 'i', 'i')
1590
other.tt.new_directory('i1', this_b, 'i')
1592
for tg in [this, base, other]:
1594
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1596
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1597
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1598
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1599
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1600
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1601
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1602
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1605
class TestBuildTree(tests.TestCaseWithTransport):
1607
def test_build_tree_with_symlinks(self):
1608
self.requireFeature(SymlinkFeature)
1610
a = BzrDir.create_standalone_workingtree('a')
1612
file('a/foo/bar', 'wb').write('contents')
1613
os.symlink('a/foo/bar', 'a/foo/baz')
1614
a.add(['foo', 'foo/bar', 'foo/baz'])
1615
a.commit('initial commit')
1616
b = BzrDir.create_standalone_workingtree('b')
1617
basis = a.basis_tree()
1619
self.addCleanup(basis.unlock)
1620
build_tree(basis, b)
1621
self.assertIs(os.path.isdir('b/foo'), True)
1622
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1623
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1625
def test_build_with_references(self):
1626
tree = self.make_branch_and_tree('source',
1627
format='dirstate-with-subtree')
1628
subtree = self.make_branch_and_tree('source/subtree',
1629
format='dirstate-with-subtree')
1630
tree.add_reference(subtree)
1631
tree.commit('a revision')
1632
tree.branch.create_checkout('target')
1633
self.failUnlessExists('target')
1634
self.failUnlessExists('target/subtree')
1636
def test_file_conflict_handling(self):
1637
"""Ensure that when building trees, conflict handling is done"""
1638
source = self.make_branch_and_tree('source')
1639
target = self.make_branch_and_tree('target')
1640
self.build_tree(['source/file', 'target/file'])
1641
source.add('file', 'new-file')
1642
source.commit('added file')
1643
build_tree(source.basis_tree(), target)
1644
self.assertEqual([DuplicateEntry('Moved existing file to',
1645
'file.moved', 'file', None, 'new-file')],
1647
target2 = self.make_branch_and_tree('target2')
1648
target_file = file('target2/file', 'wb')
1650
source_file = file('source/file', 'rb')
1652
target_file.write(source_file.read())
1657
build_tree(source.basis_tree(), target2)
1658
self.assertEqual([], target2.conflicts())
1660
def test_symlink_conflict_handling(self):
1661
"""Ensure that when building trees, conflict handling is done"""
1662
self.requireFeature(SymlinkFeature)
1663
source = self.make_branch_and_tree('source')
1664
os.symlink('foo', 'source/symlink')
1665
source.add('symlink', 'new-symlink')
1666
source.commit('added file')
1667
target = self.make_branch_and_tree('target')
1668
os.symlink('bar', 'target/symlink')
1669
build_tree(source.basis_tree(), target)
1670
self.assertEqual([DuplicateEntry('Moved existing file to',
1671
'symlink.moved', 'symlink', None, 'new-symlink')],
1673
target = self.make_branch_and_tree('target2')
1674
os.symlink('foo', 'target2/symlink')
1675
build_tree(source.basis_tree(), target)
1676
self.assertEqual([], target.conflicts())
1678
def test_directory_conflict_handling(self):
1679
"""Ensure that when building trees, conflict handling is done"""
1680
source = self.make_branch_and_tree('source')
1681
target = self.make_branch_and_tree('target')
1682
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1683
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1684
source.commit('added file')
1685
build_tree(source.basis_tree(), target)
1686
self.assertEqual([], target.conflicts())
1687
self.failUnlessExists('target/dir1/file')
1689
# Ensure contents are merged
1690
target = self.make_branch_and_tree('target2')
1691
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1692
build_tree(source.basis_tree(), target)
1693
self.assertEqual([], target.conflicts())
1694
self.failUnlessExists('target2/dir1/file2')
1695
self.failUnlessExists('target2/dir1/file')
1697
# Ensure new contents are suppressed for existing branches
1698
target = self.make_branch_and_tree('target3')
1699
self.make_branch('target3/dir1')
1700
self.build_tree(['target3/dir1/file2'])
1701
build_tree(source.basis_tree(), target)
1702
self.failIfExists('target3/dir1/file')
1703
self.failUnlessExists('target3/dir1/file2')
1704
self.failUnlessExists('target3/dir1.diverted/file')
1705
self.assertEqual([DuplicateEntry('Diverted to',
1706
'dir1.diverted', 'dir1', 'new-dir1', None)],
1709
target = self.make_branch_and_tree('target4')
1710
self.build_tree(['target4/dir1/'])
1711
self.make_branch('target4/dir1/file')
1712
build_tree(source.basis_tree(), target)
1713
self.failUnlessExists('target4/dir1/file')
1714
self.assertEqual('directory', file_kind('target4/dir1/file'))
1715
self.failUnlessExists('target4/dir1/file.diverted')
1716
self.assertEqual([DuplicateEntry('Diverted to',
1717
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1720
def test_mixed_conflict_handling(self):
1721
"""Ensure that when building trees, conflict handling is done"""
1722
source = self.make_branch_and_tree('source')
1723
target = self.make_branch_and_tree('target')
1724
self.build_tree(['source/name', 'target/name/'])
1725
source.add('name', 'new-name')
1726
source.commit('added file')
1727
build_tree(source.basis_tree(), target)
1728
self.assertEqual([DuplicateEntry('Moved existing file to',
1729
'name.moved', 'name', None, 'new-name')], target.conflicts())
1731
def test_raises_in_populated(self):
1732
source = self.make_branch_and_tree('source')
1733
self.build_tree(['source/name'])
1735
source.commit('added name')
1736
target = self.make_branch_and_tree('target')
1737
self.build_tree(['target/name'])
1739
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1740
build_tree, source.basis_tree(), target)
1742
def test_build_tree_rename_count(self):
1743
source = self.make_branch_and_tree('source')
1744
self.build_tree(['source/file1', 'source/dir1/'])
1745
source.add(['file1', 'dir1'])
1746
source.commit('add1')
1747
target1 = self.make_branch_and_tree('target1')
1748
transform_result = build_tree(source.basis_tree(), target1)
1749
self.assertEqual(2, transform_result.rename_count)
1751
self.build_tree(['source/dir1/file2'])
1752
source.add(['dir1/file2'])
1753
source.commit('add3')
1754
target2 = self.make_branch_and_tree('target2')
1755
transform_result = build_tree(source.basis_tree(), target2)
1756
# children of non-root directories should not be renamed
1757
self.assertEqual(2, transform_result.rename_count)
1759
def create_ab_tree(self):
1760
"""Create a committed test tree with two files"""
1761
source = self.make_branch_and_tree('source')
1762
self.build_tree_contents([('source/file1', 'A')])
1763
self.build_tree_contents([('source/file2', 'B')])
1764
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1765
source.commit('commit files')
1767
self.addCleanup(source.unlock)
1770
def test_build_tree_accelerator_tree(self):
1771
source = self.create_ab_tree()
1772
self.build_tree_contents([('source/file2', 'C')])
1774
real_source_get_file = source.get_file
1775
def get_file(file_id, path=None):
1776
calls.append(file_id)
1777
return real_source_get_file(file_id, path)
1778
source.get_file = get_file
1779
target = self.make_branch_and_tree('target')
1780
revision_tree = source.basis_tree()
1781
revision_tree.lock_read()
1782
self.addCleanup(revision_tree.unlock)
1783
build_tree(revision_tree, target, source)
1784
self.assertEqual(['file1-id'], calls)
1786
self.addCleanup(target.unlock)
1787
self.assertEqual([], list(target.iter_changes(revision_tree)))
1789
def test_build_tree_accelerator_tree_missing_file(self):
1790
source = self.create_ab_tree()
1791
os.unlink('source/file1')
1792
source.remove(['file2'])
1793
target = self.make_branch_and_tree('target')
1794
revision_tree = source.basis_tree()
1795
revision_tree.lock_read()
1796
self.addCleanup(revision_tree.unlock)
1797
build_tree(revision_tree, target, source)
1799
self.addCleanup(target.unlock)
1800
self.assertEqual([], list(target.iter_changes(revision_tree)))
1802
def test_build_tree_accelerator_wrong_kind(self):
1803
self.requireFeature(SymlinkFeature)
1804
source = self.make_branch_and_tree('source')
1805
self.build_tree_contents([('source/file1', '')])
1806
self.build_tree_contents([('source/file2', '')])
1807
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1808
source.commit('commit files')
1809
os.unlink('source/file2')
1810
self.build_tree_contents([('source/file2/', 'C')])
1811
os.unlink('source/file1')
1812
os.symlink('file2', 'source/file1')
1814
real_source_get_file = source.get_file
1815
def get_file(file_id, path=None):
1816
calls.append(file_id)
1817
return real_source_get_file(file_id, path)
1818
source.get_file = get_file
1819
target = self.make_branch_and_tree('target')
1820
revision_tree = source.basis_tree()
1821
revision_tree.lock_read()
1822
self.addCleanup(revision_tree.unlock)
1823
build_tree(revision_tree, target, source)
1824
self.assertEqual([], calls)
1826
self.addCleanup(target.unlock)
1827
self.assertEqual([], list(target.iter_changes(revision_tree)))
1829
def test_build_tree_hardlink(self):
1830
self.requireFeature(HardlinkFeature)
1831
source = self.create_ab_tree()
1832
target = self.make_branch_and_tree('target')
1833
revision_tree = source.basis_tree()
1834
revision_tree.lock_read()
1835
self.addCleanup(revision_tree.unlock)
1836
build_tree(revision_tree, target, source, hardlink=True)
1838
self.addCleanup(target.unlock)
1839
self.assertEqual([], list(target.iter_changes(revision_tree)))
1840
source_stat = os.stat('source/file1')
1841
target_stat = os.stat('target/file1')
1842
self.assertEqual(source_stat, target_stat)
1844
# Explicitly disallowing hardlinks should prevent them.
1845
target2 = self.make_branch_and_tree('target2')
1846
build_tree(revision_tree, target2, source, hardlink=False)
1848
self.addCleanup(target2.unlock)
1849
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1850
source_stat = os.stat('source/file1')
1851
target2_stat = os.stat('target2/file1')
1852
self.assertNotEqual(source_stat, target2_stat)
1854
def test_build_tree_accelerator_tree_moved(self):
1855
source = self.make_branch_and_tree('source')
1856
self.build_tree_contents([('source/file1', 'A')])
1857
source.add(['file1'], ['file1-id'])
1858
source.commit('commit files')
1859
source.rename_one('file1', 'file2')
1861
self.addCleanup(source.unlock)
1862
target = self.make_branch_and_tree('target')
1863
revision_tree = source.basis_tree()
1864
revision_tree.lock_read()
1865
self.addCleanup(revision_tree.unlock)
1866
build_tree(revision_tree, target, source)
1868
self.addCleanup(target.unlock)
1869
self.assertEqual([], list(target.iter_changes(revision_tree)))
1871
def test_build_tree_hardlinks_preserve_execute(self):
1872
self.requireFeature(HardlinkFeature)
1873
source = self.create_ab_tree()
1874
tt = TreeTransform(source)
1875
trans_id = tt.trans_id_tree_file_id('file1-id')
1876
tt.set_executability(True, trans_id)
1878
self.assertTrue(source.is_executable('file1-id'))
1879
target = self.make_branch_and_tree('target')
1880
revision_tree = source.basis_tree()
1881
revision_tree.lock_read()
1882
self.addCleanup(revision_tree.unlock)
1883
build_tree(revision_tree, target, source, hardlink=True)
1885
self.addCleanup(target.unlock)
1886
self.assertEqual([], list(target.iter_changes(revision_tree)))
1887
self.assertTrue(source.is_executable('file1-id'))
1889
def test_case_insensitive_build_tree_inventory(self):
1890
if (tests.CaseInsensitiveFilesystemFeature.available()
1891
or tests.CaseInsCasePresFilenameFeature.available()):
1892
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1893
source = self.make_branch_and_tree('source')
1894
self.build_tree(['source/file', 'source/FILE'])
1895
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1896
source.commit('added files')
1897
# Don't try this at home, kids!
1898
# Force the tree to report that it is case insensitive
1899
target = self.make_branch_and_tree('target')
1900
target.case_sensitive = False
1901
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1902
self.assertEqual('file.moved', target.id2path('lower-id'))
1903
self.assertEqual('FILE', target.id2path('upper-id'))
1906
class TestCommitTransform(tests.TestCaseWithTransport):
1908
def get_branch(self):
1909
tree = self.make_branch_and_tree('tree')
1911
self.addCleanup(tree.unlock)
1912
tree.commit('empty commit')
1915
def get_branch_and_transform(self):
1916
branch = self.get_branch()
1917
tt = TransformPreview(branch.basis_tree())
1918
self.addCleanup(tt.finalize)
1921
def test_commit_wrong_basis(self):
1922
branch = self.get_branch()
1923
basis = branch.repository.revision_tree(
1924
_mod_revision.NULL_REVISION)
1925
tt = TransformPreview(basis)
1926
self.addCleanup(tt.finalize)
1927
e = self.assertRaises(ValueError, tt.commit, branch, '')
1928
self.assertEqual('TreeTransform not based on branch basis: null:',
1931
def test_empy_commit(self):
1932
branch, tt = self.get_branch_and_transform()
1933
rev = tt.commit(branch, 'my message')
1934
self.assertEqual(2, branch.revno())
1935
repo = branch.repository
1936
self.assertEqual('my message', repo.get_revision(rev).message)
1938
def test_merge_parents(self):
1939
branch, tt = self.get_branch_and_transform()
1940
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
1941
self.assertEqual(['rev1b', 'rev1c'],
1942
branch.basis_tree().get_parent_ids()[1:])
1944
def test_first_commit(self):
1945
branch = self.make_branch('branch')
1947
self.addCleanup(branch.unlock)
1948
tt = TransformPreview(branch.basis_tree())
1949
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
1950
rev = tt.commit(branch, 'my message')
1951
self.assertEqual([], branch.basis_tree().get_parent_ids())
1952
self.assertNotEqual(_mod_revision.NULL_REVISION,
1953
branch.last_revision())
1955
def test_first_commit_with_merge_parents(self):
1956
branch = self.make_branch('branch')
1958
self.addCleanup(branch.unlock)
1959
tt = TransformPreview(branch.basis_tree())
1960
e = self.assertRaises(ValueError, tt.commit, branch,
1961
'my message', ['rev1b-id'])
1962
self.assertEqual('Cannot supply merge parents for first commit.',
1964
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
1966
def test_add_files(self):
1967
branch, tt = self.get_branch_and_transform()
1968
tt.new_file('file', tt.root, 'contents', 'file-id')
1969
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
1970
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
1971
rev = tt.commit(branch, 'message')
1972
tree = branch.basis_tree()
1973
self.assertEqual('file', tree.id2path('file-id'))
1974
self.assertEqual('contents', tree.get_file_text('file-id'))
1975
self.assertEqual('dir', tree.id2path('dir-id'))
1976
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
1977
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
1979
def test_add_unversioned(self):
1980
branch, tt = self.get_branch_and_transform()
1981
tt.new_file('file', tt.root, 'contents')
1982
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
1983
'message', strict=True)
1985
def test_modify_strict(self):
1986
branch, tt = self.get_branch_and_transform()
1987
tt.new_file('file', tt.root, 'contents', 'file-id')
1988
tt.commit(branch, 'message', strict=True)
1989
tt = TransformPreview(branch.basis_tree())
1990
trans_id = tt.trans_id_file_id('file-id')
1991
tt.delete_contents(trans_id)
1992
tt.create_file('contents', trans_id)
1993
tt.commit(branch, 'message', strict=True)
1995
def test_commit_malformed(self):
1996
"""Committing a malformed transform should raise an exception.
1998
In this case, we are adding a file without adding its parent.
2000
branch, tt = self.get_branch_and_transform()
2001
parent_id = tt.trans_id_file_id('parent-id')
2002
tt.new_file('file', parent_id, 'contents', 'file-id')
2003
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2007
class MockTransform(object):
2009
def has_named_child(self, by_parent, parent_id, name):
2010
for child_id in by_parent[parent_id]:
2014
elif name == "name.~%s~" % child_id:
2019
class MockEntry(object):
2021
object.__init__(self)
2025
class TestGetBackupName(TestCase):
2026
def test_get_backup_name(self):
2027
tt = MockTransform()
2028
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2029
self.assertEqual(name, 'name.~1~')
2030
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2031
self.assertEqual(name, 'name.~2~')
2032
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2033
self.assertEqual(name, 'name.~1~')
2034
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2035
self.assertEqual(name, 'name.~1~')
2036
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2037
self.assertEqual(name, 'name.~4~')
2040
class TestFileMover(tests.TestCaseWithTransport):
2042
def test_file_mover(self):
2043
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2044
mover = _FileMover()
2045
mover.rename('a', 'q')
2046
self.failUnlessExists('q')
2047
self.failIfExists('a')
2048
self.failUnlessExists('q/b')
2049
self.failUnlessExists('c')
2050
self.failUnlessExists('c/d')
2052
def test_pre_delete_rollback(self):
2053
self.build_tree(['a/'])
2054
mover = _FileMover()
2055
mover.pre_delete('a', 'q')
2056
self.failUnlessExists('q')
2057
self.failIfExists('a')
2059
self.failIfExists('q')
2060
self.failUnlessExists('a')
2062
def test_apply_deletions(self):
2063
self.build_tree(['a/', 'b/'])
2064
mover = _FileMover()
2065
mover.pre_delete('a', 'q')
2066
mover.pre_delete('b', 'r')
2067
self.failUnlessExists('q')
2068
self.failUnlessExists('r')
2069
self.failIfExists('a')
2070
self.failIfExists('b')
2071
mover.apply_deletions()
2072
self.failIfExists('q')
2073
self.failIfExists('r')
2074
self.failIfExists('a')
2075
self.failIfExists('b')
2077
def test_file_mover_rollback(self):
2078
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2079
mover = _FileMover()
2080
mover.rename('c/d', 'c/f')
2081
mover.rename('c/e', 'c/d')
2083
mover.rename('a', 'c')
2084
except errors.FileExists, e:
2086
self.failUnlessExists('a')
2087
self.failUnlessExists('c/d')
2090
class Bogus(Exception):
2094
class TestTransformRollback(tests.TestCaseWithTransport):
2096
class ExceptionFileMover(_FileMover):
2098
def __init__(self, bad_source=None, bad_target=None):
2099
_FileMover.__init__(self)
2100
self.bad_source = bad_source
2101
self.bad_target = bad_target
2103
def rename(self, source, target):
2104
if (self.bad_source is not None and
2105
source.endswith(self.bad_source)):
2107
elif (self.bad_target is not None and
2108
target.endswith(self.bad_target)):
2111
_FileMover.rename(self, source, target)
2113
def test_rollback_rename(self):
2114
tree = self.make_branch_and_tree('.')
2115
self.build_tree(['a/', 'a/b'])
2116
tt = TreeTransform(tree)
2117
self.addCleanup(tt.finalize)
2118
a_id = tt.trans_id_tree_path('a')
2119
tt.adjust_path('c', tt.root, a_id)
2120
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2121
self.assertRaises(Bogus, tt.apply,
2122
_mover=self.ExceptionFileMover(bad_source='a'))
2123
self.failUnlessExists('a')
2124
self.failUnlessExists('a/b')
2126
self.failUnlessExists('c')
2127
self.failUnlessExists('c/d')
2129
def test_rollback_rename_into_place(self):
2130
tree = self.make_branch_and_tree('.')
2131
self.build_tree(['a/', 'a/b'])
2132
tt = TreeTransform(tree)
2133
self.addCleanup(tt.finalize)
2134
a_id = tt.trans_id_tree_path('a')
2135
tt.adjust_path('c', tt.root, a_id)
2136
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2137
self.assertRaises(Bogus, tt.apply,
2138
_mover=self.ExceptionFileMover(bad_target='c/d'))
2139
self.failUnlessExists('a')
2140
self.failUnlessExists('a/b')
2142
self.failUnlessExists('c')
2143
self.failUnlessExists('c/d')
2145
def test_rollback_deletion(self):
2146
tree = self.make_branch_and_tree('.')
2147
self.build_tree(['a/', 'a/b'])
2148
tt = TreeTransform(tree)
2149
self.addCleanup(tt.finalize)
2150
a_id = tt.trans_id_tree_path('a')
2151
tt.delete_contents(a_id)
2152
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2153
self.assertRaises(Bogus, tt.apply,
2154
_mover=self.ExceptionFileMover(bad_target='d'))
2155
self.failUnlessExists('a')
2156
self.failUnlessExists('a/b')
2158
def test_resolve_no_parent(self):
2159
wt = self.make_branch_and_tree('.')
2160
tt = TreeTransform(wt)
2161
self.addCleanup(tt.finalize)
2162
parent = tt.trans_id_file_id('parent-id')
2163
tt.new_file('file', parent, 'Contents')
2164
resolve_conflicts(tt)
2167
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2168
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2170
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2171
('', ''), ('directory', 'directory'), (False, None))
2174
class TestTransformPreview(tests.TestCaseWithTransport):
2176
def create_tree(self):
2177
tree = self.make_branch_and_tree('.')
2178
self.build_tree_contents([('a', 'content 1')])
2179
tree.set_root_id('TREE_ROOT')
2180
tree.add('a', 'a-id')
2181
tree.commit('rev1', rev_id='rev1')
2182
return tree.branch.repository.revision_tree('rev1')
2184
def get_empty_preview(self):
2185
repository = self.make_repository('repo')
2186
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2187
preview = TransformPreview(tree)
2188
self.addCleanup(preview.finalize)
2191
def test_transform_preview(self):
2192
revision_tree = self.create_tree()
2193
preview = TransformPreview(revision_tree)
2194
self.addCleanup(preview.finalize)
2196
def test_transform_preview_tree(self):
2197
revision_tree = self.create_tree()
2198
preview = TransformPreview(revision_tree)
2199
self.addCleanup(preview.finalize)
2200
preview.get_preview_tree()
2202
def test_transform_new_file(self):
2203
revision_tree = self.create_tree()
2204
preview = TransformPreview(revision_tree)
2205
self.addCleanup(preview.finalize)
2206
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2207
preview_tree = preview.get_preview_tree()
2208
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2210
preview_tree.get_file('file2-id').read(), 'content B\n')
2212
def test_diff_preview_tree(self):
2213
revision_tree = self.create_tree()
2214
preview = TransformPreview(revision_tree)
2215
self.addCleanup(preview.finalize)
2216
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2217
preview_tree = preview.get_preview_tree()
2219
show_diff_trees(revision_tree, preview_tree, out)
2220
lines = out.getvalue().splitlines()
2221
self.assertEqual(lines[0], "=== added file 'file2'")
2222
# 3 lines of diff administrivia
2223
self.assertEqual(lines[4], "+content B")
2225
def test_transform_conflicts(self):
2226
revision_tree = self.create_tree()
2227
preview = TransformPreview(revision_tree)
2228
self.addCleanup(preview.finalize)
2229
preview.new_file('a', preview.root, 'content 2')
2230
resolve_conflicts(preview)
2231
trans_id = preview.trans_id_file_id('a-id')
2232
self.assertEqual('a.moved', preview.final_name(trans_id))
2234
def get_tree_and_preview_tree(self):
2235
revision_tree = self.create_tree()
2236
preview = TransformPreview(revision_tree)
2237
self.addCleanup(preview.finalize)
2238
a_trans_id = preview.trans_id_file_id('a-id')
2239
preview.delete_contents(a_trans_id)
2240
preview.create_file('b content', a_trans_id)
2241
preview_tree = preview.get_preview_tree()
2242
return revision_tree, preview_tree
2244
def test_iter_changes(self):
2245
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2246
root = revision_tree.inventory.root.file_id
2247
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2248
(root, root), ('a', 'a'), ('file', 'file'),
2250
list(preview_tree.iter_changes(revision_tree)))
2252
def test_include_unchanged_succeeds(self):
2253
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2254
changes = preview_tree.iter_changes(revision_tree,
2255
include_unchanged=True)
2256
root = revision_tree.inventory.root.file_id
2258
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2260
def test_specific_files(self):
2261
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2262
changes = preview_tree.iter_changes(revision_tree,
2263
specific_files=[''])
2264
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2266
def test_want_unversioned(self):
2267
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2268
changes = preview_tree.iter_changes(revision_tree,
2269
want_unversioned=True)
2270
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2272
def test_ignore_extra_trees_no_specific_files(self):
2273
# extra_trees is harmless without specific_files, so we'll silently
2274
# accept it, even though we won't use it.
2275
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2276
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2278
def test_ignore_require_versioned_no_specific_files(self):
2279
# require_versioned is meaningless without specific_files.
2280
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2281
preview_tree.iter_changes(revision_tree, require_versioned=False)
2283
def test_ignore_pb(self):
2284
# pb could be supported, but TT.iter_changes doesn't support it.
2285
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2286
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2288
def test_kind(self):
2289
revision_tree = self.create_tree()
2290
preview = TransformPreview(revision_tree)
2291
self.addCleanup(preview.finalize)
2292
preview.new_file('file', preview.root, 'contents', 'file-id')
2293
preview.new_directory('directory', preview.root, 'dir-id')
2294
preview_tree = preview.get_preview_tree()
2295
self.assertEqual('file', preview_tree.kind('file-id'))
2296
self.assertEqual('directory', preview_tree.kind('dir-id'))
2298
def test_get_file_mtime(self):
2299
preview = self.get_empty_preview()
2300
file_trans_id = preview.new_file('file', preview.root, 'contents',
2302
limbo_path = preview._limbo_name(file_trans_id)
2303
preview_tree = preview.get_preview_tree()
2304
self.assertEqual(os.stat(limbo_path).st_mtime,
2305
preview_tree.get_file_mtime('file-id'))
2307
def test_get_file(self):
2308
preview = self.get_empty_preview()
2309
preview.new_file('file', preview.root, 'contents', 'file-id')
2310
preview_tree = preview.get_preview_tree()
2311
tree_file = preview_tree.get_file('file-id')
2313
self.assertEqual('contents', tree_file.read())
2317
def test_get_symlink_target(self):
2318
self.requireFeature(SymlinkFeature)
2319
preview = self.get_empty_preview()
2320
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2321
preview_tree = preview.get_preview_tree()
2322
self.assertEqual('target',
2323
preview_tree.get_symlink_target('symlink-id'))
2325
def test_all_file_ids(self):
2326
tree = self.make_branch_and_tree('tree')
2327
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2328
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2329
preview = TransformPreview(tree)
2330
self.addCleanup(preview.finalize)
2331
preview.unversion_file(preview.trans_id_file_id('b-id'))
2332
c_trans_id = preview.trans_id_file_id('c-id')
2333
preview.unversion_file(c_trans_id)
2334
preview.version_file('c-id', c_trans_id)
2335
preview_tree = preview.get_preview_tree()
2336
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2337
preview_tree.all_file_ids())
2339
def test_path2id_deleted_unchanged(self):
2340
tree = self.make_branch_and_tree('tree')
2341
self.build_tree(['tree/unchanged', 'tree/deleted'])
2342
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2343
preview = TransformPreview(tree)
2344
self.addCleanup(preview.finalize)
2345
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2346
preview_tree = preview.get_preview_tree()
2347
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2348
self.assertIs(None, preview_tree.path2id('deleted'))
2350
def test_path2id_created(self):
2351
tree = self.make_branch_and_tree('tree')
2352
self.build_tree(['tree/unchanged'])
2353
tree.add(['unchanged'], ['unchanged-id'])
2354
preview = TransformPreview(tree)
2355
self.addCleanup(preview.finalize)
2356
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2357
'contents', 'new-id')
2358
preview_tree = preview.get_preview_tree()
2359
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2361
def test_path2id_moved(self):
2362
tree = self.make_branch_and_tree('tree')
2363
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2364
tree.add(['old_parent', 'old_parent/child'],
2365
['old_parent-id', 'child-id'])
2366
preview = TransformPreview(tree)
2367
self.addCleanup(preview.finalize)
2368
new_parent = preview.new_directory('new_parent', preview.root,
2370
preview.adjust_path('child', new_parent,
2371
preview.trans_id_file_id('child-id'))
2372
preview_tree = preview.get_preview_tree()
2373
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2374
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2376
def test_path2id_renamed_parent(self):
2377
tree = self.make_branch_and_tree('tree')
2378
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2379
tree.add(['old_name', 'old_name/child'],
2380
['parent-id', 'child-id'])
2381
preview = TransformPreview(tree)
2382
self.addCleanup(preview.finalize)
2383
preview.adjust_path('new_name', preview.root,
2384
preview.trans_id_file_id('parent-id'))
2385
preview_tree = preview.get_preview_tree()
2386
self.assertIs(None, preview_tree.path2id('old_name/child'))
2387
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2389
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2390
preview_tree = tt.get_preview_tree()
2391
preview_result = list(preview_tree.iter_entries_by_dir(
2395
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2396
self.assertEqual(actual_result, preview_result)
2398
def test_iter_entries_by_dir_new(self):
2399
tree = self.make_branch_and_tree('tree')
2400
tt = TreeTransform(tree)
2401
tt.new_file('new', tt.root, 'contents', 'new-id')
2402
self.assertMatchingIterEntries(tt)
2404
def test_iter_entries_by_dir_deleted(self):
2405
tree = self.make_branch_and_tree('tree')
2406
self.build_tree(['tree/deleted'])
2407
tree.add('deleted', 'deleted-id')
2408
tt = TreeTransform(tree)
2409
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2410
self.assertMatchingIterEntries(tt)
2412
def test_iter_entries_by_dir_unversioned(self):
2413
tree = self.make_branch_and_tree('tree')
2414
self.build_tree(['tree/removed'])
2415
tree.add('removed', 'removed-id')
2416
tt = TreeTransform(tree)
2417
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2418
self.assertMatchingIterEntries(tt)
2420
def test_iter_entries_by_dir_moved(self):
2421
tree = self.make_branch_and_tree('tree')
2422
self.build_tree(['tree/moved', 'tree/new_parent/'])
2423
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2424
tt = TreeTransform(tree)
2425
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2426
tt.trans_id_file_id('moved-id'))
2427
self.assertMatchingIterEntries(tt)
2429
def test_iter_entries_by_dir_specific_file_ids(self):
2430
tree = self.make_branch_and_tree('tree')
2431
tree.set_root_id('tree-root-id')
2432
self.build_tree(['tree/parent/', 'tree/parent/child'])
2433
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2434
tt = TreeTransform(tree)
2435
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2437
def test_symlink_content_summary(self):
2438
self.requireFeature(SymlinkFeature)
2439
preview = self.get_empty_preview()
2440
preview.new_symlink('path', preview.root, 'target', 'path-id')
2441
summary = preview.get_preview_tree().path_content_summary('path')
2442
self.assertEqual(('symlink', None, None, 'target'), summary)
2444
def test_missing_content_summary(self):
2445
preview = self.get_empty_preview()
2446
summary = preview.get_preview_tree().path_content_summary('path')
2447
self.assertEqual(('missing', None, None, None), summary)
2449
def test_deleted_content_summary(self):
2450
tree = self.make_branch_and_tree('tree')
2451
self.build_tree(['tree/path/'])
2453
preview = TransformPreview(tree)
2454
self.addCleanup(preview.finalize)
2455
preview.delete_contents(preview.trans_id_tree_path('path'))
2456
summary = preview.get_preview_tree().path_content_summary('path')
2457
self.assertEqual(('missing', None, None, None), summary)
2459
def test_file_content_summary_executable(self):
2460
if not osutils.supports_executable():
2461
raise tests.TestNotApplicable()
2462
preview = self.get_empty_preview()
2463
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2464
preview.set_executability(True, path_id)
2465
summary = preview.get_preview_tree().path_content_summary('path')
2466
self.assertEqual(4, len(summary))
2467
self.assertEqual('file', summary[0])
2468
# size must be known
2469
self.assertEqual(len('contents'), summary[1])
2471
self.assertEqual(True, summary[2])
2472
# will not have hash (not cheap to determine)
2473
self.assertIs(None, summary[3])
2475
def test_change_executability(self):
2476
tree = self.make_branch_and_tree('tree')
2477
self.build_tree(['tree/path'])
2479
preview = TransformPreview(tree)
2480
self.addCleanup(preview.finalize)
2481
path_id = preview.trans_id_tree_path('path')
2482
preview.set_executability(True, path_id)
2483
summary = preview.get_preview_tree().path_content_summary('path')
2484
self.assertEqual(True, summary[2])
2486
def test_file_content_summary_non_exec(self):
2487
preview = self.get_empty_preview()
2488
preview.new_file('path', preview.root, 'contents', 'path-id')
2489
summary = preview.get_preview_tree().path_content_summary('path')
2490
self.assertEqual(4, len(summary))
2491
self.assertEqual('file', summary[0])
2492
# size must be known
2493
self.assertEqual(len('contents'), summary[1])
2495
if osutils.supports_executable():
2496
self.assertEqual(False, summary[2])
2498
self.assertEqual(None, summary[2])
2499
# will not have hash (not cheap to determine)
2500
self.assertIs(None, summary[3])
2502
def test_dir_content_summary(self):
2503
preview = self.get_empty_preview()
2504
preview.new_directory('path', preview.root, 'path-id')
2505
summary = preview.get_preview_tree().path_content_summary('path')
2506
self.assertEqual(('directory', None, None, None), summary)
2508
def test_tree_content_summary(self):
2509
preview = self.get_empty_preview()
2510
path = preview.new_directory('path', preview.root, 'path-id')
2511
preview.set_tree_reference('rev-1', path)
2512
summary = preview.get_preview_tree().path_content_summary('path')
2513
self.assertEqual(4, len(summary))
2514
self.assertEqual('tree-reference', summary[0])
2516
def test_annotate(self):
2517
tree = self.make_branch_and_tree('tree')
2518
self.build_tree_contents([('tree/file', 'a\n')])
2519
tree.add('file', 'file-id')
2520
tree.commit('a', rev_id='one')
2521
self.build_tree_contents([('tree/file', 'a\nb\n')])
2522
preview = TransformPreview(tree)
2523
self.addCleanup(preview.finalize)
2524
file_trans_id = preview.trans_id_file_id('file-id')
2525
preview.delete_contents(file_trans_id)
2526
preview.create_file('a\nb\nc\n', file_trans_id)
2527
preview_tree = preview.get_preview_tree()
2533
annotation = preview_tree.annotate_iter('file-id', 'me:')
2534
self.assertEqual(expected, annotation)
2536
def test_annotate_missing(self):
2537
preview = self.get_empty_preview()
2538
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2539
preview_tree = preview.get_preview_tree()
2545
annotation = preview_tree.annotate_iter('file-id', 'me:')
2546
self.assertEqual(expected, annotation)
2548
def test_annotate_rename(self):
2549
tree = self.make_branch_and_tree('tree')
2550
self.build_tree_contents([('tree/file', 'a\n')])
2551
tree.add('file', 'file-id')
2552
tree.commit('a', rev_id='one')
2553
preview = TransformPreview(tree)
2554
self.addCleanup(preview.finalize)
2555
file_trans_id = preview.trans_id_file_id('file-id')
2556
preview.adjust_path('newname', preview.root, file_trans_id)
2557
preview_tree = preview.get_preview_tree()
2561
annotation = preview_tree.annotate_iter('file-id', 'me:')
2562
self.assertEqual(expected, annotation)
2564
def test_annotate_deleted(self):
2565
tree = self.make_branch_and_tree('tree')
2566
self.build_tree_contents([('tree/file', 'a\n')])
2567
tree.add('file', 'file-id')
2568
tree.commit('a', rev_id='one')
2569
self.build_tree_contents([('tree/file', 'a\nb\n')])
2570
preview = TransformPreview(tree)
2571
self.addCleanup(preview.finalize)
2572
file_trans_id = preview.trans_id_file_id('file-id')
2573
preview.delete_contents(file_trans_id)
2574
preview_tree = preview.get_preview_tree()
2575
annotation = preview_tree.annotate_iter('file-id', 'me:')
2576
self.assertIs(None, annotation)
2578
def test_stored_kind(self):
2579
preview = self.get_empty_preview()
2580
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2581
preview_tree = preview.get_preview_tree()
2582
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2584
def test_is_executable(self):
2585
preview = self.get_empty_preview()
2586
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2587
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2588
preview_tree = preview.get_preview_tree()
2589
self.assertEqual(True, preview_tree.is_executable('file-id'))
2591
def test_get_set_parent_ids(self):
2592
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2593
self.assertEqual([], preview_tree.get_parent_ids())
2594
preview_tree.set_parent_ids(['rev-1'])
2595
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2597
def test_plan_file_merge(self):
2598
work_a = self.make_branch_and_tree('wta')
2599
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2600
work_a.add('file', 'file-id')
2601
base_id = work_a.commit('base version')
2602
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2603
preview = TransformPreview(work_a)
2604
self.addCleanup(preview.finalize)
2605
trans_id = preview.trans_id_file_id('file-id')
2606
preview.delete_contents(trans_id)
2607
preview.create_file('b\nc\nd\ne\n', trans_id)
2608
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2609
tree_a = preview.get_preview_tree()
2610
tree_a.set_parent_ids([base_id])
2612
('killed-a', 'a\n'),
2613
('killed-b', 'b\n'),
2614
('unchanged', 'c\n'),
2615
('unchanged', 'd\n'),
2618
], list(tree_a.plan_file_merge('file-id', tree_b)))
2620
def test_plan_file_merge_revision_tree(self):
2621
work_a = self.make_branch_and_tree('wta')
2622
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2623
work_a.add('file', 'file-id')
2624
base_id = work_a.commit('base version')
2625
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2626
preview = TransformPreview(work_a.basis_tree())
2627
self.addCleanup(preview.finalize)
2628
trans_id = preview.trans_id_file_id('file-id')
2629
preview.delete_contents(trans_id)
2630
preview.create_file('b\nc\nd\ne\n', trans_id)
2631
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2632
tree_a = preview.get_preview_tree()
2633
tree_a.set_parent_ids([base_id])
2635
('killed-a', 'a\n'),
2636
('killed-b', 'b\n'),
2637
('unchanged', 'c\n'),
2638
('unchanged', 'd\n'),
2641
], list(tree_a.plan_file_merge('file-id', tree_b)))
2643
def test_walkdirs(self):
2644
preview = self.get_empty_preview()
2645
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2646
# FIXME: new_directory should mark root.
2647
preview.fixup_new_roots()
2648
preview_tree = preview.get_preview_tree()
2649
file_trans_id = preview.new_file('a', preview.root, 'contents',
2651
expected = [(('', 'tree-root'),
2652
[('a', 'a', 'file', None, 'a-id', 'file')])]
2653
self.assertEqual(expected, list(preview_tree.walkdirs()))
2655
def test_extras(self):
2656
work_tree = self.make_branch_and_tree('tree')
2657
self.build_tree(['tree/removed-file', 'tree/existing-file',
2658
'tree/not-removed-file'])
2659
work_tree.add(['removed-file', 'not-removed-file'])
2660
preview = TransformPreview(work_tree)
2661
self.addCleanup(preview.finalize)
2662
preview.new_file('new-file', preview.root, 'contents')
2663
preview.new_file('new-versioned-file', preview.root, 'contents',
2665
tree = preview.get_preview_tree()
2666
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2667
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2670
def test_merge_into_preview(self):
2671
work_tree = self.make_branch_and_tree('tree')
2672
self.build_tree_contents([('tree/file','b\n')])
2673
work_tree.add('file', 'file-id')
2674
work_tree.commit('first commit')
2675
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2676
self.build_tree_contents([('child/file','b\nc\n')])
2677
child_tree.commit('child commit')
2678
child_tree.lock_write()
2679
self.addCleanup(child_tree.unlock)
2680
work_tree.lock_write()
2681
self.addCleanup(work_tree.unlock)
2682
preview = TransformPreview(work_tree)
2683
self.addCleanup(preview.finalize)
2684
file_trans_id = preview.trans_id_file_id('file-id')
2685
preview.delete_contents(file_trans_id)
2686
preview.create_file('a\nb\n', file_trans_id)
2687
pb = progress.DummyProgress()
2688
preview_tree = preview.get_preview_tree()
2689
merger = Merger.from_revision_ids(pb, preview_tree,
2690
child_tree.branch.last_revision(),
2691
other_branch=child_tree.branch,
2692
tree_branch=work_tree.branch)
2693
merger.merge_type = Merge3Merger
2694
tt = merger.make_merger().make_preview_transform()
2695
self.addCleanup(tt.finalize)
2696
final_tree = tt.get_preview_tree()
2697
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2699
def test_merge_preview_into_workingtree(self):
2700
tree = self.make_branch_and_tree('tree')
2701
tree.set_root_id('TREE_ROOT')
2702
tt = TransformPreview(tree)
2703
self.addCleanup(tt.finalize)
2704
tt.new_file('name', tt.root, 'content', 'file-id')
2705
tree2 = self.make_branch_and_tree('tree2')
2706
tree2.set_root_id('TREE_ROOT')
2707
pb = progress.DummyProgress()
2708
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2709
pb, tree.basis_tree())
2710
merger.merge_type = Merge3Merger
2713
def test_merge_preview_into_workingtree_handles_conflicts(self):
2714
tree = self.make_branch_and_tree('tree')
2715
self.build_tree_contents([('tree/foo', 'bar')])
2716
tree.add('foo', 'foo-id')
2718
tt = TransformPreview(tree)
2719
self.addCleanup(tt.finalize)
2720
trans_id = tt.trans_id_file_id('foo-id')
2721
tt.delete_contents(trans_id)
2722
tt.create_file('baz', trans_id)
2723
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2724
self.build_tree_contents([('tree2/foo', 'qux')])
2725
pb = progress.DummyProgress()
2726
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2727
pb, tree.basis_tree())
2728
merger.merge_type = Merge3Merger
2731
def test_is_executable(self):
2732
tree = self.make_branch_and_tree('tree')
2733
preview = TransformPreview(tree)
2734
self.addCleanup(preview.finalize)
2735
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2736
preview_tree = preview.get_preview_tree()
2737
self.assertEqual(False, preview_tree.is_executable('baz-id',
2739
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2741
def test_commit_preview_tree(self):
2742
tree = self.make_branch_and_tree('tree')
2743
rev_id = tree.commit('rev1')
2744
tree.branch.lock_write()
2745
self.addCleanup(tree.branch.unlock)
2746
tt = TransformPreview(tree)
2747
tt.new_file('file', tt.root, 'contents', 'file_id')
2748
self.addCleanup(tt.finalize)
2749
preview = tt.get_preview_tree()
2750
preview.set_parent_ids([rev_id])
2751
builder = tree.branch.get_commit_builder([rev_id])
2752
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2753
builder.finish_inventory()
2754
rev2_id = builder.commit('rev2')
2755
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2756
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2758
def test_ascii_limbo_paths(self):
2759
self.requireFeature(tests.UnicodeFilenameFeature)
2760
branch = self.make_branch('any')
2761
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2762
tt = TransformPreview(tree)
2763
foo_id = tt.new_directory('', ROOT_PARENT)
2764
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2765
limbo_path = tt._limbo_name(bar_id)
2766
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2769
class FakeSerializer(object):
2770
"""Serializer implementation that simply returns the input.
2772
The input is returned in the order used by pack.ContainerPushParser.
2775
def bytes_record(bytes, names):
2779
class TestSerializeTransform(tests.TestCaseWithTransport):
2781
_test_needs_features = [tests.UnicodeFilenameFeature]
2783
def get_preview(self, tree=None):
2785
tree = self.make_branch_and_tree('tree')
2786
tt = TransformPreview(tree)
2787
self.addCleanup(tt.finalize)
2790
def assertSerializesTo(self, expected, tt):
2791
records = list(tt.serialize(FakeSerializer()))
2792
self.assertEqual(expected, records)
2795
def default_attribs():
2800
'_new_executability': {},
2802
'_tree_path_ids': {'': 'new-0'},
2804
'_removed_contents': [],
2805
'_non_present_ids': {},
2808
def make_records(self, attribs, contents):
2810
(((('attribs'),),), bencode.bencode(attribs))]
2811
records.extend([(((n, k),), c) for n, k, c in contents])
2814
def creation_records(self):
2815
attribs = self.default_attribs()
2816
attribs['_id_number'] = 3
2817
attribs['_new_name'] = {
2818
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2819
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2820
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2821
attribs['_new_executability'] = {'new-1': 1}
2823
('new-1', 'file', 'i 1\nbar\n'),
2824
('new-2', 'directory', ''),
2826
return self.make_records(attribs, contents)
2828
def test_serialize_creation(self):
2829
tt = self.get_preview()
2830
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2831
tt.new_directory('qux', tt.root, 'quxx')
2832
self.assertSerializesTo(self.creation_records(), tt)
2834
def test_deserialize_creation(self):
2835
tt = self.get_preview()
2836
tt.deserialize(iter(self.creation_records()))
2837
self.assertEqual(3, tt._id_number)
2838
self.assertEqual({'new-1': u'foo\u1234',
2839
'new-2': 'qux'}, tt._new_name)
2840
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2841
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2842
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2843
self.assertEqual({'new-1': True}, tt._new_executability)
2844
self.assertEqual({'new-1': 'file',
2845
'new-2': 'directory'}, tt._new_contents)
2846
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2848
foo_content = foo_limbo.read()
2851
self.assertEqual('bar', foo_content)
2853
def symlink_creation_records(self):
2854
attribs = self.default_attribs()
2855
attribs['_id_number'] = 2
2856
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2857
attribs['_new_parent'] = {'new-1': 'new-0'}
2858
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2859
return self.make_records(attribs, contents)
2861
def test_serialize_symlink_creation(self):
2862
self.requireFeature(tests.SymlinkFeature)
2863
tt = self.get_preview()
2864
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2865
self.assertSerializesTo(self.symlink_creation_records(), tt)
2867
def test_deserialize_symlink_creation(self):
2868
self.requireFeature(tests.SymlinkFeature)
2869
tt = self.get_preview()
2870
tt.deserialize(iter(self.symlink_creation_records()))
2871
abspath = tt._limbo_name('new-1')
2872
foo_content = osutils.readlink(abspath)
2873
self.assertEqual(u'bar\u1234', foo_content)
2875
def make_destruction_preview(self):
2876
tree = self.make_branch_and_tree('.')
2877
self.build_tree([u'foo\u1234', 'bar'])
2878
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2879
return self.get_preview(tree)
2881
def destruction_records(self):
2882
attribs = self.default_attribs()
2883
attribs['_id_number'] = 3
2884
attribs['_removed_id'] = ['new-1']
2885
attribs['_removed_contents'] = ['new-2']
2886
attribs['_tree_path_ids'] = {
2888
u'foo\u1234'.encode('utf-8'): 'new-1',
2891
return self.make_records(attribs, [])
2893
def test_serialize_destruction(self):
2894
tt = self.make_destruction_preview()
2895
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2896
tt.unversion_file(foo_trans_id)
2897
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2898
tt.delete_contents(bar_trans_id)
2899
self.assertSerializesTo(self.destruction_records(), tt)
2901
def test_deserialize_destruction(self):
2902
tt = self.make_destruction_preview()
2903
tt.deserialize(iter(self.destruction_records()))
2904
self.assertEqual({u'foo\u1234': 'new-1',
2906
'': tt.root}, tt._tree_path_ids)
2907
self.assertEqual({'new-1': u'foo\u1234',
2909
tt.root: ''}, tt._tree_id_paths)
2910
self.assertEqual(set(['new-1']), tt._removed_id)
2911
self.assertEqual(set(['new-2']), tt._removed_contents)
2913
def missing_records(self):
2914
attribs = self.default_attribs()
2915
attribs['_id_number'] = 2
2916
attribs['_non_present_ids'] = {
2918
return self.make_records(attribs, [])
2920
def test_serialize_missing(self):
2921
tt = self.get_preview()
2922
boo_trans_id = tt.trans_id_file_id('boo')
2923
self.assertSerializesTo(self.missing_records(), tt)
2925
def test_deserialize_missing(self):
2926
tt = self.get_preview()
2927
tt.deserialize(iter(self.missing_records()))
2928
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2930
def make_modification_preview(self):
2931
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2932
LINES_TWO = 'z\nbb\nx\ndd\n'
2933
tree = self.make_branch_and_tree('tree')
2934
self.build_tree_contents([('tree/file', LINES_ONE)])
2935
tree.add('file', 'file-id')
2936
return self.get_preview(tree), LINES_TWO
2938
def modification_records(self):
2939
attribs = self.default_attribs()
2940
attribs['_id_number'] = 2
2941
attribs['_tree_path_ids'] = {
2944
attribs['_removed_contents'] = ['new-1']
2945
contents = [('new-1', 'file',
2946
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2947
return self.make_records(attribs, contents)
2949
def test_serialize_modification(self):
2950
tt, LINES = self.make_modification_preview()
2951
trans_id = tt.trans_id_file_id('file-id')
2952
tt.delete_contents(trans_id)
2953
tt.create_file(LINES, trans_id)
2954
self.assertSerializesTo(self.modification_records(), tt)
2956
def test_deserialize_modification(self):
2957
tt, LINES = self.make_modification_preview()
2958
tt.deserialize(iter(self.modification_records()))
2959
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2961
def make_kind_change_preview(self):
2962
LINES = 'a\nb\nc\nd\n'
2963
tree = self.make_branch_and_tree('tree')
2964
self.build_tree(['tree/foo/'])
2965
tree.add('foo', 'foo-id')
2966
return self.get_preview(tree), LINES
2968
def kind_change_records(self):
2969
attribs = self.default_attribs()
2970
attribs['_id_number'] = 2
2971
attribs['_tree_path_ids'] = {
2974
attribs['_removed_contents'] = ['new-1']
2975
contents = [('new-1', 'file',
2976
'i 4\na\nb\nc\nd\n\n')]
2977
return self.make_records(attribs, contents)
2979
def test_serialize_kind_change(self):
2980
tt, LINES = self.make_kind_change_preview()
2981
trans_id = tt.trans_id_file_id('foo-id')
2982
tt.delete_contents(trans_id)
2983
tt.create_file(LINES, trans_id)
2984
self.assertSerializesTo(self.kind_change_records(), tt)
2986
def test_deserialize_kind_change(self):
2987
tt, LINES = self.make_kind_change_preview()
2988
tt.deserialize(iter(self.kind_change_records()))
2989
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2991
def make_add_contents_preview(self):
2992
LINES = 'a\nb\nc\nd\n'
2993
tree = self.make_branch_and_tree('tree')
2994
self.build_tree(['tree/foo'])
2996
os.unlink('tree/foo')
2997
return self.get_preview(tree), LINES
2999
def add_contents_records(self):
3000
attribs = self.default_attribs()
3001
attribs['_id_number'] = 2
3002
attribs['_tree_path_ids'] = {
3005
contents = [('new-1', 'file',
3006
'i 4\na\nb\nc\nd\n\n')]
3007
return self.make_records(attribs, contents)
3009
def test_serialize_add_contents(self):
3010
tt, LINES = self.make_add_contents_preview()
3011
trans_id = tt.trans_id_tree_path('foo')
3012
tt.create_file(LINES, trans_id)
3013
self.assertSerializesTo(self.add_contents_records(), tt)
3015
def test_deserialize_add_contents(self):
3016
tt, LINES = self.make_add_contents_preview()
3017
tt.deserialize(iter(self.add_contents_records()))
3018
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3020
def test_get_parents_lines(self):
3021
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3022
LINES_TWO = 'z\nbb\nx\ndd\n'
3023
tree = self.make_branch_and_tree('tree')
3024
self.build_tree_contents([('tree/file', LINES_ONE)])
3025
tree.add('file', 'file-id')
3026
tt = self.get_preview(tree)
3027
trans_id = tt.trans_id_tree_path('file')
3028
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3029
tt._get_parents_lines(trans_id))
3031
def test_get_parents_texts(self):
3032
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3033
LINES_TWO = 'z\nbb\nx\ndd\n'
3034
tree = self.make_branch_and_tree('tree')
3035
self.build_tree_contents([('tree/file', LINES_ONE)])
3036
tree.add('file', 'file-id')
3037
tt = self.get_preview(tree)
3038
trans_id = tt.trans_id_tree_path('file')
3039
self.assertEqual((LINES_ONE,),
3040
tt._get_parents_texts(trans_id))