1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from StringIO import StringIO
28
revision as _mod_revision,
33
from bzrlib.bzrdir import BzrDir
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
35
UnversionedParent, ParentLoop, DeletingParent,
37
from bzrlib.diff import show_diff_trees
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
40
PathsNotVersionedError, ExistingLimbo,
41
ExistingPendingDeletion, ImmortalLimbo,
42
ImmortalPendingDeletion, LockError)
43
from bzrlib.osutils import file_kind, pathjoin
44
from bzrlib.merge import Merge3Merger, Merger
45
from bzrlib.tests import (
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
resolve_conflicts, cook_conflicts,
54
build_tree, get_backup_name,
55
_FileMover, resolve_checkout,
56
TransformPreview, create_from_tree)
59
class TestTreeTransform(tests.TestCaseWithTransport):
62
super(TestTreeTransform, self).setUp()
63
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
66
def get_transform(self):
67
transform = TreeTransform(self.wt)
68
self.addCleanup(transform.finalize)
69
return transform, transform.root
71
def test_existing_limbo(self):
72
transform, root = self.get_transform()
73
limbo_name = transform._limbodir
74
deletion_path = transform._deletiondir
75
os.mkdir(pathjoin(limbo_name, 'hehe'))
76
self.assertRaises(ImmortalLimbo, transform.apply)
77
self.assertRaises(LockError, self.wt.unlock)
78
self.assertRaises(ExistingLimbo, self.get_transform)
79
self.assertRaises(LockError, self.wt.unlock)
80
os.rmdir(pathjoin(limbo_name, 'hehe'))
82
os.rmdir(deletion_path)
83
transform, root = self.get_transform()
86
def test_existing_pending_deletion(self):
87
transform, root = self.get_transform()
88
deletion_path = self._limbodir = urlutils.local_path_from_url(
89
transform._tree._transport.abspath('pending-deletion'))
90
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
91
self.assertRaises(ImmortalPendingDeletion, transform.apply)
92
self.assertRaises(LockError, self.wt.unlock)
93
self.assertRaises(ExistingPendingDeletion, self.get_transform)
96
transform, root = self.get_transform()
97
self.wt.lock_tree_write()
98
self.addCleanup(self.wt.unlock)
99
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
100
imaginary_id = transform.trans_id_tree_path('imaginary')
101
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
102
self.assertEqual(imaginary_id, imaginary_id2)
103
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
104
self.assertEqual(transform.final_kind(root), 'directory')
105
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
106
trans_id = transform.create_path('name', root)
107
self.assertIs(transform.final_file_id(trans_id), None)
108
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
109
transform.create_file('contents', trans_id)
110
transform.set_executability(True, trans_id)
111
transform.version_file('my_pretties', trans_id)
112
self.assertRaises(DuplicateKey, transform.version_file,
113
'my_pretties', trans_id)
114
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
115
self.assertEqual(transform.final_parent(trans_id), root)
116
self.assertIs(transform.final_parent(root), ROOT_PARENT)
117
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
118
oz_id = transform.create_path('oz', root)
119
transform.create_directory(oz_id)
120
transform.version_file('ozzie', oz_id)
121
trans_id2 = transform.create_path('name2', root)
122
transform.create_file('contents', trans_id2)
123
transform.set_executability(False, trans_id2)
124
transform.version_file('my_pretties2', trans_id2)
125
modified_paths = transform.apply().modified_paths
126
self.assertEqual('contents', self.wt.get_file_byname('name').read())
127
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
128
self.assertIs(self.wt.is_executable('my_pretties'), True)
129
self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
self.assertEqual(len(modified_paths), 3)
132
tree_mod_paths = [self.wt.id2abspath(f) for f in
133
('ozzie', 'my_pretties', 'my_pretties2')]
134
self.assertSubset(tree_mod_paths, modified_paths)
135
# is it safe to finalize repeatedly?
139
def test_hardlink(self):
140
self.requireFeature(HardlinkFeature)
141
transform, root = self.get_transform()
142
transform.new_file('file1', root, 'contents')
144
target = self.make_branch_and_tree('target')
145
target_transform = TreeTransform(target)
146
trans_id = target_transform.create_path('file1', target_transform.root)
147
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
148
target_transform.apply()
149
self.failUnlessExists('target/file1')
150
source_stat = os.stat(self.wt.abspath('file1'))
151
target_stat = os.stat('target/file1')
152
self.assertEqual(source_stat, target_stat)
154
def test_convenience(self):
155
transform, root = self.get_transform()
156
self.wt.lock_tree_write()
157
self.addCleanup(self.wt.unlock)
158
trans_id = transform.new_file('name', root, 'contents',
160
oz = transform.new_directory('oz', root, 'oz-id')
161
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
toto = transform.new_file('toto', dorothy, 'toto-contents',
165
self.assertEqual(len(transform.find_conflicts()), 0)
167
self.assertRaises(ReusingTransform, transform.find_conflicts)
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
169
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
170
self.assertIs(self.wt.is_executable('my_pretties'), True)
171
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
172
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
173
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
175
self.assertEqual('toto-contents',
176
self.wt.get_file_byname('oz/dorothy/toto').read())
177
self.assertIs(self.wt.is_executable('toto-id'), False)
179
def test_tree_reference(self):
180
transform, root = self.get_transform()
181
tree = transform._tree
182
trans_id = transform.new_directory('reference', root, 'subtree-id')
183
transform.set_tree_reference('subtree-revision', trans_id)
186
self.addCleanup(tree.unlock)
187
self.assertEqual('subtree-revision',
188
tree.inventory['subtree-id'].reference_revision)
190
def test_conflicts(self):
191
transform, root = self.get_transform()
192
trans_id = transform.new_file('name', root, 'contents',
194
self.assertEqual(len(transform.find_conflicts()), 0)
195
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
self.assertEqual(transform.find_conflicts(),
197
[('duplicate', trans_id, trans_id2, 'name')])
198
self.assertRaises(MalformedTransform, transform.apply)
199
transform.adjust_path('name', trans_id, trans_id2)
200
self.assertEqual(transform.find_conflicts(),
201
[('non-directory parent', trans_id)])
202
tinman_id = transform.trans_id_tree_path('tinman')
203
transform.adjust_path('name', tinman_id, trans_id2)
204
self.assertEqual(transform.find_conflicts(),
205
[('unversioned parent', tinman_id),
206
('missing parent', tinman_id)])
207
lion_id = transform.create_path('lion', root)
208
self.assertEqual(transform.find_conflicts(),
209
[('unversioned parent', tinman_id),
210
('missing parent', tinman_id)])
211
transform.adjust_path('name', lion_id, trans_id2)
212
self.assertEqual(transform.find_conflicts(),
213
[('unversioned parent', lion_id),
214
('missing parent', lion_id)])
215
transform.version_file("Courage", lion_id)
216
self.assertEqual(transform.find_conflicts(),
217
[('missing parent', lion_id),
218
('versioning no contents', lion_id)])
219
transform.adjust_path('name2', root, trans_id2)
220
self.assertEqual(transform.find_conflicts(),
221
[('versioning no contents', lion_id)])
222
transform.create_file('Contents, okay?', lion_id)
223
transform.adjust_path('name2', trans_id2, trans_id2)
224
self.assertEqual(transform.find_conflicts(),
225
[('parent loop', trans_id2),
226
('non-directory parent', trans_id2)])
227
transform.adjust_path('name2', root, trans_id2)
228
oz_id = transform.new_directory('oz', root)
229
transform.set_executability(True, oz_id)
230
self.assertEqual(transform.find_conflicts(),
231
[('unversioned executability', oz_id)])
232
transform.version_file('oz-id', oz_id)
233
self.assertEqual(transform.find_conflicts(),
234
[('non-file executability', oz_id)])
235
transform.set_executability(None, oz_id)
236
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
238
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
239
self.assertEqual('contents', file(self.wt.abspath('name')).read())
240
transform2, root = self.get_transform()
241
oz_id = transform2.trans_id_tree_file_id('oz-id')
242
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
243
result = transform2.find_conflicts()
244
fp = FinalPaths(transform2)
245
self.assert_('oz/tip' in transform2._tree_path_ids)
246
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
self.assertEqual(len(result), 2)
248
self.assertEqual((result[0][0], result[0][1]),
249
('duplicate', newtip))
250
self.assertEqual((result[1][0], result[1][2]),
251
('duplicate id', newtip))
252
transform2.finalize()
253
transform3 = TreeTransform(self.wt)
254
self.addCleanup(transform3.finalize)
255
oz_id = transform3.trans_id_tree_file_id('oz-id')
256
transform3.delete_contents(oz_id)
257
self.assertEqual(transform3.find_conflicts(),
258
[('missing parent', oz_id)])
259
root_id = transform3.root
260
tip_id = transform3.trans_id_tree_file_id('tip-id')
261
transform3.adjust_path('tip', root_id, tip_id)
264
def test_conflict_on_case_insensitive(self):
265
tree = self.make_branch_and_tree('tree')
266
# Don't try this at home, kids!
267
# Force the tree to report that it is case sensitive, for conflict
269
tree.case_sensitive = True
270
transform = TreeTransform(tree)
271
self.addCleanup(transform.finalize)
272
transform.new_file('file', transform.root, 'content')
273
transform.new_file('FiLe', transform.root, 'content')
274
result = transform.find_conflicts()
275
self.assertEqual([], result)
277
# Force the tree to report that it is case insensitive, for conflict
279
tree.case_sensitive = False
280
transform = TreeTransform(tree)
281
self.addCleanup(transform.finalize)
282
transform.new_file('file', transform.root, 'content')
283
transform.new_file('FiLe', transform.root, 'content')
284
result = transform.find_conflicts()
285
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
287
def test_conflict_on_case_insensitive_existing(self):
288
tree = self.make_branch_and_tree('tree')
289
self.build_tree(['tree/FiLe'])
290
# Don't try this at home, kids!
291
# Force the tree to report that it is case sensitive, for conflict
293
tree.case_sensitive = True
294
transform = TreeTransform(tree)
295
self.addCleanup(transform.finalize)
296
transform.new_file('file', transform.root, 'content')
297
result = transform.find_conflicts()
298
self.assertEqual([], result)
300
# Force the tree to report that it is case insensitive, for conflict
302
tree.case_sensitive = False
303
transform = TreeTransform(tree)
304
self.addCleanup(transform.finalize)
305
transform.new_file('file', transform.root, 'content')
306
result = transform.find_conflicts()
307
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
309
def test_resolve_case_insensitive_conflict(self):
310
tree = self.make_branch_and_tree('tree')
311
# Don't try this at home, kids!
312
# Force the tree to report that it is case insensitive, for conflict
314
tree.case_sensitive = False
315
transform = TreeTransform(tree)
316
self.addCleanup(transform.finalize)
317
transform.new_file('file', transform.root, 'content')
318
transform.new_file('FiLe', transform.root, 'content')
319
resolve_conflicts(transform)
321
self.failUnlessExists('tree/file')
322
self.failUnlessExists('tree/FiLe.moved')
324
def test_resolve_checkout_case_conflict(self):
325
tree = self.make_branch_and_tree('tree')
326
# Don't try this at home, kids!
327
# Force the tree to report that it is case insensitive, for conflict
329
tree.case_sensitive = False
330
transform = TreeTransform(tree)
331
self.addCleanup(transform.finalize)
332
transform.new_file('file', transform.root, 'content')
333
transform.new_file('FiLe', transform.root, 'content')
334
resolve_conflicts(transform,
335
pass_func=lambda t, c: resolve_checkout(t, c, []))
337
self.failUnlessExists('tree/file')
338
self.failUnlessExists('tree/FiLe.moved')
340
def test_apply_case_conflict(self):
341
"""Ensure that a transform with case conflicts can always be applied"""
342
tree = self.make_branch_and_tree('tree')
343
transform = TreeTransform(tree)
344
self.addCleanup(transform.finalize)
345
transform.new_file('file', transform.root, 'content')
346
transform.new_file('FiLe', transform.root, 'content')
347
dir = transform.new_directory('dir', transform.root)
348
transform.new_file('dirfile', dir, 'content')
349
transform.new_file('dirFiLe', dir, 'content')
350
resolve_conflicts(transform)
352
self.failUnlessExists('tree/file')
353
if not os.path.exists('tree/FiLe.moved'):
354
self.failUnlessExists('tree/FiLe')
355
self.failUnlessExists('tree/dir/dirfile')
356
if not os.path.exists('tree/dir/dirFiLe.moved'):
357
self.failUnlessExists('tree/dir/dirFiLe')
359
def test_case_insensitive_limbo(self):
360
tree = self.make_branch_and_tree('tree')
361
# Don't try this at home, kids!
362
# Force the tree to report that it is case insensitive
363
tree.case_sensitive = False
364
transform = TreeTransform(tree)
365
self.addCleanup(transform.finalize)
366
dir = transform.new_directory('dir', transform.root)
367
first = transform.new_file('file', dir, 'content')
368
second = transform.new_file('FiLe', dir, 'content')
369
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
372
def test_add_del(self):
373
start, root = self.get_transform()
374
start.new_directory('a', root, 'a')
376
transform, root = self.get_transform()
377
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
378
transform.new_directory('a', root, 'a')
381
def test_unversioning(self):
382
create_tree, root = self.get_transform()
383
parent_id = create_tree.new_directory('parent', root, 'parent-id')
384
create_tree.new_file('child', parent_id, 'child', 'child-id')
386
unversion = TreeTransform(self.wt)
387
self.addCleanup(unversion.finalize)
388
parent = unversion.trans_id_tree_path('parent')
389
unversion.unversion_file(parent)
390
self.assertEqual(unversion.find_conflicts(),
391
[('unversioned parent', parent_id)])
392
file_id = unversion.trans_id_tree_file_id('child-id')
393
unversion.unversion_file(file_id)
396
def test_name_invariants(self):
397
create_tree, root = self.get_transform()
399
root = create_tree.root
400
create_tree.new_file('name1', root, 'hello1', 'name1')
401
create_tree.new_file('name2', root, 'hello2', 'name2')
402
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
403
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
404
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
405
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
408
mangle_tree,root = self.get_transform()
409
root = mangle_tree.root
411
name1 = mangle_tree.trans_id_tree_file_id('name1')
412
name2 = mangle_tree.trans_id_tree_file_id('name2')
413
mangle_tree.adjust_path('name2', root, name1)
414
mangle_tree.adjust_path('name1', root, name2)
416
#tests for deleting parent directories
417
ddir = mangle_tree.trans_id_tree_file_id('ddir')
418
mangle_tree.delete_contents(ddir)
419
dfile = mangle_tree.trans_id_tree_file_id('dfile')
420
mangle_tree.delete_versioned(dfile)
421
mangle_tree.unversion_file(dfile)
422
mfile = mangle_tree.trans_id_tree_file_id('mfile')
423
mangle_tree.adjust_path('mfile', root, mfile)
425
#tests for adding parent directories
426
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
427
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
428
mangle_tree.adjust_path('mfile2', newdir, mfile2)
429
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
430
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
431
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
432
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
434
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
435
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
436
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
437
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
438
self.assertEqual(file(mfile2_path).read(), 'later2')
439
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
440
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
441
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
442
self.assertEqual(file(newfile_path).read(), 'hello3')
443
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
444
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
445
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
447
def test_both_rename(self):
448
create_tree,root = self.get_transform()
449
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
450
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
452
mangle_tree,root = self.get_transform()
453
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
454
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
455
mangle_tree.adjust_path('test', root, selftest)
456
mangle_tree.adjust_path('test_too_much', root, selftest)
457
mangle_tree.set_executability(True, blackbox)
460
def test_both_rename2(self):
461
create_tree,root = self.get_transform()
462
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
463
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
464
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
465
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
468
mangle_tree,root = self.get_transform()
469
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
470
tests = mangle_tree.trans_id_tree_file_id('tests-id')
471
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
472
mangle_tree.adjust_path('selftest', bzrlib, tests)
473
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
474
mangle_tree.set_executability(True, test_too_much)
477
def test_both_rename3(self):
478
create_tree,root = self.get_transform()
479
tests = create_tree.new_directory('tests', root, 'tests-id')
480
create_tree.new_file('test_too_much.py', tests, 'hello1',
483
mangle_tree,root = self.get_transform()
484
tests = mangle_tree.trans_id_tree_file_id('tests-id')
485
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
486
mangle_tree.adjust_path('selftest', root, tests)
487
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
488
mangle_tree.set_executability(True, test_too_much)
491
def test_move_dangling_ie(self):
492
create_tree, root = self.get_transform()
494
root = create_tree.root
495
create_tree.new_file('name1', root, 'hello1', 'name1')
497
delete_contents, root = self.get_transform()
498
file = delete_contents.trans_id_tree_file_id('name1')
499
delete_contents.delete_contents(file)
500
delete_contents.apply()
501
move_id, root = self.get_transform()
502
name1 = move_id.trans_id_tree_file_id('name1')
503
newdir = move_id.new_directory('dir', root, 'newdir')
504
move_id.adjust_path('name2', newdir, name1)
507
def test_replace_dangling_ie(self):
508
create_tree, root = self.get_transform()
510
root = create_tree.root
511
create_tree.new_file('name1', root, 'hello1', 'name1')
513
delete_contents = TreeTransform(self.wt)
514
self.addCleanup(delete_contents.finalize)
515
file = delete_contents.trans_id_tree_file_id('name1')
516
delete_contents.delete_contents(file)
517
delete_contents.apply()
518
delete_contents.finalize()
519
replace = TreeTransform(self.wt)
520
self.addCleanup(replace.finalize)
521
name2 = replace.new_file('name2', root, 'hello2', 'name1')
522
conflicts = replace.find_conflicts()
523
name1 = replace.trans_id_tree_file_id('name1')
524
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
525
resolve_conflicts(replace)
528
def _test_symlinks(self, link_name1,link_target1,
529
link_name2, link_target2):
531
def ozpath(p): return 'oz/' + p
533
self.requireFeature(SymlinkFeature)
534
transform, root = self.get_transform()
535
oz_id = transform.new_directory('oz', root, 'oz-id')
536
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
538
wiz_id = transform.create_path(link_name2, oz_id)
539
transform.create_symlink(link_target2, wiz_id)
540
transform.version_file('wiz-id2', wiz_id)
541
transform.set_executability(True, wiz_id)
542
self.assertEqual(transform.find_conflicts(),
543
[('non-file executability', wiz_id)])
544
transform.set_executability(None, wiz_id)
546
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
547
self.assertEqual('symlink',
548
file_kind(self.wt.abspath(ozpath(link_name1))))
549
self.assertEqual(link_target2,
550
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
551
self.assertEqual(link_target1,
552
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
554
def test_symlinks(self):
555
self._test_symlinks('wizard', 'wizard-target',
556
'wizard2', 'behind_curtain')
558
def test_symlinks_unicode(self):
559
self.requireFeature(tests.UnicodeFilenameFeature)
560
self._test_symlinks(u'\N{Euro Sign}wizard',
561
u'wizard-targ\N{Euro Sign}t',
562
u'\N{Euro Sign}wizard2',
563
u'b\N{Euro Sign}hind_curtain')
565
def test_unable_create_symlink(self):
567
wt = self.make_branch_and_tree('.')
568
tt = TreeTransform(wt) # TreeTransform obtains write lock
570
tt.new_symlink('foo', tt.root, 'bar')
574
os_symlink = getattr(os, 'symlink', None)
577
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
579
"Unable to create symlink 'foo' on this platform",
583
os.symlink = os_symlink
585
def get_conflicted(self):
586
create,root = self.get_transform()
587
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
588
oz = create.new_directory('oz', root, 'oz-id')
589
create.new_directory('emeraldcity', oz, 'emerald-id')
591
conflicts,root = self.get_transform()
592
# set up duplicate entry, duplicate id
593
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
595
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
596
oz = conflicts.trans_id_tree_file_id('oz-id')
597
# set up DeletedParent parent conflict
598
conflicts.delete_versioned(oz)
599
emerald = conflicts.trans_id_tree_file_id('emerald-id')
600
# set up MissingParent conflict
601
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
602
conflicts.adjust_path('munchkincity', root, munchkincity)
603
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
605
conflicts.adjust_path('emeraldcity', emerald, emerald)
606
return conflicts, emerald, oz, old_dorothy, new_dorothy
608
def test_conflict_resolution(self):
609
conflicts, emerald, oz, old_dorothy, new_dorothy =\
610
self.get_conflicted()
611
resolve_conflicts(conflicts)
612
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
613
self.assertIs(conflicts.final_file_id(old_dorothy), None)
614
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
615
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
616
self.assertEqual(conflicts.final_parent(emerald), oz)
619
def test_cook_conflicts(self):
620
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
621
raw_conflicts = resolve_conflicts(tt)
622
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
623
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
624
'dorothy', None, 'dorothy-id')
625
self.assertEqual(cooked_conflicts[0], duplicate)
626
duplicate_id = DuplicateID('Unversioned existing file',
627
'dorothy.moved', 'dorothy', None,
629
self.assertEqual(cooked_conflicts[1], duplicate_id)
630
missing_parent = MissingParent('Created directory', 'munchkincity',
632
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
633
self.assertEqual(cooked_conflicts[2], missing_parent)
634
unversioned_parent = UnversionedParent('Versioned directory',
637
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
639
self.assertEqual(cooked_conflicts[3], unversioned_parent)
640
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
641
'oz/emeraldcity', 'emerald-id', 'emerald-id')
642
self.assertEqual(cooked_conflicts[4], deleted_parent)
643
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
644
self.assertEqual(cooked_conflicts[6], parent_loop)
645
self.assertEqual(len(cooked_conflicts), 7)
648
def test_string_conflicts(self):
649
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
650
raw_conflicts = resolve_conflicts(tt)
651
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
653
conflicts_s = [str(c) for c in cooked_conflicts]
654
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
655
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
656
'Moved existing file to '
658
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
659
'Unversioned existing file '
661
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
662
' munchkincity. Created directory.')
663
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
664
' versioned, but has versioned'
665
' children. Versioned directory.')
666
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
667
" is not empty. Not deleting.")
668
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
669
' versioned, but has versioned'
670
' children. Versioned directory.')
671
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
672
' oz/emeraldcity. Cancelled move.')
674
def prepare_wrong_parent_kind(self):
675
tt, root = self.get_transform()
676
tt.new_file('parent', root, 'contents', 'parent-id')
678
tt, root = self.get_transform()
679
parent_id = tt.trans_id_file_id('parent-id')
680
tt.new_file('child,', parent_id, 'contents2', 'file-id')
683
def test_find_conflicts_wrong_parent_kind(self):
684
tt = self.prepare_wrong_parent_kind()
687
def test_resolve_conflicts_wrong_existing_parent_kind(self):
688
tt = self.prepare_wrong_parent_kind()
689
raw_conflicts = resolve_conflicts(tt)
690
self.assertEqual(set([('non-directory parent', 'Created directory',
691
'new-3')]), raw_conflicts)
692
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
693
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
694
'parent-id')], cooked_conflicts)
696
self.assertEqual(None, self.wt.path2id('parent'))
697
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
699
def test_resolve_conflicts_wrong_new_parent_kind(self):
700
tt, root = self.get_transform()
701
parent_id = tt.new_directory('parent', root, 'parent-id')
702
tt.new_file('child,', parent_id, 'contents2', 'file-id')
704
tt, root = self.get_transform()
705
parent_id = tt.trans_id_file_id('parent-id')
706
tt.delete_contents(parent_id)
707
tt.create_file('contents', parent_id)
708
raw_conflicts = resolve_conflicts(tt)
709
self.assertEqual(set([('non-directory parent', 'Created directory',
710
'new-3')]), raw_conflicts)
712
self.assertEqual(None, self.wt.path2id('parent'))
713
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
715
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
716
tt, root = self.get_transform()
717
parent_id = tt.new_directory('parent', root)
718
tt.new_file('child,', parent_id, 'contents2')
720
tt, root = self.get_transform()
721
parent_id = tt.trans_id_tree_path('parent')
722
tt.delete_contents(parent_id)
723
tt.create_file('contents', parent_id)
724
resolve_conflicts(tt)
726
self.assertIs(None, self.wt.path2id('parent'))
727
self.assertIs(None, self.wt.path2id('parent.new'))
729
def test_moving_versioned_directories(self):
730
create, root = self.get_transform()
731
kansas = create.new_directory('kansas', root, 'kansas-id')
732
create.new_directory('house', kansas, 'house-id')
733
create.new_directory('oz', root, 'oz-id')
735
cyclone, root = self.get_transform()
736
oz = cyclone.trans_id_tree_file_id('oz-id')
737
house = cyclone.trans_id_tree_file_id('house-id')
738
cyclone.adjust_path('house', oz, house)
741
def test_moving_root(self):
742
create, root = self.get_transform()
743
fun = create.new_directory('fun', root, 'fun-id')
744
create.new_directory('sun', root, 'sun-id')
745
create.new_directory('moon', root, 'moon')
747
transform, root = self.get_transform()
748
transform.adjust_root_path('oldroot', fun)
749
new_root=transform.trans_id_tree_path('')
750
transform.version_file('new-root', new_root)
753
def test_renames(self):
754
create, root = self.get_transform()
755
old = create.new_directory('old-parent', root, 'old-id')
756
intermediate = create.new_directory('intermediate', old, 'im-id')
757
myfile = create.new_file('myfile', intermediate, 'myfile-text',
760
rename, root = self.get_transform()
761
old = rename.trans_id_file_id('old-id')
762
rename.adjust_path('new', root, old)
763
myfile = rename.trans_id_file_id('myfile-id')
764
rename.set_executability(True, myfile)
767
def test_set_executability_order(self):
768
"""Ensure that executability behaves the same, no matter what order.
770
- create file and set executability simultaneously
771
- create file and set executability afterward
772
- unsetting the executability of a file whose executability has not been
773
declared should throw an exception (this may happen when a
774
merge attempts to create a file with a duplicate ID)
776
transform, root = self.get_transform()
779
self.addCleanup(wt.unlock)
780
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
782
sac = transform.new_file('set_after_creation', root,
783
'Set after creation', 'sac')
784
transform.set_executability(True, sac)
785
uws = transform.new_file('unset_without_set', root, 'Unset badly',
787
self.assertRaises(KeyError, transform.set_executability, None, uws)
789
self.assertTrue(wt.is_executable('soc'))
790
self.assertTrue(wt.is_executable('sac'))
792
def test_preserve_mode(self):
793
"""File mode is preserved when replacing content"""
794
if sys.platform == 'win32':
795
raise TestSkipped('chmod has no effect on win32')
796
transform, root = self.get_transform()
797
transform.new_file('file1', root, 'contents', 'file1-id', True)
800
self.addCleanup(self.wt.unlock)
801
self.assertTrue(self.wt.is_executable('file1-id'))
802
transform, root = self.get_transform()
803
file1_id = transform.trans_id_tree_file_id('file1-id')
804
transform.delete_contents(file1_id)
805
transform.create_file('contents2', file1_id)
807
self.assertTrue(self.wt.is_executable('file1-id'))
809
def test__set_mode_stats_correctly(self):
810
"""_set_mode stats to determine file mode."""
811
if sys.platform == 'win32':
812
raise TestSkipped('chmod has no effect on win32')
816
def instrumented_stat(path):
817
stat_paths.append(path)
818
return real_stat(path)
820
transform, root = self.get_transform()
822
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
823
file_id='bar-id-1', executable=False)
826
transform, root = self.get_transform()
827
bar1_id = transform.trans_id_tree_path('bar')
828
bar2_id = transform.trans_id_tree_path('bar2')
830
os.stat = instrumented_stat
831
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
836
bar1_abspath = self.wt.abspath('bar')
837
self.assertEqual([bar1_abspath], stat_paths)
839
def test_iter_changes(self):
840
self.wt.set_root_id('eert_toor')
841
transform, root = self.get_transform()
842
transform.new_file('old', root, 'blah', 'id-1', True)
844
transform, root = self.get_transform()
846
self.assertEqual([], list(transform.iter_changes()))
847
old = transform.trans_id_tree_file_id('id-1')
848
transform.unversion_file(old)
849
self.assertEqual([('id-1', ('old', None), False, (True, False),
850
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
851
(True, True))], list(transform.iter_changes()))
852
transform.new_directory('new', root, 'id-1')
853
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
854
('eert_toor', 'eert_toor'), ('old', 'new'),
855
('file', 'directory'),
856
(True, False))], list(transform.iter_changes()))
860
def test_iter_changes_new(self):
861
self.wt.set_root_id('eert_toor')
862
transform, root = self.get_transform()
863
transform.new_file('old', root, 'blah')
865
transform, root = self.get_transform()
867
old = transform.trans_id_tree_path('old')
868
transform.version_file('id-1', old)
869
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
870
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
871
(False, False))], list(transform.iter_changes()))
875
def test_iter_changes_modifications(self):
876
self.wt.set_root_id('eert_toor')
877
transform, root = self.get_transform()
878
transform.new_file('old', root, 'blah', 'id-1')
879
transform.new_file('new', root, 'blah')
880
transform.new_directory('subdir', root, 'subdir-id')
882
transform, root = self.get_transform()
884
old = transform.trans_id_tree_path('old')
885
subdir = transform.trans_id_tree_file_id('subdir-id')
886
new = transform.trans_id_tree_path('new')
887
self.assertEqual([], list(transform.iter_changes()))
890
transform.delete_contents(old)
891
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
892
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
893
(False, False))], list(transform.iter_changes()))
896
transform.create_file('blah', old)
897
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
898
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
899
(False, False))], list(transform.iter_changes()))
900
transform.cancel_deletion(old)
901
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
902
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
903
(False, False))], list(transform.iter_changes()))
904
transform.cancel_creation(old)
906
# move file_id to a different file
907
self.assertEqual([], list(transform.iter_changes()))
908
transform.unversion_file(old)
909
transform.version_file('id-1', new)
910
transform.adjust_path('old', root, new)
911
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
912
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
913
(False, False))], list(transform.iter_changes()))
914
transform.cancel_versioning(new)
915
transform._removed_id = set()
918
self.assertEqual([], list(transform.iter_changes()))
919
transform.set_executability(True, old)
920
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
921
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
922
(False, True))], list(transform.iter_changes()))
923
transform.set_executability(None, old)
926
self.assertEqual([], list(transform.iter_changes()))
927
transform.adjust_path('new', root, old)
928
transform._new_parent = {}
929
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
930
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
931
(False, False))], list(transform.iter_changes()))
932
transform._new_name = {}
935
self.assertEqual([], list(transform.iter_changes()))
936
transform.adjust_path('new', subdir, old)
937
transform._new_name = {}
938
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
939
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
940
('file', 'file'), (False, False))],
941
list(transform.iter_changes()))
942
transform._new_path = {}
947
def test_iter_changes_modified_bleed(self):
948
self.wt.set_root_id('eert_toor')
949
"""Modified flag should not bleed from one change to another"""
950
# unfortunately, we have no guarantee that file1 (which is modified)
951
# will be applied before file2. And if it's applied after file2, it
952
# obviously can't bleed into file2's change output. But for now, it
954
transform, root = self.get_transform()
955
transform.new_file('file1', root, 'blah', 'id-1')
956
transform.new_file('file2', root, 'blah', 'id-2')
958
transform, root = self.get_transform()
960
transform.delete_contents(transform.trans_id_file_id('id-1'))
961
transform.set_executability(True,
962
transform.trans_id_file_id('id-2'))
963
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
964
('eert_toor', 'eert_toor'), ('file1', u'file1'),
965
('file', None), (False, False)),
966
('id-2', (u'file2', u'file2'), False, (True, True),
967
('eert_toor', 'eert_toor'), ('file2', u'file2'),
968
('file', 'file'), (False, True))],
969
list(transform.iter_changes()))
973
def test_iter_changes_move_missing(self):
974
"""Test moving ids with no files around"""
975
self.wt.set_root_id('toor_eert')
976
# Need two steps because versioning a non-existant file is a conflict.
977
transform, root = self.get_transform()
978
transform.new_directory('floater', root, 'floater-id')
980
transform, root = self.get_transform()
981
transform.delete_contents(transform.trans_id_tree_path('floater'))
983
transform, root = self.get_transform()
984
floater = transform.trans_id_tree_path('floater')
986
transform.adjust_path('flitter', root, floater)
987
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
988
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
989
(None, None), (False, False))], list(transform.iter_changes()))
993
def test_iter_changes_pointless(self):
994
"""Ensure that no-ops are not treated as modifications"""
995
self.wt.set_root_id('eert_toor')
996
transform, root = self.get_transform()
997
transform.new_file('old', root, 'blah', 'id-1')
998
transform.new_directory('subdir', root, 'subdir-id')
1000
transform, root = self.get_transform()
1002
old = transform.trans_id_tree_path('old')
1003
subdir = transform.trans_id_tree_file_id('subdir-id')
1004
self.assertEqual([], list(transform.iter_changes()))
1005
transform.delete_contents(subdir)
1006
transform.create_directory(subdir)
1007
transform.set_executability(False, old)
1008
transform.unversion_file(old)
1009
transform.version_file('id-1', old)
1010
transform.adjust_path('old', root, old)
1011
self.assertEqual([], list(transform.iter_changes()))
1013
transform.finalize()
1015
def test_rename_count(self):
1016
transform, root = self.get_transform()
1017
transform.new_file('name1', root, 'contents')
1018
self.assertEqual(transform.rename_count, 0)
1020
self.assertEqual(transform.rename_count, 1)
1021
transform2, root = self.get_transform()
1022
transform2.adjust_path('name2', root,
1023
transform2.trans_id_tree_path('name1'))
1024
self.assertEqual(transform2.rename_count, 0)
1026
self.assertEqual(transform2.rename_count, 2)
1028
def test_change_parent(self):
1029
"""Ensure that after we change a parent, the results are still right.
1031
Renames and parent changes on pending transforms can happen as part
1032
of conflict resolution, and are explicitly permitted by the
1035
This test ensures they work correctly with the rename-avoidance
1038
transform, root = self.get_transform()
1039
parent1 = transform.new_directory('parent1', root)
1040
child1 = transform.new_file('child1', parent1, 'contents')
1041
parent2 = transform.new_directory('parent2', root)
1042
transform.adjust_path('child1', parent2, child1)
1044
self.failIfExists(self.wt.abspath('parent1/child1'))
1045
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1046
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1047
# no rename for child1 (counting only renames during apply)
1048
self.failUnlessEqual(2, transform.rename_count)
1050
def test_cancel_parent(self):
1051
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1053
This is like the test_change_parent, except that we cancel the parent
1054
before adjusting the path. The transform must detect that the
1055
directory is non-empty, and move children to safe locations.
1057
transform, root = self.get_transform()
1058
parent1 = transform.new_directory('parent1', root)
1059
child1 = transform.new_file('child1', parent1, 'contents')
1060
child2 = transform.new_file('child2', parent1, 'contents')
1062
transform.cancel_creation(parent1)
1064
self.fail('Failed to move child1 before deleting parent1')
1065
transform.cancel_creation(child2)
1066
transform.create_directory(parent1)
1068
transform.cancel_creation(parent1)
1069
# If the transform incorrectly believes that child2 is still in
1070
# parent1's limbo directory, it will try to rename it and fail
1071
# because was already moved by the first cancel_creation.
1073
self.fail('Transform still thinks child2 is a child of parent1')
1074
parent2 = transform.new_directory('parent2', root)
1075
transform.adjust_path('child1', parent2, child1)
1077
self.failIfExists(self.wt.abspath('parent1'))
1078
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1079
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1080
self.failUnlessEqual(2, transform.rename_count)
1082
def test_adjust_and_cancel(self):
1083
"""Make sure adjust_path keeps track of limbo children properly"""
1084
transform, root = self.get_transform()
1085
parent1 = transform.new_directory('parent1', root)
1086
child1 = transform.new_file('child1', parent1, 'contents')
1087
parent2 = transform.new_directory('parent2', root)
1088
transform.adjust_path('child1', parent2, child1)
1089
transform.cancel_creation(child1)
1091
transform.cancel_creation(parent1)
1092
# if the transform thinks child1 is still in parent1's limbo
1093
# directory, it will attempt to move it and fail.
1095
self.fail('Transform still thinks child1 is a child of parent1')
1096
transform.finalize()
1098
def test_noname_contents(self):
1099
"""TreeTransform should permit deferring naming files."""
1100
transform, root = self.get_transform()
1101
parent = transform.trans_id_file_id('parent-id')
1103
transform.create_directory(parent)
1105
self.fail("Can't handle contents with no name")
1106
transform.finalize()
1108
def test_noname_contents_nested(self):
1109
"""TreeTransform should permit deferring naming files."""
1110
transform, root = self.get_transform()
1111
parent = transform.trans_id_file_id('parent-id')
1113
transform.create_directory(parent)
1115
self.fail("Can't handle contents with no name")
1116
child = transform.new_directory('child', parent)
1117
transform.adjust_path('parent', root, parent)
1119
self.failUnlessExists(self.wt.abspath('parent/child'))
1120
self.assertEqual(1, transform.rename_count)
1122
def test_reuse_name(self):
1123
"""Avoid reusing the same limbo name for different files"""
1124
transform, root = self.get_transform()
1125
parent = transform.new_directory('parent', root)
1126
child1 = transform.new_directory('child', parent)
1128
child2 = transform.new_directory('child', parent)
1130
self.fail('Tranform tried to use the same limbo name twice')
1131
transform.adjust_path('child2', parent, child2)
1133
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1134
# child2 is put into top-level limbo because child1 has already
1135
# claimed the direct limbo path when child2 is created. There is no
1136
# advantage in renaming files once they're in top-level limbo, except
1138
self.assertEqual(2, transform.rename_count)
1140
def test_reuse_when_first_moved(self):
1141
"""Don't avoid direct paths when it is safe to use them"""
1142
transform, root = self.get_transform()
1143
parent = transform.new_directory('parent', root)
1144
child1 = transform.new_directory('child', parent)
1145
transform.adjust_path('child1', parent, child1)
1146
child2 = transform.new_directory('child', parent)
1148
# limbo/new-1 => parent
1149
self.assertEqual(1, transform.rename_count)
1151
def test_reuse_after_cancel(self):
1152
"""Don't avoid direct paths when it is safe to use them"""
1153
transform, root = self.get_transform()
1154
parent2 = transform.new_directory('parent2', root)
1155
child1 = transform.new_directory('child1', parent2)
1156
transform.cancel_creation(parent2)
1157
transform.create_directory(parent2)
1158
child2 = transform.new_directory('child1', parent2)
1159
transform.adjust_path('child2', parent2, child1)
1161
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1162
self.assertEqual(2, transform.rename_count)
1164
def test_finalize_order(self):
1165
"""Finalize must be done in child-to-parent order"""
1166
transform, root = self.get_transform()
1167
parent = transform.new_directory('parent', root)
1168
child = transform.new_directory('child', parent)
1170
transform.finalize()
1172
self.fail('Tried to remove parent before child1')
1174
def test_cancel_with_cancelled_child_should_succeed(self):
1175
transform, root = self.get_transform()
1176
parent = transform.new_directory('parent', root)
1177
child = transform.new_directory('child', parent)
1178
transform.cancel_creation(child)
1179
transform.cancel_creation(parent)
1180
transform.finalize()
1182
def test_rollback_on_directory_clash(self):
1184
wt = self.make_branch_and_tree('.')
1185
tt = TreeTransform(wt) # TreeTransform obtains write lock
1187
foo = tt.new_directory('foo', tt.root)
1188
tt.new_file('bar', foo, 'foobar')
1189
baz = tt.new_directory('baz', tt.root)
1190
tt.new_file('qux', baz, 'quux')
1191
# Ask for a rename 'foo' -> 'baz'
1192
tt.adjust_path('baz', tt.root, foo)
1193
# Lie to tt that we've already resolved all conflicts.
1194
tt.apply(no_conflicts=True)
1198
# The rename will fail because the target directory is not empty (but
1199
# raises FileExists anyway).
1200
err = self.assertRaises(errors.FileExists, tt_helper)
1201
self.assertContainsRe(str(err),
1202
"^File exists: .+/baz")
1204
def test_two_directories_clash(self):
1206
wt = self.make_branch_and_tree('.')
1207
tt = TreeTransform(wt) # TreeTransform obtains write lock
1209
foo_1 = tt.new_directory('foo', tt.root)
1210
tt.new_directory('bar', foo_1)
1211
# Adding the same directory with a different content
1212
foo_2 = tt.new_directory('foo', tt.root)
1213
tt.new_directory('baz', foo_2)
1214
# Lie to tt that we've already resolved all conflicts.
1215
tt.apply(no_conflicts=True)
1219
err = self.assertRaises(errors.FileExists, tt_helper)
1220
self.assertContainsRe(str(err),
1221
"^File exists: .+/foo")
1223
def test_two_directories_clash_finalize(self):
1225
wt = self.make_branch_and_tree('.')
1226
tt = TreeTransform(wt) # TreeTransform obtains write lock
1228
foo_1 = tt.new_directory('foo', tt.root)
1229
tt.new_directory('bar', foo_1)
1230
# Adding the same directory with a different content
1231
foo_2 = tt.new_directory('foo', tt.root)
1232
tt.new_directory('baz', foo_2)
1233
# Lie to tt that we've already resolved all conflicts.
1234
tt.apply(no_conflicts=True)
1238
err = self.assertRaises(errors.FileExists, tt_helper)
1239
self.assertContainsRe(str(err),
1240
"^File exists: .+/foo")
1242
def test_file_to_directory(self):
1243
wt = self.make_branch_and_tree('.')
1244
self.build_tree(['foo'])
1247
tt = TreeTransform(wt)
1248
self.addCleanup(tt.finalize)
1249
foo_trans_id = tt.trans_id_tree_path("foo")
1250
tt.delete_contents(foo_trans_id)
1251
tt.create_directory(foo_trans_id)
1252
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1253
tt.create_file(["aa\n"], bar_trans_id)
1254
tt.version_file("bar-1", bar_trans_id)
1256
self.failUnlessExists("foo/bar")
1259
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1264
changes = wt.changes_from(wt.basis_tree())
1265
self.assertFalse(changes.has_changed(), changes)
1267
def test_file_to_symlink(self):
1268
self.requireFeature(SymlinkFeature)
1269
wt = self.make_branch_and_tree('.')
1270
self.build_tree(['foo'])
1273
tt = TreeTransform(wt)
1274
self.addCleanup(tt.finalize)
1275
foo_trans_id = tt.trans_id_tree_path("foo")
1276
tt.delete_contents(foo_trans_id)
1277
tt.create_symlink("bar", foo_trans_id)
1279
self.failUnlessExists("foo")
1281
self.addCleanup(wt.unlock)
1282
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1285
def test_dir_to_file(self):
1286
wt = self.make_branch_and_tree('.')
1287
self.build_tree(['foo/', 'foo/bar'])
1288
wt.add(['foo', 'foo/bar'])
1290
tt = TreeTransform(wt)
1291
self.addCleanup(tt.finalize)
1292
foo_trans_id = tt.trans_id_tree_path("foo")
1293
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1294
tt.delete_contents(foo_trans_id)
1295
tt.delete_versioned(bar_trans_id)
1296
tt.create_file(["aa\n"], foo_trans_id)
1298
self.failUnlessExists("foo")
1300
self.addCleanup(wt.unlock)
1301
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1304
def test_dir_to_hardlink(self):
1305
self.requireFeature(HardlinkFeature)
1306
wt = self.make_branch_and_tree('.')
1307
self.build_tree(['foo/', 'foo/bar'])
1308
wt.add(['foo', 'foo/bar'])
1310
tt = TreeTransform(wt)
1311
self.addCleanup(tt.finalize)
1312
foo_trans_id = tt.trans_id_tree_path("foo")
1313
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1314
tt.delete_contents(foo_trans_id)
1315
tt.delete_versioned(bar_trans_id)
1316
self.build_tree(['baz'])
1317
tt.create_hardlink("baz", foo_trans_id)
1319
self.failUnlessExists("foo")
1320
self.failUnlessExists("baz")
1322
self.addCleanup(wt.unlock)
1323
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1326
def test_no_final_path(self):
1327
transform, root = self.get_transform()
1328
trans_id = transform.trans_id_file_id('foo')
1329
transform.create_file('bar', trans_id)
1330
transform.cancel_creation(trans_id)
1333
def test_create_from_tree(self):
1334
tree1 = self.make_branch_and_tree('tree1')
1335
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1336
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1337
tree2 = self.make_branch_and_tree('tree2')
1338
tt = TreeTransform(tree2)
1339
foo_trans_id = tt.create_path('foo', tt.root)
1340
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1341
bar_trans_id = tt.create_path('bar', tt.root)
1342
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1344
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1345
self.assertFileEqual('baz', 'tree2/bar')
1347
def test_create_from_tree_bytes(self):
1348
"""Provided lines are used instead of tree content."""
1349
tree1 = self.make_branch_and_tree('tree1')
1350
self.build_tree_contents([('tree1/foo', 'bar'),])
1351
tree1.add('foo', 'foo-id')
1352
tree2 = self.make_branch_and_tree('tree2')
1353
tt = TreeTransform(tree2)
1354
foo_trans_id = tt.create_path('foo', tt.root)
1355
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1357
self.assertFileEqual('qux', 'tree2/foo')
1359
def test_create_from_tree_symlink(self):
1360
self.requireFeature(SymlinkFeature)
1361
tree1 = self.make_branch_and_tree('tree1')
1362
os.symlink('bar', 'tree1/foo')
1363
tree1.add('foo', 'foo-id')
1364
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1365
foo_trans_id = tt.create_path('foo', tt.root)
1366
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1368
self.assertEqual('bar', os.readlink('tree2/foo'))
1371
class TransformGroup(object):
1373
def __init__(self, dirname, root_id):
1376
self.wt = BzrDir.create_standalone_workingtree(dirname)
1377
self.wt.set_root_id(root_id)
1378
self.b = self.wt.branch
1379
self.tt = TreeTransform(self.wt)
1380
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1383
def conflict_text(tree, merge):
1384
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1385
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1388
class TestTransformMerge(TestCaseInTempDir):
1390
def test_text_merge(self):
1391
root_id = generate_ids.gen_root_id()
1392
base = TransformGroup("base", root_id)
1393
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1394
base.tt.new_file('b', base.root, 'b1', 'b')
1395
base.tt.new_file('c', base.root, 'c', 'c')
1396
base.tt.new_file('d', base.root, 'd', 'd')
1397
base.tt.new_file('e', base.root, 'e', 'e')
1398
base.tt.new_file('f', base.root, 'f', 'f')
1399
base.tt.new_directory('g', base.root, 'g')
1400
base.tt.new_directory('h', base.root, 'h')
1402
other = TransformGroup("other", root_id)
1403
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1404
other.tt.new_file('b', other.root, 'b2', 'b')
1405
other.tt.new_file('c', other.root, 'c2', 'c')
1406
other.tt.new_file('d', other.root, 'd', 'd')
1407
other.tt.new_file('e', other.root, 'e2', 'e')
1408
other.tt.new_file('f', other.root, 'f', 'f')
1409
other.tt.new_file('g', other.root, 'g', 'g')
1410
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1411
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1413
this = TransformGroup("this", root_id)
1414
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1415
this.tt.new_file('b', this.root, 'b', 'b')
1416
this.tt.new_file('c', this.root, 'c', 'c')
1417
this.tt.new_file('d', this.root, 'd2', 'd')
1418
this.tt.new_file('e', this.root, 'e2', 'e')
1419
this.tt.new_file('f', this.root, 'f', 'f')
1420
this.tt.new_file('g', this.root, 'g', 'g')
1421
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1422
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1424
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1427
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1428
# three-way text conflict
1429
self.assertEqual(this.wt.get_file('b').read(),
1430
conflict_text('b', 'b2'))
1432
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1434
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1435
# Ambigious clean merge
1436
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1438
self.assertEqual(this.wt.get_file('f').read(), 'f')
1439
# Correct correct results when THIS == OTHER
1440
self.assertEqual(this.wt.get_file('g').read(), 'g')
1441
# Text conflict when THIS & OTHER are text and BASE is dir
1442
self.assertEqual(this.wt.get_file('h').read(),
1443
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1444
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1446
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1448
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1449
self.assertEqual(this.wt.get_file('i').read(),
1450
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1451
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1453
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1455
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1456
modified = ['a', 'b', 'c', 'h', 'i']
1457
merge_modified = this.wt.merge_modified()
1458
self.assertSubset(merge_modified, modified)
1459
self.assertEqual(len(merge_modified), len(modified))
1460
file(this.wt.id2abspath('a'), 'wb').write('booga')
1462
merge_modified = this.wt.merge_modified()
1463
self.assertSubset(merge_modified, modified)
1464
self.assertEqual(len(merge_modified), len(modified))
1468
def test_file_merge(self):
1469
self.requireFeature(SymlinkFeature)
1470
root_id = generate_ids.gen_root_id()
1471
base = TransformGroup("BASE", root_id)
1472
this = TransformGroup("THIS", root_id)
1473
other = TransformGroup("OTHER", root_id)
1474
for tg in this, base, other:
1475
tg.tt.new_directory('a', tg.root, 'a')
1476
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1477
tg.tt.new_file('c', tg.root, 'c', 'c')
1478
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1479
targets = ((base, 'base-e', 'base-f', None, None),
1480
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1481
(other, 'other-e', None, 'other-g', 'other-h'))
1482
for tg, e_target, f_target, g_target, h_target in targets:
1483
for link, target in (('e', e_target), ('f', f_target),
1484
('g', g_target), ('h', h_target)):
1485
if target is not None:
1486
tg.tt.new_symlink(link, tg.root, target, link)
1488
for tg in this, base, other:
1490
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1491
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1492
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1493
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1494
for suffix in ('THIS', 'BASE', 'OTHER'):
1495
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1496
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1497
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1498
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1499
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1500
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1501
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1502
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1503
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1504
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1505
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1506
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1507
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1508
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1510
def test_filename_merge(self):
1511
root_id = generate_ids.gen_root_id()
1512
base = TransformGroup("BASE", root_id)
1513
this = TransformGroup("THIS", root_id)
1514
other = TransformGroup("OTHER", root_id)
1515
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1516
for t in [base, this, other]]
1517
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1518
for t in [base, this, other]]
1519
base.tt.new_directory('c', base_a, 'c')
1520
this.tt.new_directory('c1', this_a, 'c')
1521
other.tt.new_directory('c', other_b, 'c')
1523
base.tt.new_directory('d', base_a, 'd')
1524
this.tt.new_directory('d1', this_b, 'd')
1525
other.tt.new_directory('d', other_a, 'd')
1527
base.tt.new_directory('e', base_a, 'e')
1528
this.tt.new_directory('e', this_a, 'e')
1529
other.tt.new_directory('e1', other_b, 'e')
1531
base.tt.new_directory('f', base_a, 'f')
1532
this.tt.new_directory('f1', this_b, 'f')
1533
other.tt.new_directory('f1', other_b, 'f')
1535
for tg in [this, base, other]:
1537
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1538
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1539
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1540
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1541
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1543
def test_filename_merge_conflicts(self):
1544
root_id = generate_ids.gen_root_id()
1545
base = TransformGroup("BASE", root_id)
1546
this = TransformGroup("THIS", root_id)
1547
other = TransformGroup("OTHER", root_id)
1548
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1549
for t in [base, this, other]]
1550
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1551
for t in [base, this, other]]
1553
base.tt.new_file('g', base_a, 'g', 'g')
1554
other.tt.new_file('g1', other_b, 'g1', 'g')
1556
base.tt.new_file('h', base_a, 'h', 'h')
1557
this.tt.new_file('h1', this_b, 'h1', 'h')
1559
base.tt.new_file('i', base.root, 'i', 'i')
1560
other.tt.new_directory('i1', this_b, 'i')
1562
for tg in [this, base, other]:
1564
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1566
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1567
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1568
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1569
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1570
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1571
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1572
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1575
class TestBuildTree(tests.TestCaseWithTransport):
1577
def test_build_tree_with_symlinks(self):
1578
self.requireFeature(SymlinkFeature)
1580
a = BzrDir.create_standalone_workingtree('a')
1582
file('a/foo/bar', 'wb').write('contents')
1583
os.symlink('a/foo/bar', 'a/foo/baz')
1584
a.add(['foo', 'foo/bar', 'foo/baz'])
1585
a.commit('initial commit')
1586
b = BzrDir.create_standalone_workingtree('b')
1587
basis = a.basis_tree()
1589
self.addCleanup(basis.unlock)
1590
build_tree(basis, b)
1591
self.assertIs(os.path.isdir('b/foo'), True)
1592
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1593
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1595
def test_build_with_references(self):
1596
tree = self.make_branch_and_tree('source',
1597
format='dirstate-with-subtree')
1598
subtree = self.make_branch_and_tree('source/subtree',
1599
format='dirstate-with-subtree')
1600
tree.add_reference(subtree)
1601
tree.commit('a revision')
1602
tree.branch.create_checkout('target')
1603
self.failUnlessExists('target')
1604
self.failUnlessExists('target/subtree')
1606
def test_file_conflict_handling(self):
1607
"""Ensure that when building trees, conflict handling is done"""
1608
source = self.make_branch_and_tree('source')
1609
target = self.make_branch_and_tree('target')
1610
self.build_tree(['source/file', 'target/file'])
1611
source.add('file', 'new-file')
1612
source.commit('added file')
1613
build_tree(source.basis_tree(), target)
1614
self.assertEqual([DuplicateEntry('Moved existing file to',
1615
'file.moved', 'file', None, 'new-file')],
1617
target2 = self.make_branch_and_tree('target2')
1618
target_file = file('target2/file', 'wb')
1620
source_file = file('source/file', 'rb')
1622
target_file.write(source_file.read())
1627
build_tree(source.basis_tree(), target2)
1628
self.assertEqual([], target2.conflicts())
1630
def test_symlink_conflict_handling(self):
1631
"""Ensure that when building trees, conflict handling is done"""
1632
self.requireFeature(SymlinkFeature)
1633
source = self.make_branch_and_tree('source')
1634
os.symlink('foo', 'source/symlink')
1635
source.add('symlink', 'new-symlink')
1636
source.commit('added file')
1637
target = self.make_branch_and_tree('target')
1638
os.symlink('bar', 'target/symlink')
1639
build_tree(source.basis_tree(), target)
1640
self.assertEqual([DuplicateEntry('Moved existing file to',
1641
'symlink.moved', 'symlink', None, 'new-symlink')],
1643
target = self.make_branch_and_tree('target2')
1644
os.symlink('foo', 'target2/symlink')
1645
build_tree(source.basis_tree(), target)
1646
self.assertEqual([], target.conflicts())
1648
def test_directory_conflict_handling(self):
1649
"""Ensure that when building trees, conflict handling is done"""
1650
source = self.make_branch_and_tree('source')
1651
target = self.make_branch_and_tree('target')
1652
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1653
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1654
source.commit('added file')
1655
build_tree(source.basis_tree(), target)
1656
self.assertEqual([], target.conflicts())
1657
self.failUnlessExists('target/dir1/file')
1659
# Ensure contents are merged
1660
target = self.make_branch_and_tree('target2')
1661
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1662
build_tree(source.basis_tree(), target)
1663
self.assertEqual([], target.conflicts())
1664
self.failUnlessExists('target2/dir1/file2')
1665
self.failUnlessExists('target2/dir1/file')
1667
# Ensure new contents are suppressed for existing branches
1668
target = self.make_branch_and_tree('target3')
1669
self.make_branch('target3/dir1')
1670
self.build_tree(['target3/dir1/file2'])
1671
build_tree(source.basis_tree(), target)
1672
self.failIfExists('target3/dir1/file')
1673
self.failUnlessExists('target3/dir1/file2')
1674
self.failUnlessExists('target3/dir1.diverted/file')
1675
self.assertEqual([DuplicateEntry('Diverted to',
1676
'dir1.diverted', 'dir1', 'new-dir1', None)],
1679
target = self.make_branch_and_tree('target4')
1680
self.build_tree(['target4/dir1/'])
1681
self.make_branch('target4/dir1/file')
1682
build_tree(source.basis_tree(), target)
1683
self.failUnlessExists('target4/dir1/file')
1684
self.assertEqual('directory', file_kind('target4/dir1/file'))
1685
self.failUnlessExists('target4/dir1/file.diverted')
1686
self.assertEqual([DuplicateEntry('Diverted to',
1687
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1690
def test_mixed_conflict_handling(self):
1691
"""Ensure that when building trees, conflict handling is done"""
1692
source = self.make_branch_and_tree('source')
1693
target = self.make_branch_and_tree('target')
1694
self.build_tree(['source/name', 'target/name/'])
1695
source.add('name', 'new-name')
1696
source.commit('added file')
1697
build_tree(source.basis_tree(), target)
1698
self.assertEqual([DuplicateEntry('Moved existing file to',
1699
'name.moved', 'name', None, 'new-name')], target.conflicts())
1701
def test_raises_in_populated(self):
1702
source = self.make_branch_and_tree('source')
1703
self.build_tree(['source/name'])
1705
source.commit('added name')
1706
target = self.make_branch_and_tree('target')
1707
self.build_tree(['target/name'])
1709
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1710
build_tree, source.basis_tree(), target)
1712
def test_build_tree_rename_count(self):
1713
source = self.make_branch_and_tree('source')
1714
self.build_tree(['source/file1', 'source/dir1/'])
1715
source.add(['file1', 'dir1'])
1716
source.commit('add1')
1717
target1 = self.make_branch_and_tree('target1')
1718
transform_result = build_tree(source.basis_tree(), target1)
1719
self.assertEqual(2, transform_result.rename_count)
1721
self.build_tree(['source/dir1/file2'])
1722
source.add(['dir1/file2'])
1723
source.commit('add3')
1724
target2 = self.make_branch_and_tree('target2')
1725
transform_result = build_tree(source.basis_tree(), target2)
1726
# children of non-root directories should not be renamed
1727
self.assertEqual(2, transform_result.rename_count)
1729
def create_ab_tree(self):
1730
"""Create a committed test tree with two files"""
1731
source = self.make_branch_and_tree('source')
1732
self.build_tree_contents([('source/file1', 'A')])
1733
self.build_tree_contents([('source/file2', 'B')])
1734
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1735
source.commit('commit files')
1737
self.addCleanup(source.unlock)
1740
def test_build_tree_accelerator_tree(self):
1741
source = self.create_ab_tree()
1742
self.build_tree_contents([('source/file2', 'C')])
1744
real_source_get_file = source.get_file
1745
def get_file(file_id, path=None):
1746
calls.append(file_id)
1747
return real_source_get_file(file_id, path)
1748
source.get_file = get_file
1749
target = self.make_branch_and_tree('target')
1750
revision_tree = source.basis_tree()
1751
revision_tree.lock_read()
1752
self.addCleanup(revision_tree.unlock)
1753
build_tree(revision_tree, target, source)
1754
self.assertEqual(['file1-id'], calls)
1756
self.addCleanup(target.unlock)
1757
self.assertEqual([], list(target.iter_changes(revision_tree)))
1759
def test_build_tree_accelerator_tree_missing_file(self):
1760
source = self.create_ab_tree()
1761
os.unlink('source/file1')
1762
source.remove(['file2'])
1763
target = self.make_branch_and_tree('target')
1764
revision_tree = source.basis_tree()
1765
revision_tree.lock_read()
1766
self.addCleanup(revision_tree.unlock)
1767
build_tree(revision_tree, target, source)
1769
self.addCleanup(target.unlock)
1770
self.assertEqual([], list(target.iter_changes(revision_tree)))
1772
def test_build_tree_accelerator_wrong_kind(self):
1773
self.requireFeature(SymlinkFeature)
1774
source = self.make_branch_and_tree('source')
1775
self.build_tree_contents([('source/file1', '')])
1776
self.build_tree_contents([('source/file2', '')])
1777
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1778
source.commit('commit files')
1779
os.unlink('source/file2')
1780
self.build_tree_contents([('source/file2/', 'C')])
1781
os.unlink('source/file1')
1782
os.symlink('file2', 'source/file1')
1784
real_source_get_file = source.get_file
1785
def get_file(file_id, path=None):
1786
calls.append(file_id)
1787
return real_source_get_file(file_id, path)
1788
source.get_file = get_file
1789
target = self.make_branch_and_tree('target')
1790
revision_tree = source.basis_tree()
1791
revision_tree.lock_read()
1792
self.addCleanup(revision_tree.unlock)
1793
build_tree(revision_tree, target, source)
1794
self.assertEqual([], calls)
1796
self.addCleanup(target.unlock)
1797
self.assertEqual([], list(target.iter_changes(revision_tree)))
1799
def test_build_tree_hardlink(self):
1800
self.requireFeature(HardlinkFeature)
1801
source = self.create_ab_tree()
1802
target = self.make_branch_and_tree('target')
1803
revision_tree = source.basis_tree()
1804
revision_tree.lock_read()
1805
self.addCleanup(revision_tree.unlock)
1806
build_tree(revision_tree, target, source, hardlink=True)
1808
self.addCleanup(target.unlock)
1809
self.assertEqual([], list(target.iter_changes(revision_tree)))
1810
source_stat = os.stat('source/file1')
1811
target_stat = os.stat('target/file1')
1812
self.assertEqual(source_stat, target_stat)
1814
# Explicitly disallowing hardlinks should prevent them.
1815
target2 = self.make_branch_and_tree('target2')
1816
build_tree(revision_tree, target2, source, hardlink=False)
1818
self.addCleanup(target2.unlock)
1819
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1820
source_stat = os.stat('source/file1')
1821
target2_stat = os.stat('target2/file1')
1822
self.assertNotEqual(source_stat, target2_stat)
1824
def test_build_tree_accelerator_tree_moved(self):
1825
source = self.make_branch_and_tree('source')
1826
self.build_tree_contents([('source/file1', 'A')])
1827
source.add(['file1'], ['file1-id'])
1828
source.commit('commit files')
1829
source.rename_one('file1', 'file2')
1831
self.addCleanup(source.unlock)
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)
1838
self.addCleanup(target.unlock)
1839
self.assertEqual([], list(target.iter_changes(revision_tree)))
1841
def test_build_tree_hardlinks_preserve_execute(self):
1842
self.requireFeature(HardlinkFeature)
1843
source = self.create_ab_tree()
1844
tt = TreeTransform(source)
1845
trans_id = tt.trans_id_tree_file_id('file1-id')
1846
tt.set_executability(True, trans_id)
1848
self.assertTrue(source.is_executable('file1-id'))
1849
target = self.make_branch_and_tree('target')
1850
revision_tree = source.basis_tree()
1851
revision_tree.lock_read()
1852
self.addCleanup(revision_tree.unlock)
1853
build_tree(revision_tree, target, source, hardlink=True)
1855
self.addCleanup(target.unlock)
1856
self.assertEqual([], list(target.iter_changes(revision_tree)))
1857
self.assertTrue(source.is_executable('file1-id'))
1859
def test_case_insensitive_build_tree_inventory(self):
1860
if (tests.CaseInsensitiveFilesystemFeature.available()
1861
or tests.CaseInsCasePresFilenameFeature.available()):
1862
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1863
source = self.make_branch_and_tree('source')
1864
self.build_tree(['source/file', 'source/FILE'])
1865
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1866
source.commit('added files')
1867
# Don't try this at home, kids!
1868
# Force the tree to report that it is case insensitive
1869
target = self.make_branch_and_tree('target')
1870
target.case_sensitive = False
1871
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1872
self.assertEqual('file.moved', target.id2path('lower-id'))
1873
self.assertEqual('FILE', target.id2path('upper-id'))
1876
class TestCommitTransform(tests.TestCaseWithTransport):
1878
def get_branch(self):
1879
tree = self.make_branch_and_tree('tree')
1881
self.addCleanup(tree.unlock)
1882
tree.commit('empty commit')
1885
def get_branch_and_transform(self):
1886
branch = self.get_branch()
1887
tt = TransformPreview(branch.basis_tree())
1888
self.addCleanup(tt.finalize)
1891
def test_commit_wrong_basis(self):
1892
branch = self.get_branch()
1893
basis = branch.repository.revision_tree(
1894
_mod_revision.NULL_REVISION)
1895
tt = TransformPreview(basis)
1896
self.addCleanup(tt.finalize)
1897
e = self.assertRaises(ValueError, tt.commit, branch, '')
1898
self.assertEqual('TreeTransform not based on branch basis: null:',
1901
def test_empy_commit(self):
1902
branch, tt = self.get_branch_and_transform()
1903
rev = tt.commit(branch, 'my message')
1904
self.assertEqual(2, branch.revno())
1905
repo = branch.repository
1906
self.assertEqual('my message', repo.get_revision(rev).message)
1908
def test_merge_parents(self):
1909
branch, tt = self.get_branch_and_transform()
1910
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
1911
self.assertEqual(['rev1b', 'rev1c'],
1912
branch.basis_tree().get_parent_ids()[1:])
1914
def test_first_commit(self):
1915
branch = self.make_branch('branch')
1917
self.addCleanup(branch.unlock)
1918
tt = TransformPreview(branch.basis_tree())
1919
self.addCleanup(tt.finalize)
1920
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
1921
rev = tt.commit(branch, 'my message')
1922
self.assertEqual([], branch.basis_tree().get_parent_ids())
1923
self.assertNotEqual(_mod_revision.NULL_REVISION,
1924
branch.last_revision())
1926
def test_first_commit_with_merge_parents(self):
1927
branch = self.make_branch('branch')
1929
self.addCleanup(branch.unlock)
1930
tt = TransformPreview(branch.basis_tree())
1931
self.addCleanup(tt.finalize)
1932
e = self.assertRaises(ValueError, tt.commit, branch,
1933
'my message', ['rev1b-id'])
1934
self.assertEqual('Cannot supply merge parents for first commit.',
1936
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
1938
def test_add_files(self):
1939
branch, tt = self.get_branch_and_transform()
1940
tt.new_file('file', tt.root, 'contents', 'file-id')
1941
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
1942
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
1943
rev = tt.commit(branch, 'message')
1944
tree = branch.basis_tree()
1945
self.assertEqual('file', tree.id2path('file-id'))
1946
self.assertEqual('contents', tree.get_file_text('file-id'))
1947
self.assertEqual('dir', tree.id2path('dir-id'))
1948
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
1949
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
1951
def test_add_unversioned(self):
1952
branch, tt = self.get_branch_and_transform()
1953
tt.new_file('file', tt.root, 'contents')
1954
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
1955
'message', strict=True)
1957
def test_modify_strict(self):
1958
branch, tt = self.get_branch_and_transform()
1959
tt.new_file('file', tt.root, 'contents', 'file-id')
1960
tt.commit(branch, 'message', strict=True)
1961
tt = TransformPreview(branch.basis_tree())
1962
self.addCleanup(tt.finalize)
1963
trans_id = tt.trans_id_file_id('file-id')
1964
tt.delete_contents(trans_id)
1965
tt.create_file('contents', trans_id)
1966
tt.commit(branch, 'message', strict=True)
1968
def test_commit_malformed(self):
1969
"""Committing a malformed transform should raise an exception.
1971
In this case, we are adding a file without adding its parent.
1973
branch, tt = self.get_branch_and_transform()
1974
parent_id = tt.trans_id_file_id('parent-id')
1975
tt.new_file('file', parent_id, 'contents', 'file-id')
1976
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
1980
class MockTransform(object):
1982
def has_named_child(self, by_parent, parent_id, name):
1983
for child_id in by_parent[parent_id]:
1987
elif name == "name.~%s~" % child_id:
1992
class MockEntry(object):
1994
object.__init__(self)
1998
class TestGetBackupName(TestCase):
1999
def test_get_backup_name(self):
2000
tt = MockTransform()
2001
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2002
self.assertEqual(name, 'name.~1~')
2003
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2004
self.assertEqual(name, 'name.~2~')
2005
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2006
self.assertEqual(name, 'name.~1~')
2007
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2008
self.assertEqual(name, 'name.~1~')
2009
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2010
self.assertEqual(name, 'name.~4~')
2013
class TestFileMover(tests.TestCaseWithTransport):
2015
def test_file_mover(self):
2016
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2017
mover = _FileMover()
2018
mover.rename('a', 'q')
2019
self.failUnlessExists('q')
2020
self.failIfExists('a')
2021
self.failUnlessExists('q/b')
2022
self.failUnlessExists('c')
2023
self.failUnlessExists('c/d')
2025
def test_pre_delete_rollback(self):
2026
self.build_tree(['a/'])
2027
mover = _FileMover()
2028
mover.pre_delete('a', 'q')
2029
self.failUnlessExists('q')
2030
self.failIfExists('a')
2032
self.failIfExists('q')
2033
self.failUnlessExists('a')
2035
def test_apply_deletions(self):
2036
self.build_tree(['a/', 'b/'])
2037
mover = _FileMover()
2038
mover.pre_delete('a', 'q')
2039
mover.pre_delete('b', 'r')
2040
self.failUnlessExists('q')
2041
self.failUnlessExists('r')
2042
self.failIfExists('a')
2043
self.failIfExists('b')
2044
mover.apply_deletions()
2045
self.failIfExists('q')
2046
self.failIfExists('r')
2047
self.failIfExists('a')
2048
self.failIfExists('b')
2050
def test_file_mover_rollback(self):
2051
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2052
mover = _FileMover()
2053
mover.rename('c/d', 'c/f')
2054
mover.rename('c/e', 'c/d')
2056
mover.rename('a', 'c')
2057
except errors.FileExists, e:
2059
self.failUnlessExists('a')
2060
self.failUnlessExists('c/d')
2063
class Bogus(Exception):
2067
class TestTransformRollback(tests.TestCaseWithTransport):
2069
class ExceptionFileMover(_FileMover):
2071
def __init__(self, bad_source=None, bad_target=None):
2072
_FileMover.__init__(self)
2073
self.bad_source = bad_source
2074
self.bad_target = bad_target
2076
def rename(self, source, target):
2077
if (self.bad_source is not None and
2078
source.endswith(self.bad_source)):
2080
elif (self.bad_target is not None and
2081
target.endswith(self.bad_target)):
2084
_FileMover.rename(self, source, target)
2086
def test_rollback_rename(self):
2087
tree = self.make_branch_and_tree('.')
2088
self.build_tree(['a/', 'a/b'])
2089
tt = TreeTransform(tree)
2090
self.addCleanup(tt.finalize)
2091
a_id = tt.trans_id_tree_path('a')
2092
tt.adjust_path('c', tt.root, a_id)
2093
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2094
self.assertRaises(Bogus, tt.apply,
2095
_mover=self.ExceptionFileMover(bad_source='a'))
2096
self.failUnlessExists('a')
2097
self.failUnlessExists('a/b')
2099
self.failUnlessExists('c')
2100
self.failUnlessExists('c/d')
2102
def test_rollback_rename_into_place(self):
2103
tree = self.make_branch_and_tree('.')
2104
self.build_tree(['a/', 'a/b'])
2105
tt = TreeTransform(tree)
2106
self.addCleanup(tt.finalize)
2107
a_id = tt.trans_id_tree_path('a')
2108
tt.adjust_path('c', tt.root, a_id)
2109
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2110
self.assertRaises(Bogus, tt.apply,
2111
_mover=self.ExceptionFileMover(bad_target='c/d'))
2112
self.failUnlessExists('a')
2113
self.failUnlessExists('a/b')
2115
self.failUnlessExists('c')
2116
self.failUnlessExists('c/d')
2118
def test_rollback_deletion(self):
2119
tree = self.make_branch_and_tree('.')
2120
self.build_tree(['a/', 'a/b'])
2121
tt = TreeTransform(tree)
2122
self.addCleanup(tt.finalize)
2123
a_id = tt.trans_id_tree_path('a')
2124
tt.delete_contents(a_id)
2125
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2126
self.assertRaises(Bogus, tt.apply,
2127
_mover=self.ExceptionFileMover(bad_target='d'))
2128
self.failUnlessExists('a')
2129
self.failUnlessExists('a/b')
2131
def test_resolve_no_parent(self):
2132
wt = self.make_branch_and_tree('.')
2133
tt = TreeTransform(wt)
2134
self.addCleanup(tt.finalize)
2135
parent = tt.trans_id_file_id('parent-id')
2136
tt.new_file('file', parent, 'Contents')
2137
resolve_conflicts(tt)
2140
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2141
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2143
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2144
('', ''), ('directory', 'directory'), (False, None))
2147
class TestTransformPreview(tests.TestCaseWithTransport):
2149
def create_tree(self):
2150
tree = self.make_branch_and_tree('.')
2151
self.build_tree_contents([('a', 'content 1')])
2152
tree.set_root_id('TREE_ROOT')
2153
tree.add('a', 'a-id')
2154
tree.commit('rev1', rev_id='rev1')
2155
return tree.branch.repository.revision_tree('rev1')
2157
def get_empty_preview(self):
2158
repository = self.make_repository('repo')
2159
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2160
preview = TransformPreview(tree)
2161
self.addCleanup(preview.finalize)
2164
def test_transform_preview(self):
2165
revision_tree = self.create_tree()
2166
preview = TransformPreview(revision_tree)
2167
self.addCleanup(preview.finalize)
2169
def test_transform_preview_tree(self):
2170
revision_tree = self.create_tree()
2171
preview = TransformPreview(revision_tree)
2172
self.addCleanup(preview.finalize)
2173
preview.get_preview_tree()
2175
def test_transform_new_file(self):
2176
revision_tree = self.create_tree()
2177
preview = TransformPreview(revision_tree)
2178
self.addCleanup(preview.finalize)
2179
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2180
preview_tree = preview.get_preview_tree()
2181
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2183
preview_tree.get_file('file2-id').read(), 'content B\n')
2185
def test_diff_preview_tree(self):
2186
revision_tree = self.create_tree()
2187
preview = TransformPreview(revision_tree)
2188
self.addCleanup(preview.finalize)
2189
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2190
preview_tree = preview.get_preview_tree()
2192
show_diff_trees(revision_tree, preview_tree, out)
2193
lines = out.getvalue().splitlines()
2194
self.assertEqual(lines[0], "=== added file 'file2'")
2195
# 3 lines of diff administrivia
2196
self.assertEqual(lines[4], "+content B")
2198
def test_transform_conflicts(self):
2199
revision_tree = self.create_tree()
2200
preview = TransformPreview(revision_tree)
2201
self.addCleanup(preview.finalize)
2202
preview.new_file('a', preview.root, 'content 2')
2203
resolve_conflicts(preview)
2204
trans_id = preview.trans_id_file_id('a-id')
2205
self.assertEqual('a.moved', preview.final_name(trans_id))
2207
def get_tree_and_preview_tree(self):
2208
revision_tree = self.create_tree()
2209
preview = TransformPreview(revision_tree)
2210
self.addCleanup(preview.finalize)
2211
a_trans_id = preview.trans_id_file_id('a-id')
2212
preview.delete_contents(a_trans_id)
2213
preview.create_file('b content', a_trans_id)
2214
preview_tree = preview.get_preview_tree()
2215
return revision_tree, preview_tree
2217
def test_iter_changes(self):
2218
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2219
root = revision_tree.inventory.root.file_id
2220
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2221
(root, root), ('a', 'a'), ('file', 'file'),
2223
list(preview_tree.iter_changes(revision_tree)))
2225
def test_include_unchanged_succeeds(self):
2226
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2227
changes = preview_tree.iter_changes(revision_tree,
2228
include_unchanged=True)
2229
root = revision_tree.inventory.root.file_id
2231
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2233
def test_specific_files(self):
2234
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2235
changes = preview_tree.iter_changes(revision_tree,
2236
specific_files=[''])
2237
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2239
def test_want_unversioned(self):
2240
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2241
changes = preview_tree.iter_changes(revision_tree,
2242
want_unversioned=True)
2243
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2245
def test_ignore_extra_trees_no_specific_files(self):
2246
# extra_trees is harmless without specific_files, so we'll silently
2247
# accept it, even though we won't use it.
2248
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2249
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2251
def test_ignore_require_versioned_no_specific_files(self):
2252
# require_versioned is meaningless without specific_files.
2253
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2254
preview_tree.iter_changes(revision_tree, require_versioned=False)
2256
def test_ignore_pb(self):
2257
# pb could be supported, but TT.iter_changes doesn't support it.
2258
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2259
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2261
def test_kind(self):
2262
revision_tree = self.create_tree()
2263
preview = TransformPreview(revision_tree)
2264
self.addCleanup(preview.finalize)
2265
preview.new_file('file', preview.root, 'contents', 'file-id')
2266
preview.new_directory('directory', preview.root, 'dir-id')
2267
preview_tree = preview.get_preview_tree()
2268
self.assertEqual('file', preview_tree.kind('file-id'))
2269
self.assertEqual('directory', preview_tree.kind('dir-id'))
2271
def test_get_file_mtime(self):
2272
preview = self.get_empty_preview()
2273
file_trans_id = preview.new_file('file', preview.root, 'contents',
2275
limbo_path = preview._limbo_name(file_trans_id)
2276
preview_tree = preview.get_preview_tree()
2277
self.assertEqual(os.stat(limbo_path).st_mtime,
2278
preview_tree.get_file_mtime('file-id'))
2280
def test_get_file_mtime_renamed(self):
2281
work_tree = self.make_branch_and_tree('tree')
2282
self.build_tree(['tree/file'])
2283
work_tree.add('file', 'file-id')
2284
preview = TransformPreview(work_tree)
2285
self.addCleanup(preview.finalize)
2286
file_trans_id = preview.trans_id_tree_file_id('file-id')
2287
preview.adjust_path('renamed', preview.root, file_trans_id)
2288
preview_tree = preview.get_preview_tree()
2289
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2290
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2292
def test_get_file(self):
2293
preview = self.get_empty_preview()
2294
preview.new_file('file', preview.root, 'contents', 'file-id')
2295
preview_tree = preview.get_preview_tree()
2296
tree_file = preview_tree.get_file('file-id')
2298
self.assertEqual('contents', tree_file.read())
2302
def test_get_symlink_target(self):
2303
self.requireFeature(SymlinkFeature)
2304
preview = self.get_empty_preview()
2305
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2306
preview_tree = preview.get_preview_tree()
2307
self.assertEqual('target',
2308
preview_tree.get_symlink_target('symlink-id'))
2310
def test_all_file_ids(self):
2311
tree = self.make_branch_and_tree('tree')
2312
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2313
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2314
preview = TransformPreview(tree)
2315
self.addCleanup(preview.finalize)
2316
preview.unversion_file(preview.trans_id_file_id('b-id'))
2317
c_trans_id = preview.trans_id_file_id('c-id')
2318
preview.unversion_file(c_trans_id)
2319
preview.version_file('c-id', c_trans_id)
2320
preview_tree = preview.get_preview_tree()
2321
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2322
preview_tree.all_file_ids())
2324
def test_path2id_deleted_unchanged(self):
2325
tree = self.make_branch_and_tree('tree')
2326
self.build_tree(['tree/unchanged', 'tree/deleted'])
2327
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2328
preview = TransformPreview(tree)
2329
self.addCleanup(preview.finalize)
2330
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2331
preview_tree = preview.get_preview_tree()
2332
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2333
self.assertIs(None, preview_tree.path2id('deleted'))
2335
def test_path2id_created(self):
2336
tree = self.make_branch_and_tree('tree')
2337
self.build_tree(['tree/unchanged'])
2338
tree.add(['unchanged'], ['unchanged-id'])
2339
preview = TransformPreview(tree)
2340
self.addCleanup(preview.finalize)
2341
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2342
'contents', 'new-id')
2343
preview_tree = preview.get_preview_tree()
2344
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2346
def test_path2id_moved(self):
2347
tree = self.make_branch_and_tree('tree')
2348
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2349
tree.add(['old_parent', 'old_parent/child'],
2350
['old_parent-id', 'child-id'])
2351
preview = TransformPreview(tree)
2352
self.addCleanup(preview.finalize)
2353
new_parent = preview.new_directory('new_parent', preview.root,
2355
preview.adjust_path('child', new_parent,
2356
preview.trans_id_file_id('child-id'))
2357
preview_tree = preview.get_preview_tree()
2358
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2359
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2361
def test_path2id_renamed_parent(self):
2362
tree = self.make_branch_and_tree('tree')
2363
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2364
tree.add(['old_name', 'old_name/child'],
2365
['parent-id', 'child-id'])
2366
preview = TransformPreview(tree)
2367
self.addCleanup(preview.finalize)
2368
preview.adjust_path('new_name', preview.root,
2369
preview.trans_id_file_id('parent-id'))
2370
preview_tree = preview.get_preview_tree()
2371
self.assertIs(None, preview_tree.path2id('old_name/child'))
2372
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2374
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2375
preview_tree = tt.get_preview_tree()
2376
preview_result = list(preview_tree.iter_entries_by_dir(
2380
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2381
self.assertEqual(actual_result, preview_result)
2383
def test_iter_entries_by_dir_new(self):
2384
tree = self.make_branch_and_tree('tree')
2385
tt = TreeTransform(tree)
2386
tt.new_file('new', tt.root, 'contents', 'new-id')
2387
self.assertMatchingIterEntries(tt)
2389
def test_iter_entries_by_dir_deleted(self):
2390
tree = self.make_branch_and_tree('tree')
2391
self.build_tree(['tree/deleted'])
2392
tree.add('deleted', 'deleted-id')
2393
tt = TreeTransform(tree)
2394
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2395
self.assertMatchingIterEntries(tt)
2397
def test_iter_entries_by_dir_unversioned(self):
2398
tree = self.make_branch_and_tree('tree')
2399
self.build_tree(['tree/removed'])
2400
tree.add('removed', 'removed-id')
2401
tt = TreeTransform(tree)
2402
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2403
self.assertMatchingIterEntries(tt)
2405
def test_iter_entries_by_dir_moved(self):
2406
tree = self.make_branch_and_tree('tree')
2407
self.build_tree(['tree/moved', 'tree/new_parent/'])
2408
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2409
tt = TreeTransform(tree)
2410
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2411
tt.trans_id_file_id('moved-id'))
2412
self.assertMatchingIterEntries(tt)
2414
def test_iter_entries_by_dir_specific_file_ids(self):
2415
tree = self.make_branch_and_tree('tree')
2416
tree.set_root_id('tree-root-id')
2417
self.build_tree(['tree/parent/', 'tree/parent/child'])
2418
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2419
tt = TreeTransform(tree)
2420
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2422
def test_symlink_content_summary(self):
2423
self.requireFeature(SymlinkFeature)
2424
preview = self.get_empty_preview()
2425
preview.new_symlink('path', preview.root, 'target', 'path-id')
2426
summary = preview.get_preview_tree().path_content_summary('path')
2427
self.assertEqual(('symlink', None, None, 'target'), summary)
2429
def test_missing_content_summary(self):
2430
preview = self.get_empty_preview()
2431
summary = preview.get_preview_tree().path_content_summary('path')
2432
self.assertEqual(('missing', None, None, None), summary)
2434
def test_deleted_content_summary(self):
2435
tree = self.make_branch_and_tree('tree')
2436
self.build_tree(['tree/path/'])
2438
preview = TransformPreview(tree)
2439
self.addCleanup(preview.finalize)
2440
preview.delete_contents(preview.trans_id_tree_path('path'))
2441
summary = preview.get_preview_tree().path_content_summary('path')
2442
self.assertEqual(('missing', None, None, None), summary)
2444
def test_file_content_summary_executable(self):
2445
if not osutils.supports_executable():
2446
raise TestNotApplicable()
2447
preview = self.get_empty_preview()
2448
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2449
preview.set_executability(True, path_id)
2450
summary = preview.get_preview_tree().path_content_summary('path')
2451
self.assertEqual(4, len(summary))
2452
self.assertEqual('file', summary[0])
2453
# size must be known
2454
self.assertEqual(len('contents'), summary[1])
2456
self.assertEqual(True, summary[2])
2457
# will not have hash (not cheap to determine)
2458
self.assertIs(None, summary[3])
2460
def test_change_executability(self):
2461
if not osutils.supports_executable():
2462
raise TestNotApplicable()
2463
tree = self.make_branch_and_tree('tree')
2464
self.build_tree(['tree/path'])
2466
preview = TransformPreview(tree)
2467
self.addCleanup(preview.finalize)
2468
path_id = preview.trans_id_tree_path('path')
2469
preview.set_executability(True, path_id)
2470
summary = preview.get_preview_tree().path_content_summary('path')
2471
self.assertEqual(True, summary[2])
2473
def test_file_content_summary_non_exec(self):
2474
preview = self.get_empty_preview()
2475
preview.new_file('path', preview.root, 'contents', 'path-id')
2476
summary = preview.get_preview_tree().path_content_summary('path')
2477
self.assertEqual(4, len(summary))
2478
self.assertEqual('file', summary[0])
2479
# size must be known
2480
self.assertEqual(len('contents'), summary[1])
2482
if osutils.supports_executable():
2483
self.assertEqual(False, summary[2])
2485
self.assertEqual(None, summary[2])
2486
# will not have hash (not cheap to determine)
2487
self.assertIs(None, summary[3])
2489
def test_dir_content_summary(self):
2490
preview = self.get_empty_preview()
2491
preview.new_directory('path', preview.root, 'path-id')
2492
summary = preview.get_preview_tree().path_content_summary('path')
2493
self.assertEqual(('directory', None, None, None), summary)
2495
def test_tree_content_summary(self):
2496
preview = self.get_empty_preview()
2497
path = preview.new_directory('path', preview.root, 'path-id')
2498
preview.set_tree_reference('rev-1', path)
2499
summary = preview.get_preview_tree().path_content_summary('path')
2500
self.assertEqual(4, len(summary))
2501
self.assertEqual('tree-reference', summary[0])
2503
def test_annotate(self):
2504
tree = self.make_branch_and_tree('tree')
2505
self.build_tree_contents([('tree/file', 'a\n')])
2506
tree.add('file', 'file-id')
2507
tree.commit('a', rev_id='one')
2508
self.build_tree_contents([('tree/file', 'a\nb\n')])
2509
preview = TransformPreview(tree)
2510
self.addCleanup(preview.finalize)
2511
file_trans_id = preview.trans_id_file_id('file-id')
2512
preview.delete_contents(file_trans_id)
2513
preview.create_file('a\nb\nc\n', file_trans_id)
2514
preview_tree = preview.get_preview_tree()
2520
annotation = preview_tree.annotate_iter('file-id', 'me:')
2521
self.assertEqual(expected, annotation)
2523
def test_annotate_missing(self):
2524
preview = self.get_empty_preview()
2525
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2526
preview_tree = preview.get_preview_tree()
2532
annotation = preview_tree.annotate_iter('file-id', 'me:')
2533
self.assertEqual(expected, annotation)
2535
def test_annotate_rename(self):
2536
tree = self.make_branch_and_tree('tree')
2537
self.build_tree_contents([('tree/file', 'a\n')])
2538
tree.add('file', 'file-id')
2539
tree.commit('a', rev_id='one')
2540
preview = TransformPreview(tree)
2541
self.addCleanup(preview.finalize)
2542
file_trans_id = preview.trans_id_file_id('file-id')
2543
preview.adjust_path('newname', preview.root, file_trans_id)
2544
preview_tree = preview.get_preview_tree()
2548
annotation = preview_tree.annotate_iter('file-id', 'me:')
2549
self.assertEqual(expected, annotation)
2551
def test_annotate_deleted(self):
2552
tree = self.make_branch_and_tree('tree')
2553
self.build_tree_contents([('tree/file', 'a\n')])
2554
tree.add('file', 'file-id')
2555
tree.commit('a', rev_id='one')
2556
self.build_tree_contents([('tree/file', 'a\nb\n')])
2557
preview = TransformPreview(tree)
2558
self.addCleanup(preview.finalize)
2559
file_trans_id = preview.trans_id_file_id('file-id')
2560
preview.delete_contents(file_trans_id)
2561
preview_tree = preview.get_preview_tree()
2562
annotation = preview_tree.annotate_iter('file-id', 'me:')
2563
self.assertIs(None, annotation)
2565
def test_stored_kind(self):
2566
preview = self.get_empty_preview()
2567
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2568
preview_tree = preview.get_preview_tree()
2569
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2571
def test_is_executable(self):
2572
preview = self.get_empty_preview()
2573
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2574
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2575
preview_tree = preview.get_preview_tree()
2576
self.assertEqual(True, preview_tree.is_executable('file-id'))
2578
def test_get_set_parent_ids(self):
2579
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2580
self.assertEqual([], preview_tree.get_parent_ids())
2581
preview_tree.set_parent_ids(['rev-1'])
2582
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2584
def test_plan_file_merge(self):
2585
work_a = self.make_branch_and_tree('wta')
2586
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2587
work_a.add('file', 'file-id')
2588
base_id = work_a.commit('base version')
2589
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2590
preview = TransformPreview(work_a)
2591
self.addCleanup(preview.finalize)
2592
trans_id = preview.trans_id_file_id('file-id')
2593
preview.delete_contents(trans_id)
2594
preview.create_file('b\nc\nd\ne\n', trans_id)
2595
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2596
tree_a = preview.get_preview_tree()
2597
tree_a.set_parent_ids([base_id])
2599
('killed-a', 'a\n'),
2600
('killed-b', 'b\n'),
2601
('unchanged', 'c\n'),
2602
('unchanged', 'd\n'),
2605
], list(tree_a.plan_file_merge('file-id', tree_b)))
2607
def test_plan_file_merge_revision_tree(self):
2608
work_a = self.make_branch_and_tree('wta')
2609
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2610
work_a.add('file', 'file-id')
2611
base_id = work_a.commit('base version')
2612
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2613
preview = TransformPreview(work_a.basis_tree())
2614
self.addCleanup(preview.finalize)
2615
trans_id = preview.trans_id_file_id('file-id')
2616
preview.delete_contents(trans_id)
2617
preview.create_file('b\nc\nd\ne\n', trans_id)
2618
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2619
tree_a = preview.get_preview_tree()
2620
tree_a.set_parent_ids([base_id])
2622
('killed-a', 'a\n'),
2623
('killed-b', 'b\n'),
2624
('unchanged', 'c\n'),
2625
('unchanged', 'd\n'),
2628
], list(tree_a.plan_file_merge('file-id', tree_b)))
2630
def test_walkdirs(self):
2631
preview = self.get_empty_preview()
2632
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2633
# FIXME: new_directory should mark root.
2634
preview.adjust_path('', ROOT_PARENT, root)
2635
preview_tree = preview.get_preview_tree()
2636
file_trans_id = preview.new_file('a', preview.root, 'contents',
2638
expected = [(('', 'tree-root'),
2639
[('a', 'a', 'file', None, 'a-id', 'file')])]
2640
self.assertEqual(expected, list(preview_tree.walkdirs()))
2642
def test_extras(self):
2643
work_tree = self.make_branch_and_tree('tree')
2644
self.build_tree(['tree/removed-file', 'tree/existing-file',
2645
'tree/not-removed-file'])
2646
work_tree.add(['removed-file', 'not-removed-file'])
2647
preview = TransformPreview(work_tree)
2648
self.addCleanup(preview.finalize)
2649
preview.new_file('new-file', preview.root, 'contents')
2650
preview.new_file('new-versioned-file', preview.root, 'contents',
2652
tree = preview.get_preview_tree()
2653
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2654
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2657
def test_merge_into_preview(self):
2658
work_tree = self.make_branch_and_tree('tree')
2659
self.build_tree_contents([('tree/file','b\n')])
2660
work_tree.add('file', 'file-id')
2661
work_tree.commit('first commit')
2662
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2663
self.build_tree_contents([('child/file','b\nc\n')])
2664
child_tree.commit('child commit')
2665
child_tree.lock_write()
2666
self.addCleanup(child_tree.unlock)
2667
work_tree.lock_write()
2668
self.addCleanup(work_tree.unlock)
2669
preview = TransformPreview(work_tree)
2670
self.addCleanup(preview.finalize)
2671
file_trans_id = preview.trans_id_file_id('file-id')
2672
preview.delete_contents(file_trans_id)
2673
preview.create_file('a\nb\n', file_trans_id)
2674
pb = progress.DummyProgress()
2675
preview_tree = preview.get_preview_tree()
2676
merger = Merger.from_revision_ids(pb, preview_tree,
2677
child_tree.branch.last_revision(),
2678
other_branch=child_tree.branch,
2679
tree_branch=work_tree.branch)
2680
merger.merge_type = Merge3Merger
2681
tt = merger.make_merger().make_preview_transform()
2682
self.addCleanup(tt.finalize)
2683
final_tree = tt.get_preview_tree()
2684
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2686
def test_merge_preview_into_workingtree(self):
2687
tree = self.make_branch_and_tree('tree')
2688
tree.set_root_id('TREE_ROOT')
2689
tt = TransformPreview(tree)
2690
self.addCleanup(tt.finalize)
2691
tt.new_file('name', tt.root, 'content', 'file-id')
2692
tree2 = self.make_branch_and_tree('tree2')
2693
tree2.set_root_id('TREE_ROOT')
2694
pb = progress.DummyProgress()
2695
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2696
pb, tree.basis_tree())
2697
merger.merge_type = Merge3Merger
2700
def test_merge_preview_into_workingtree_handles_conflicts(self):
2701
tree = self.make_branch_and_tree('tree')
2702
self.build_tree_contents([('tree/foo', 'bar')])
2703
tree.add('foo', 'foo-id')
2705
tt = TransformPreview(tree)
2706
self.addCleanup(tt.finalize)
2707
trans_id = tt.trans_id_file_id('foo-id')
2708
tt.delete_contents(trans_id)
2709
tt.create_file('baz', trans_id)
2710
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2711
self.build_tree_contents([('tree2/foo', 'qux')])
2712
pb = progress.DummyProgress()
2713
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2714
pb, tree.basis_tree())
2715
merger.merge_type = Merge3Merger
2718
def test_is_executable(self):
2719
tree = self.make_branch_and_tree('tree')
2720
preview = TransformPreview(tree)
2721
self.addCleanup(preview.finalize)
2722
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2723
preview_tree = preview.get_preview_tree()
2724
self.assertEqual(False, preview_tree.is_executable('baz-id',
2726
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2728
def test_commit_preview_tree(self):
2729
tree = self.make_branch_and_tree('tree')
2730
rev_id = tree.commit('rev1')
2731
tree.branch.lock_write()
2732
self.addCleanup(tree.branch.unlock)
2733
tt = TransformPreview(tree)
2734
tt.new_file('file', tt.root, 'contents', 'file_id')
2735
self.addCleanup(tt.finalize)
2736
preview = tt.get_preview_tree()
2737
preview.set_parent_ids([rev_id])
2738
builder = tree.branch.get_commit_builder([rev_id])
2739
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2740
builder.finish_inventory()
2741
rev2_id = builder.commit('rev2')
2742
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2743
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2746
class FakeSerializer(object):
2747
"""Serializer implementation that simply returns the input.
2749
The input is returned in the order used by pack.ContainerPushParser.
2752
def bytes_record(bytes, names):
2756
class TestSerializeTransform(tests.TestCaseWithTransport):
2758
_test_needs_features = [tests.UnicodeFilenameFeature]
2760
def get_preview(self, tree=None):
2762
tree = self.make_branch_and_tree('tree')
2763
tt = TransformPreview(tree)
2764
self.addCleanup(tt.finalize)
2767
def assertSerializesTo(self, expected, tt):
2768
records = list(tt.serialize(FakeSerializer()))
2769
self.assertEqual(expected, records)
2772
def default_attribs():
2777
'_new_executability': {},
2779
'_tree_path_ids': {'': 'new-0'},
2781
'_removed_contents': [],
2782
'_non_present_ids': {},
2785
def make_records(self, attribs, contents):
2787
(((('attribs'),),), bencode.bencode(attribs))]
2788
records.extend([(((n, k),), c) for n, k, c in contents])
2791
def creation_records(self):
2792
attribs = self.default_attribs()
2793
attribs['_id_number'] = 3
2794
attribs['_new_name'] = {
2795
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2796
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2797
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2798
attribs['_new_executability'] = {'new-1': 1}
2800
('new-1', 'file', 'i 1\nbar\n'),
2801
('new-2', 'directory', ''),
2803
return self.make_records(attribs, contents)
2805
def test_serialize_creation(self):
2806
tt = self.get_preview()
2807
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2808
tt.new_directory('qux', tt.root, 'quxx')
2809
self.assertSerializesTo(self.creation_records(), tt)
2811
def test_deserialize_creation(self):
2812
tt = self.get_preview()
2813
tt.deserialize(iter(self.creation_records()))
2814
self.assertEqual(3, tt._id_number)
2815
self.assertEqual({'new-1': u'foo\u1234',
2816
'new-2': 'qux'}, tt._new_name)
2817
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2818
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2819
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2820
self.assertEqual({'new-1': True}, tt._new_executability)
2821
self.assertEqual({'new-1': 'file',
2822
'new-2': 'directory'}, tt._new_contents)
2823
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2825
foo_content = foo_limbo.read()
2828
self.assertEqual('bar', foo_content)
2830
def symlink_creation_records(self):
2831
attribs = self.default_attribs()
2832
attribs['_id_number'] = 2
2833
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2834
attribs['_new_parent'] = {'new-1': 'new-0'}
2835
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2836
return self.make_records(attribs, contents)
2838
def test_serialize_symlink_creation(self):
2839
self.requireFeature(tests.SymlinkFeature)
2840
tt = self.get_preview()
2841
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2842
self.assertSerializesTo(self.symlink_creation_records(), tt)
2844
def test_deserialize_symlink_creation(self):
2845
self.requireFeature(tests.SymlinkFeature)
2846
tt = self.get_preview()
2847
tt.deserialize(iter(self.symlink_creation_records()))
2848
abspath = tt._limbo_name('new-1')
2849
foo_content = osutils.readlink(abspath)
2850
self.assertEqual(u'bar\u1234', foo_content)
2852
def make_destruction_preview(self):
2853
tree = self.make_branch_and_tree('.')
2854
self.build_tree([u'foo\u1234', 'bar'])
2855
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2856
return self.get_preview(tree)
2858
def destruction_records(self):
2859
attribs = self.default_attribs()
2860
attribs['_id_number'] = 3
2861
attribs['_removed_id'] = ['new-1']
2862
attribs['_removed_contents'] = ['new-2']
2863
attribs['_tree_path_ids'] = {
2865
u'foo\u1234'.encode('utf-8'): 'new-1',
2868
return self.make_records(attribs, [])
2870
def test_serialize_destruction(self):
2871
tt = self.make_destruction_preview()
2872
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2873
tt.unversion_file(foo_trans_id)
2874
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2875
tt.delete_contents(bar_trans_id)
2876
self.assertSerializesTo(self.destruction_records(), tt)
2878
def test_deserialize_destruction(self):
2879
tt = self.make_destruction_preview()
2880
tt.deserialize(iter(self.destruction_records()))
2881
self.assertEqual({u'foo\u1234': 'new-1',
2883
'': tt.root}, tt._tree_path_ids)
2884
self.assertEqual({'new-1': u'foo\u1234',
2886
tt.root: ''}, tt._tree_id_paths)
2887
self.assertEqual(set(['new-1']), tt._removed_id)
2888
self.assertEqual(set(['new-2']), tt._removed_contents)
2890
def missing_records(self):
2891
attribs = self.default_attribs()
2892
attribs['_id_number'] = 2
2893
attribs['_non_present_ids'] = {
2895
return self.make_records(attribs, [])
2897
def test_serialize_missing(self):
2898
tt = self.get_preview()
2899
boo_trans_id = tt.trans_id_file_id('boo')
2900
self.assertSerializesTo(self.missing_records(), tt)
2902
def test_deserialize_missing(self):
2903
tt = self.get_preview()
2904
tt.deserialize(iter(self.missing_records()))
2905
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2907
def make_modification_preview(self):
2908
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2909
LINES_TWO = 'z\nbb\nx\ndd\n'
2910
tree = self.make_branch_and_tree('tree')
2911
self.build_tree_contents([('tree/file', LINES_ONE)])
2912
tree.add('file', 'file-id')
2913
return self.get_preview(tree), LINES_TWO
2915
def modification_records(self):
2916
attribs = self.default_attribs()
2917
attribs['_id_number'] = 2
2918
attribs['_tree_path_ids'] = {
2921
attribs['_removed_contents'] = ['new-1']
2922
contents = [('new-1', 'file',
2923
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2924
return self.make_records(attribs, contents)
2926
def test_serialize_modification(self):
2927
tt, LINES = self.make_modification_preview()
2928
trans_id = tt.trans_id_file_id('file-id')
2929
tt.delete_contents(trans_id)
2930
tt.create_file(LINES, trans_id)
2931
self.assertSerializesTo(self.modification_records(), tt)
2933
def test_deserialize_modification(self):
2934
tt, LINES = self.make_modification_preview()
2935
tt.deserialize(iter(self.modification_records()))
2936
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2938
def make_kind_change_preview(self):
2939
LINES = 'a\nb\nc\nd\n'
2940
tree = self.make_branch_and_tree('tree')
2941
self.build_tree(['tree/foo/'])
2942
tree.add('foo', 'foo-id')
2943
return self.get_preview(tree), LINES
2945
def kind_change_records(self):
2946
attribs = self.default_attribs()
2947
attribs['_id_number'] = 2
2948
attribs['_tree_path_ids'] = {
2951
attribs['_removed_contents'] = ['new-1']
2952
contents = [('new-1', 'file',
2953
'i 4\na\nb\nc\nd\n\n')]
2954
return self.make_records(attribs, contents)
2956
def test_serialize_kind_change(self):
2957
tt, LINES = self.make_kind_change_preview()
2958
trans_id = tt.trans_id_file_id('foo-id')
2959
tt.delete_contents(trans_id)
2960
tt.create_file(LINES, trans_id)
2961
self.assertSerializesTo(self.kind_change_records(), tt)
2963
def test_deserialize_kind_change(self):
2964
tt, LINES = self.make_kind_change_preview()
2965
tt.deserialize(iter(self.kind_change_records()))
2966
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2968
def make_add_contents_preview(self):
2969
LINES = 'a\nb\nc\nd\n'
2970
tree = self.make_branch_and_tree('tree')
2971
self.build_tree(['tree/foo'])
2973
os.unlink('tree/foo')
2974
return self.get_preview(tree), LINES
2976
def add_contents_records(self):
2977
attribs = self.default_attribs()
2978
attribs['_id_number'] = 2
2979
attribs['_tree_path_ids'] = {
2982
contents = [('new-1', 'file',
2983
'i 4\na\nb\nc\nd\n\n')]
2984
return self.make_records(attribs, contents)
2986
def test_serialize_add_contents(self):
2987
tt, LINES = self.make_add_contents_preview()
2988
trans_id = tt.trans_id_tree_path('foo')
2989
tt.create_file(LINES, trans_id)
2990
self.assertSerializesTo(self.add_contents_records(), tt)
2992
def test_deserialize_add_contents(self):
2993
tt, LINES = self.make_add_contents_preview()
2994
tt.deserialize(iter(self.add_contents_records()))
2995
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2997
def test_get_parents_lines(self):
2998
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2999
LINES_TWO = 'z\nbb\nx\ndd\n'
3000
tree = self.make_branch_and_tree('tree')
3001
self.build_tree_contents([('tree/file', LINES_ONE)])
3002
tree.add('file', 'file-id')
3003
tt = self.get_preview(tree)
3004
trans_id = tt.trans_id_tree_path('file')
3005
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3006
tt._get_parents_lines(trans_id))
3008
def test_get_parents_texts(self):
3009
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3010
LINES_TWO = 'z\nbb\nx\ndd\n'
3011
tree = self.make_branch_and_tree('tree')
3012
self.build_tree_contents([('tree/file', LINES_ONE)])
3013
tree.add('file', 'file-id')
3014
tt = self.get_preview(tree)
3015
trans_id = tt.trans_id_tree_path('file')
3016
self.assertEqual((LINES_ONE,),
3017
tt._get_parents_texts(trans_id))