1
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from StringIO import StringIO
29
revision as _mod_revision,
34
from bzrlib.bzrdir import BzrDir
35
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
36
UnversionedParent, ParentLoop, DeletingParent,
38
from bzrlib.diff import show_diff_trees
39
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
40
ReusingTransform, CantMoveRoot,
41
PathsNotVersionedError, ExistingLimbo,
42
ExistingPendingDeletion, ImmortalLimbo,
43
ImmortalPendingDeletion, LockError)
44
from bzrlib.osutils import file_kind, pathjoin
45
from bzrlib.merge import Merge3Merger, Merger
46
from bzrlib.tests import (
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
54
resolve_conflicts, cook_conflicts,
55
build_tree, get_backup_name,
56
_FileMover, resolve_checkout,
57
TransformPreview, create_from_tree)
60
class TestTreeTransform(tests.TestCaseWithTransport):
63
super(TestTreeTransform, self).setUp()
64
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
67
def get_transform(self):
68
transform = TreeTransform(self.wt)
69
self.addCleanup(transform.finalize)
70
return transform, transform.root
72
def test_existing_limbo(self):
73
transform, root = self.get_transform()
74
limbo_name = transform._limbodir
75
deletion_path = transform._deletiondir
76
os.mkdir(pathjoin(limbo_name, 'hehe'))
77
self.assertRaises(ImmortalLimbo, transform.apply)
78
self.assertRaises(LockError, self.wt.unlock)
79
self.assertRaises(ExistingLimbo, self.get_transform)
80
self.assertRaises(LockError, self.wt.unlock)
81
os.rmdir(pathjoin(limbo_name, 'hehe'))
83
os.rmdir(deletion_path)
84
transform, root = self.get_transform()
87
def test_existing_pending_deletion(self):
88
transform, root = self.get_transform()
89
deletion_path = self._limbodir = urlutils.local_path_from_url(
90
transform._tree._transport.abspath('pending-deletion'))
91
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
92
self.assertRaises(ImmortalPendingDeletion, transform.apply)
93
self.assertRaises(LockError, self.wt.unlock)
94
self.assertRaises(ExistingPendingDeletion, self.get_transform)
97
transform, root = self.get_transform()
98
self.wt.lock_tree_write()
99
self.addCleanup(self.wt.unlock)
100
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
101
imaginary_id = transform.trans_id_tree_path('imaginary')
102
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
103
self.assertEqual(imaginary_id, imaginary_id2)
104
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
105
self.assertEqual(transform.final_kind(root), 'directory')
106
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
107
trans_id = transform.create_path('name', root)
108
self.assertIs(transform.final_file_id(trans_id), None)
109
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
110
transform.create_file('contents', trans_id)
111
transform.set_executability(True, trans_id)
112
transform.version_file('my_pretties', trans_id)
113
self.assertRaises(DuplicateKey, transform.version_file,
114
'my_pretties', trans_id)
115
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
116
self.assertEqual(transform.final_parent(trans_id), root)
117
self.assertIs(transform.final_parent(root), ROOT_PARENT)
118
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
119
oz_id = transform.create_path('oz', root)
120
transform.create_directory(oz_id)
121
transform.version_file('ozzie', oz_id)
122
trans_id2 = transform.create_path('name2', root)
123
transform.create_file('contents', trans_id2)
124
transform.set_executability(False, trans_id2)
125
transform.version_file('my_pretties2', trans_id2)
126
modified_paths = transform.apply().modified_paths
127
self.assertEqual('contents', self.wt.get_file_byname('name').read())
128
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
129
self.assertIs(self.wt.is_executable('my_pretties'), True)
130
self.assertIs(self.wt.is_executable('my_pretties2'), False)
131
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
132
self.assertEqual(len(modified_paths), 3)
133
tree_mod_paths = [self.wt.id2abspath(f) for f in
134
('ozzie', 'my_pretties', 'my_pretties2')]
135
self.assertSubset(tree_mod_paths, modified_paths)
136
# is it safe to finalize repeatedly?
140
def test_create_files_same_timestamp(self):
141
transform, root = self.get_transform()
142
self.wt.lock_tree_write()
143
self.addCleanup(self.wt.unlock)
144
# Roll back the clock, so that we know everything is being set to the
146
transform._creation_mtime = creation_mtime = time.time() - 20.0
147
transform.create_file('content-one',
148
transform.create_path('one', root))
149
time.sleep(1) # *ugly*
150
transform.create_file('content-two',
151
transform.create_path('two', root))
153
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
155
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
157
# We only guarantee 2s resolution
158
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
159
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
160
# But if we have more than that, all files should get the same result
161
self.assertEqual(st1.st_mtime, st2.st_mtime)
163
def test_hardlink(self):
164
self.requireFeature(HardlinkFeature)
165
transform, root = self.get_transform()
166
transform.new_file('file1', root, 'contents')
168
target = self.make_branch_and_tree('target')
169
target_transform = TreeTransform(target)
170
trans_id = target_transform.create_path('file1', target_transform.root)
171
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
172
target_transform.apply()
173
self.failUnlessExists('target/file1')
174
source_stat = os.stat(self.wt.abspath('file1'))
175
target_stat = os.stat('target/file1')
176
self.assertEqual(source_stat, target_stat)
178
def test_convenience(self):
179
transform, root = self.get_transform()
180
self.wt.lock_tree_write()
181
self.addCleanup(self.wt.unlock)
182
trans_id = transform.new_file('name', root, 'contents',
184
oz = transform.new_directory('oz', root, 'oz-id')
185
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
186
toto = transform.new_file('toto', dorothy, 'toto-contents',
189
self.assertEqual(len(transform.find_conflicts()), 0)
191
self.assertRaises(ReusingTransform, transform.find_conflicts)
192
self.assertEqual('contents', file(self.wt.abspath('name')).read())
193
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
194
self.assertIs(self.wt.is_executable('my_pretties'), True)
195
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
196
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
197
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
199
self.assertEqual('toto-contents',
200
self.wt.get_file_byname('oz/dorothy/toto').read())
201
self.assertIs(self.wt.is_executable('toto-id'), False)
203
def test_tree_reference(self):
204
transform, root = self.get_transform()
205
tree = transform._tree
206
trans_id = transform.new_directory('reference', root, 'subtree-id')
207
transform.set_tree_reference('subtree-revision', trans_id)
210
self.addCleanup(tree.unlock)
211
self.assertEqual('subtree-revision',
212
tree.inventory['subtree-id'].reference_revision)
214
def test_conflicts(self):
215
transform, root = self.get_transform()
216
trans_id = transform.new_file('name', root, 'contents',
218
self.assertEqual(len(transform.find_conflicts()), 0)
219
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
220
self.assertEqual(transform.find_conflicts(),
221
[('duplicate', trans_id, trans_id2, 'name')])
222
self.assertRaises(MalformedTransform, transform.apply)
223
transform.adjust_path('name', trans_id, trans_id2)
224
self.assertEqual(transform.find_conflicts(),
225
[('non-directory parent', trans_id)])
226
tinman_id = transform.trans_id_tree_path('tinman')
227
transform.adjust_path('name', tinman_id, trans_id2)
228
self.assertEqual(transform.find_conflicts(),
229
[('unversioned parent', tinman_id),
230
('missing parent', tinman_id)])
231
lion_id = transform.create_path('lion', root)
232
self.assertEqual(transform.find_conflicts(),
233
[('unversioned parent', tinman_id),
234
('missing parent', tinman_id)])
235
transform.adjust_path('name', lion_id, trans_id2)
236
self.assertEqual(transform.find_conflicts(),
237
[('unversioned parent', lion_id),
238
('missing parent', lion_id)])
239
transform.version_file("Courage", lion_id)
240
self.assertEqual(transform.find_conflicts(),
241
[('missing parent', lion_id),
242
('versioning no contents', lion_id)])
243
transform.adjust_path('name2', root, trans_id2)
244
self.assertEqual(transform.find_conflicts(),
245
[('versioning no contents', lion_id)])
246
transform.create_file('Contents, okay?', lion_id)
247
transform.adjust_path('name2', trans_id2, trans_id2)
248
self.assertEqual(transform.find_conflicts(),
249
[('parent loop', trans_id2),
250
('non-directory parent', trans_id2)])
251
transform.adjust_path('name2', root, trans_id2)
252
oz_id = transform.new_directory('oz', root)
253
transform.set_executability(True, oz_id)
254
self.assertEqual(transform.find_conflicts(),
255
[('unversioned executability', oz_id)])
256
transform.version_file('oz-id', oz_id)
257
self.assertEqual(transform.find_conflicts(),
258
[('non-file executability', oz_id)])
259
transform.set_executability(None, oz_id)
260
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
262
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
263
self.assertEqual('contents', file(self.wt.abspath('name')).read())
264
transform2, root = self.get_transform()
265
oz_id = transform2.trans_id_tree_file_id('oz-id')
266
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
267
result = transform2.find_conflicts()
268
fp = FinalPaths(transform2)
269
self.assert_('oz/tip' in transform2._tree_path_ids)
270
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
271
self.assertEqual(len(result), 2)
272
self.assertEqual((result[0][0], result[0][1]),
273
('duplicate', newtip))
274
self.assertEqual((result[1][0], result[1][2]),
275
('duplicate id', newtip))
276
transform2.finalize()
277
transform3 = TreeTransform(self.wt)
278
self.addCleanup(transform3.finalize)
279
oz_id = transform3.trans_id_tree_file_id('oz-id')
280
transform3.delete_contents(oz_id)
281
self.assertEqual(transform3.find_conflicts(),
282
[('missing parent', oz_id)])
283
root_id = transform3.root
284
tip_id = transform3.trans_id_tree_file_id('tip-id')
285
transform3.adjust_path('tip', root_id, tip_id)
288
def test_conflict_on_case_insensitive(self):
289
tree = self.make_branch_and_tree('tree')
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
transform.new_file('FiLe', transform.root, 'content')
298
result = transform.find_conflicts()
299
self.assertEqual([], result)
301
# Force the tree to report that it is case insensitive, for conflict
303
tree.case_sensitive = False
304
transform = TreeTransform(tree)
305
self.addCleanup(transform.finalize)
306
transform.new_file('file', transform.root, 'content')
307
transform.new_file('FiLe', transform.root, 'content')
308
result = transform.find_conflicts()
309
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
311
def test_conflict_on_case_insensitive_existing(self):
312
tree = self.make_branch_and_tree('tree')
313
self.build_tree(['tree/FiLe'])
314
# Don't try this at home, kids!
315
# Force the tree to report that it is case sensitive, for conflict
317
tree.case_sensitive = True
318
transform = TreeTransform(tree)
319
self.addCleanup(transform.finalize)
320
transform.new_file('file', transform.root, 'content')
321
result = transform.find_conflicts()
322
self.assertEqual([], result)
324
# Force the tree to report that it is case insensitive, for conflict
326
tree.case_sensitive = False
327
transform = TreeTransform(tree)
328
self.addCleanup(transform.finalize)
329
transform.new_file('file', transform.root, 'content')
330
result = transform.find_conflicts()
331
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
333
def test_resolve_case_insensitive_conflict(self):
334
tree = self.make_branch_and_tree('tree')
335
# Don't try this at home, kids!
336
# Force the tree to report that it is case insensitive, for conflict
338
tree.case_sensitive = False
339
transform = TreeTransform(tree)
340
self.addCleanup(transform.finalize)
341
transform.new_file('file', transform.root, 'content')
342
transform.new_file('FiLe', transform.root, 'content')
343
resolve_conflicts(transform)
345
self.failUnlessExists('tree/file')
346
self.failUnlessExists('tree/FiLe.moved')
348
def test_resolve_checkout_case_conflict(self):
349
tree = self.make_branch_and_tree('tree')
350
# Don't try this at home, kids!
351
# Force the tree to report that it is case insensitive, for conflict
353
tree.case_sensitive = False
354
transform = TreeTransform(tree)
355
self.addCleanup(transform.finalize)
356
transform.new_file('file', transform.root, 'content')
357
transform.new_file('FiLe', transform.root, 'content')
358
resolve_conflicts(transform,
359
pass_func=lambda t, c: resolve_checkout(t, c, []))
361
self.failUnlessExists('tree/file')
362
self.failUnlessExists('tree/FiLe.moved')
364
def test_apply_case_conflict(self):
365
"""Ensure that a transform with case conflicts can always be applied"""
366
tree = self.make_branch_and_tree('tree')
367
transform = TreeTransform(tree)
368
self.addCleanup(transform.finalize)
369
transform.new_file('file', transform.root, 'content')
370
transform.new_file('FiLe', transform.root, 'content')
371
dir = transform.new_directory('dir', transform.root)
372
transform.new_file('dirfile', dir, 'content')
373
transform.new_file('dirFiLe', dir, 'content')
374
resolve_conflicts(transform)
376
self.failUnlessExists('tree/file')
377
if not os.path.exists('tree/FiLe.moved'):
378
self.failUnlessExists('tree/FiLe')
379
self.failUnlessExists('tree/dir/dirfile')
380
if not os.path.exists('tree/dir/dirFiLe.moved'):
381
self.failUnlessExists('tree/dir/dirFiLe')
383
def test_case_insensitive_limbo(self):
384
tree = self.make_branch_and_tree('tree')
385
# Don't try this at home, kids!
386
# Force the tree to report that it is case insensitive
387
tree.case_sensitive = False
388
transform = TreeTransform(tree)
389
self.addCleanup(transform.finalize)
390
dir = transform.new_directory('dir', transform.root)
391
first = transform.new_file('file', dir, 'content')
392
second = transform.new_file('FiLe', dir, 'content')
393
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
394
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
396
def test_adjust_path_updates_child_limbo_names(self):
397
tree = self.make_branch_and_tree('tree')
398
transform = TreeTransform(tree)
399
self.addCleanup(transform.finalize)
400
foo_id = transform.new_directory('foo', transform.root)
401
bar_id = transform.new_directory('bar', foo_id)
402
baz_id = transform.new_directory('baz', bar_id)
403
qux_id = transform.new_directory('qux', baz_id)
404
transform.adjust_path('quxx', foo_id, bar_id)
405
self.assertStartsWith(transform._limbo_name(qux_id),
406
transform._limbo_name(bar_id))
408
def test_add_del(self):
409
start, root = self.get_transform()
410
start.new_directory('a', root, 'a')
412
transform, root = self.get_transform()
413
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
414
transform.new_directory('a', root, 'a')
417
def test_unversioning(self):
418
create_tree, root = self.get_transform()
419
parent_id = create_tree.new_directory('parent', root, 'parent-id')
420
create_tree.new_file('child', parent_id, 'child', 'child-id')
422
unversion = TreeTransform(self.wt)
423
self.addCleanup(unversion.finalize)
424
parent = unversion.trans_id_tree_path('parent')
425
unversion.unversion_file(parent)
426
self.assertEqual(unversion.find_conflicts(),
427
[('unversioned parent', parent_id)])
428
file_id = unversion.trans_id_tree_file_id('child-id')
429
unversion.unversion_file(file_id)
432
def test_name_invariants(self):
433
create_tree, root = self.get_transform()
435
root = create_tree.root
436
create_tree.new_file('name1', root, 'hello1', 'name1')
437
create_tree.new_file('name2', root, 'hello2', 'name2')
438
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
439
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
440
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
441
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
444
mangle_tree,root = self.get_transform()
445
root = mangle_tree.root
447
name1 = mangle_tree.trans_id_tree_file_id('name1')
448
name2 = mangle_tree.trans_id_tree_file_id('name2')
449
mangle_tree.adjust_path('name2', root, name1)
450
mangle_tree.adjust_path('name1', root, name2)
452
#tests for deleting parent directories
453
ddir = mangle_tree.trans_id_tree_file_id('ddir')
454
mangle_tree.delete_contents(ddir)
455
dfile = mangle_tree.trans_id_tree_file_id('dfile')
456
mangle_tree.delete_versioned(dfile)
457
mangle_tree.unversion_file(dfile)
458
mfile = mangle_tree.trans_id_tree_file_id('mfile')
459
mangle_tree.adjust_path('mfile', root, mfile)
461
#tests for adding parent directories
462
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
463
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
464
mangle_tree.adjust_path('mfile2', newdir, mfile2)
465
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
466
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
467
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
468
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
470
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
471
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
472
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
473
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
474
self.assertEqual(file(mfile2_path).read(), 'later2')
475
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
476
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
477
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
478
self.assertEqual(file(newfile_path).read(), 'hello3')
479
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
480
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
481
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
483
def test_both_rename(self):
484
create_tree,root = self.get_transform()
485
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
486
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
488
mangle_tree,root = self.get_transform()
489
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
490
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
491
mangle_tree.adjust_path('test', root, selftest)
492
mangle_tree.adjust_path('test_too_much', root, selftest)
493
mangle_tree.set_executability(True, blackbox)
496
def test_both_rename2(self):
497
create_tree,root = self.get_transform()
498
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
499
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
500
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
501
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
504
mangle_tree,root = self.get_transform()
505
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
506
tests = mangle_tree.trans_id_tree_file_id('tests-id')
507
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
508
mangle_tree.adjust_path('selftest', bzrlib, tests)
509
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
510
mangle_tree.set_executability(True, test_too_much)
513
def test_both_rename3(self):
514
create_tree,root = self.get_transform()
515
tests = create_tree.new_directory('tests', root, 'tests-id')
516
create_tree.new_file('test_too_much.py', tests, 'hello1',
519
mangle_tree,root = self.get_transform()
520
tests = mangle_tree.trans_id_tree_file_id('tests-id')
521
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
522
mangle_tree.adjust_path('selftest', root, tests)
523
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
524
mangle_tree.set_executability(True, test_too_much)
527
def test_move_dangling_ie(self):
528
create_tree, root = self.get_transform()
530
root = create_tree.root
531
create_tree.new_file('name1', root, 'hello1', 'name1')
533
delete_contents, root = self.get_transform()
534
file = delete_contents.trans_id_tree_file_id('name1')
535
delete_contents.delete_contents(file)
536
delete_contents.apply()
537
move_id, root = self.get_transform()
538
name1 = move_id.trans_id_tree_file_id('name1')
539
newdir = move_id.new_directory('dir', root, 'newdir')
540
move_id.adjust_path('name2', newdir, name1)
543
def test_replace_dangling_ie(self):
544
create_tree, root = self.get_transform()
546
root = create_tree.root
547
create_tree.new_file('name1', root, 'hello1', 'name1')
549
delete_contents = TreeTransform(self.wt)
550
self.addCleanup(delete_contents.finalize)
551
file = delete_contents.trans_id_tree_file_id('name1')
552
delete_contents.delete_contents(file)
553
delete_contents.apply()
554
delete_contents.finalize()
555
replace = TreeTransform(self.wt)
556
self.addCleanup(replace.finalize)
557
name2 = replace.new_file('name2', root, 'hello2', 'name1')
558
conflicts = replace.find_conflicts()
559
name1 = replace.trans_id_tree_file_id('name1')
560
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
561
resolve_conflicts(replace)
564
def _test_symlinks(self, link_name1,link_target1,
565
link_name2, link_target2):
567
def ozpath(p): return 'oz/' + p
569
self.requireFeature(SymlinkFeature)
570
transform, root = self.get_transform()
571
oz_id = transform.new_directory('oz', root, 'oz-id')
572
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
574
wiz_id = transform.create_path(link_name2, oz_id)
575
transform.create_symlink(link_target2, wiz_id)
576
transform.version_file('wiz-id2', wiz_id)
577
transform.set_executability(True, wiz_id)
578
self.assertEqual(transform.find_conflicts(),
579
[('non-file executability', wiz_id)])
580
transform.set_executability(None, wiz_id)
582
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
583
self.assertEqual('symlink',
584
file_kind(self.wt.abspath(ozpath(link_name1))))
585
self.assertEqual(link_target2,
586
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
587
self.assertEqual(link_target1,
588
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
590
def test_symlinks(self):
591
self._test_symlinks('wizard', 'wizard-target',
592
'wizard2', 'behind_curtain')
594
def test_symlinks_unicode(self):
595
self.requireFeature(tests.UnicodeFilenameFeature)
596
self._test_symlinks(u'\N{Euro Sign}wizard',
597
u'wizard-targ\N{Euro Sign}t',
598
u'\N{Euro Sign}wizard2',
599
u'b\N{Euro Sign}hind_curtain')
601
def test_unable_create_symlink(self):
603
wt = self.make_branch_and_tree('.')
604
tt = TreeTransform(wt) # TreeTransform obtains write lock
606
tt.new_symlink('foo', tt.root, 'bar')
610
os_symlink = getattr(os, 'symlink', None)
613
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
615
"Unable to create symlink 'foo' on this platform",
619
os.symlink = os_symlink
621
def get_conflicted(self):
622
create,root = self.get_transform()
623
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
624
oz = create.new_directory('oz', root, 'oz-id')
625
create.new_directory('emeraldcity', oz, 'emerald-id')
627
conflicts,root = self.get_transform()
628
# set up duplicate entry, duplicate id
629
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
631
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
632
oz = conflicts.trans_id_tree_file_id('oz-id')
633
# set up DeletedParent parent conflict
634
conflicts.delete_versioned(oz)
635
emerald = conflicts.trans_id_tree_file_id('emerald-id')
636
# set up MissingParent conflict
637
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
638
conflicts.adjust_path('munchkincity', root, munchkincity)
639
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
641
conflicts.adjust_path('emeraldcity', emerald, emerald)
642
return conflicts, emerald, oz, old_dorothy, new_dorothy
644
def test_conflict_resolution(self):
645
conflicts, emerald, oz, old_dorothy, new_dorothy =\
646
self.get_conflicted()
647
resolve_conflicts(conflicts)
648
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
649
self.assertIs(conflicts.final_file_id(old_dorothy), None)
650
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
651
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
652
self.assertEqual(conflicts.final_parent(emerald), oz)
655
def test_cook_conflicts(self):
656
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
657
raw_conflicts = resolve_conflicts(tt)
658
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
659
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
660
'dorothy', None, 'dorothy-id')
661
self.assertEqual(cooked_conflicts[0], duplicate)
662
duplicate_id = DuplicateID('Unversioned existing file',
663
'dorothy.moved', 'dorothy', None,
665
self.assertEqual(cooked_conflicts[1], duplicate_id)
666
missing_parent = MissingParent('Created directory', 'munchkincity',
668
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
669
self.assertEqual(cooked_conflicts[2], missing_parent)
670
unversioned_parent = UnversionedParent('Versioned directory',
673
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
675
self.assertEqual(cooked_conflicts[3], unversioned_parent)
676
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
677
'oz/emeraldcity', 'emerald-id', 'emerald-id')
678
self.assertEqual(cooked_conflicts[4], deleted_parent)
679
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
680
self.assertEqual(cooked_conflicts[6], parent_loop)
681
self.assertEqual(len(cooked_conflicts), 7)
684
def test_string_conflicts(self):
685
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
686
raw_conflicts = resolve_conflicts(tt)
687
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
689
conflicts_s = [str(c) for c in cooked_conflicts]
690
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
691
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
692
'Moved existing file to '
694
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
695
'Unversioned existing file '
697
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
698
' munchkincity. Created directory.')
699
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
700
' versioned, but has versioned'
701
' children. Versioned directory.')
702
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
703
" is not empty. Not deleting.")
704
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
705
' versioned, but has versioned'
706
' children. Versioned directory.')
707
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
708
' oz/emeraldcity. Cancelled move.')
710
def prepare_wrong_parent_kind(self):
711
tt, root = self.get_transform()
712
tt.new_file('parent', root, 'contents', 'parent-id')
714
tt, root = self.get_transform()
715
parent_id = tt.trans_id_file_id('parent-id')
716
tt.new_file('child,', parent_id, 'contents2', 'file-id')
719
def test_find_conflicts_wrong_parent_kind(self):
720
tt = self.prepare_wrong_parent_kind()
723
def test_resolve_conflicts_wrong_existing_parent_kind(self):
724
tt = self.prepare_wrong_parent_kind()
725
raw_conflicts = resolve_conflicts(tt)
726
self.assertEqual(set([('non-directory parent', 'Created directory',
727
'new-3')]), raw_conflicts)
728
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
729
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
730
'parent-id')], cooked_conflicts)
732
self.assertEqual(None, self.wt.path2id('parent'))
733
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
735
def test_resolve_conflicts_wrong_new_parent_kind(self):
736
tt, root = self.get_transform()
737
parent_id = tt.new_directory('parent', root, 'parent-id')
738
tt.new_file('child,', parent_id, 'contents2', 'file-id')
740
tt, root = self.get_transform()
741
parent_id = tt.trans_id_file_id('parent-id')
742
tt.delete_contents(parent_id)
743
tt.create_file('contents', parent_id)
744
raw_conflicts = resolve_conflicts(tt)
745
self.assertEqual(set([('non-directory parent', 'Created directory',
746
'new-3')]), raw_conflicts)
748
self.assertEqual(None, self.wt.path2id('parent'))
749
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
751
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
752
tt, root = self.get_transform()
753
parent_id = tt.new_directory('parent', root)
754
tt.new_file('child,', parent_id, 'contents2')
756
tt, root = self.get_transform()
757
parent_id = tt.trans_id_tree_path('parent')
758
tt.delete_contents(parent_id)
759
tt.create_file('contents', parent_id)
760
resolve_conflicts(tt)
762
self.assertIs(None, self.wt.path2id('parent'))
763
self.assertIs(None, self.wt.path2id('parent.new'))
765
def test_moving_versioned_directories(self):
766
create, root = self.get_transform()
767
kansas = create.new_directory('kansas', root, 'kansas-id')
768
create.new_directory('house', kansas, 'house-id')
769
create.new_directory('oz', root, 'oz-id')
771
cyclone, root = self.get_transform()
772
oz = cyclone.trans_id_tree_file_id('oz-id')
773
house = cyclone.trans_id_tree_file_id('house-id')
774
cyclone.adjust_path('house', oz, house)
777
def test_moving_root(self):
778
create, root = self.get_transform()
779
fun = create.new_directory('fun', root, 'fun-id')
780
create.new_directory('sun', root, 'sun-id')
781
create.new_directory('moon', root, 'moon')
783
transform, root = self.get_transform()
784
transform.adjust_root_path('oldroot', fun)
785
new_root=transform.trans_id_tree_path('')
786
transform.version_file('new-root', new_root)
789
def test_renames(self):
790
create, root = self.get_transform()
791
old = create.new_directory('old-parent', root, 'old-id')
792
intermediate = create.new_directory('intermediate', old, 'im-id')
793
myfile = create.new_file('myfile', intermediate, 'myfile-text',
796
rename, root = self.get_transform()
797
old = rename.trans_id_file_id('old-id')
798
rename.adjust_path('new', root, old)
799
myfile = rename.trans_id_file_id('myfile-id')
800
rename.set_executability(True, myfile)
803
def test_set_executability_order(self):
804
"""Ensure that executability behaves the same, no matter what order.
806
- create file and set executability simultaneously
807
- create file and set executability afterward
808
- unsetting the executability of a file whose executability has not been
809
declared should throw an exception (this may happen when a
810
merge attempts to create a file with a duplicate ID)
812
transform, root = self.get_transform()
815
self.addCleanup(wt.unlock)
816
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
818
sac = transform.new_file('set_after_creation', root,
819
'Set after creation', 'sac')
820
transform.set_executability(True, sac)
821
uws = transform.new_file('unset_without_set', root, 'Unset badly',
823
self.assertRaises(KeyError, transform.set_executability, None, uws)
825
self.assertTrue(wt.is_executable('soc'))
826
self.assertTrue(wt.is_executable('sac'))
828
def test_preserve_mode(self):
829
"""File mode is preserved when replacing content"""
830
if sys.platform == 'win32':
831
raise TestSkipped('chmod has no effect on win32')
832
transform, root = self.get_transform()
833
transform.new_file('file1', root, 'contents', 'file1-id', True)
836
self.addCleanup(self.wt.unlock)
837
self.assertTrue(self.wt.is_executable('file1-id'))
838
transform, root = self.get_transform()
839
file1_id = transform.trans_id_tree_file_id('file1-id')
840
transform.delete_contents(file1_id)
841
transform.create_file('contents2', file1_id)
843
self.assertTrue(self.wt.is_executable('file1-id'))
845
def test__set_mode_stats_correctly(self):
846
"""_set_mode stats to determine file mode."""
847
if sys.platform == 'win32':
848
raise TestSkipped('chmod has no effect on win32')
852
def instrumented_stat(path):
853
stat_paths.append(path)
854
return real_stat(path)
856
transform, root = self.get_transform()
858
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
859
file_id='bar-id-1', executable=False)
862
transform, root = self.get_transform()
863
bar1_id = transform.trans_id_tree_path('bar')
864
bar2_id = transform.trans_id_tree_path('bar2')
866
os.stat = instrumented_stat
867
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
872
bar1_abspath = self.wt.abspath('bar')
873
self.assertEqual([bar1_abspath], stat_paths)
875
def test_iter_changes(self):
876
self.wt.set_root_id('eert_toor')
877
transform, root = self.get_transform()
878
transform.new_file('old', root, 'blah', 'id-1', True)
880
transform, root = self.get_transform()
882
self.assertEqual([], list(transform.iter_changes()))
883
old = transform.trans_id_tree_file_id('id-1')
884
transform.unversion_file(old)
885
self.assertEqual([('id-1', ('old', None), False, (True, False),
886
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
887
(True, True))], list(transform.iter_changes()))
888
transform.new_directory('new', root, 'id-1')
889
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
890
('eert_toor', 'eert_toor'), ('old', 'new'),
891
('file', 'directory'),
892
(True, False))], list(transform.iter_changes()))
896
def test_iter_changes_new(self):
897
self.wt.set_root_id('eert_toor')
898
transform, root = self.get_transform()
899
transform.new_file('old', root, 'blah')
901
transform, root = self.get_transform()
903
old = transform.trans_id_tree_path('old')
904
transform.version_file('id-1', old)
905
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
906
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
907
(False, False))], list(transform.iter_changes()))
911
def test_iter_changes_modifications(self):
912
self.wt.set_root_id('eert_toor')
913
transform, root = self.get_transform()
914
transform.new_file('old', root, 'blah', 'id-1')
915
transform.new_file('new', root, 'blah')
916
transform.new_directory('subdir', root, 'subdir-id')
918
transform, root = self.get_transform()
920
old = transform.trans_id_tree_path('old')
921
subdir = transform.trans_id_tree_file_id('subdir-id')
922
new = transform.trans_id_tree_path('new')
923
self.assertEqual([], list(transform.iter_changes()))
926
transform.delete_contents(old)
927
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
928
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
929
(False, False))], list(transform.iter_changes()))
932
transform.create_file('blah', old)
933
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
934
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
935
(False, False))], list(transform.iter_changes()))
936
transform.cancel_deletion(old)
937
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
938
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
939
(False, False))], list(transform.iter_changes()))
940
transform.cancel_creation(old)
942
# move file_id to a different file
943
self.assertEqual([], list(transform.iter_changes()))
944
transform.unversion_file(old)
945
transform.version_file('id-1', new)
946
transform.adjust_path('old', root, new)
947
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
948
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
949
(False, False))], list(transform.iter_changes()))
950
transform.cancel_versioning(new)
951
transform._removed_id = set()
954
self.assertEqual([], list(transform.iter_changes()))
955
transform.set_executability(True, old)
956
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
957
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
958
(False, True))], list(transform.iter_changes()))
959
transform.set_executability(None, old)
962
self.assertEqual([], list(transform.iter_changes()))
963
transform.adjust_path('new', root, old)
964
transform._new_parent = {}
965
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
966
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
967
(False, False))], list(transform.iter_changes()))
968
transform._new_name = {}
971
self.assertEqual([], list(transform.iter_changes()))
972
transform.adjust_path('new', subdir, old)
973
transform._new_name = {}
974
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
975
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
976
('file', 'file'), (False, False))],
977
list(transform.iter_changes()))
978
transform._new_path = {}
983
def test_iter_changes_modified_bleed(self):
984
self.wt.set_root_id('eert_toor')
985
"""Modified flag should not bleed from one change to another"""
986
# unfortunately, we have no guarantee that file1 (which is modified)
987
# will be applied before file2. And if it's applied after file2, it
988
# obviously can't bleed into file2's change output. But for now, it
990
transform, root = self.get_transform()
991
transform.new_file('file1', root, 'blah', 'id-1')
992
transform.new_file('file2', root, 'blah', 'id-2')
994
transform, root = self.get_transform()
996
transform.delete_contents(transform.trans_id_file_id('id-1'))
997
transform.set_executability(True,
998
transform.trans_id_file_id('id-2'))
999
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1000
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1001
('file', None), (False, False)),
1002
('id-2', (u'file2', u'file2'), False, (True, True),
1003
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1004
('file', 'file'), (False, True))],
1005
list(transform.iter_changes()))
1007
transform.finalize()
1009
def test_iter_changes_move_missing(self):
1010
"""Test moving ids with no files around"""
1011
self.wt.set_root_id('toor_eert')
1012
# Need two steps because versioning a non-existant file is a conflict.
1013
transform, root = self.get_transform()
1014
transform.new_directory('floater', root, 'floater-id')
1016
transform, root = self.get_transform()
1017
transform.delete_contents(transform.trans_id_tree_path('floater'))
1019
transform, root = self.get_transform()
1020
floater = transform.trans_id_tree_path('floater')
1022
transform.adjust_path('flitter', root, floater)
1023
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1024
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1025
(None, None), (False, False))], list(transform.iter_changes()))
1027
transform.finalize()
1029
def test_iter_changes_pointless(self):
1030
"""Ensure that no-ops are not treated as modifications"""
1031
self.wt.set_root_id('eert_toor')
1032
transform, root = self.get_transform()
1033
transform.new_file('old', root, 'blah', 'id-1')
1034
transform.new_directory('subdir', root, 'subdir-id')
1036
transform, root = self.get_transform()
1038
old = transform.trans_id_tree_path('old')
1039
subdir = transform.trans_id_tree_file_id('subdir-id')
1040
self.assertEqual([], list(transform.iter_changes()))
1041
transform.delete_contents(subdir)
1042
transform.create_directory(subdir)
1043
transform.set_executability(False, old)
1044
transform.unversion_file(old)
1045
transform.version_file('id-1', old)
1046
transform.adjust_path('old', root, old)
1047
self.assertEqual([], list(transform.iter_changes()))
1049
transform.finalize()
1051
def test_rename_count(self):
1052
transform, root = self.get_transform()
1053
transform.new_file('name1', root, 'contents')
1054
self.assertEqual(transform.rename_count, 0)
1056
self.assertEqual(transform.rename_count, 1)
1057
transform2, root = self.get_transform()
1058
transform2.adjust_path('name2', root,
1059
transform2.trans_id_tree_path('name1'))
1060
self.assertEqual(transform2.rename_count, 0)
1062
self.assertEqual(transform2.rename_count, 2)
1064
def test_change_parent(self):
1065
"""Ensure that after we change a parent, the results are still right.
1067
Renames and parent changes on pending transforms can happen as part
1068
of conflict resolution, and are explicitly permitted by the
1071
This test ensures they work correctly with the rename-avoidance
1074
transform, root = self.get_transform()
1075
parent1 = transform.new_directory('parent1', root)
1076
child1 = transform.new_file('child1', parent1, 'contents')
1077
parent2 = transform.new_directory('parent2', root)
1078
transform.adjust_path('child1', parent2, child1)
1080
self.failIfExists(self.wt.abspath('parent1/child1'))
1081
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1082
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1083
# no rename for child1 (counting only renames during apply)
1084
self.failUnlessEqual(2, transform.rename_count)
1086
def test_cancel_parent(self):
1087
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1089
This is like the test_change_parent, except that we cancel the parent
1090
before adjusting the path. The transform must detect that the
1091
directory is non-empty, and move children to safe locations.
1093
transform, root = self.get_transform()
1094
parent1 = transform.new_directory('parent1', root)
1095
child1 = transform.new_file('child1', parent1, 'contents')
1096
child2 = transform.new_file('child2', parent1, 'contents')
1098
transform.cancel_creation(parent1)
1100
self.fail('Failed to move child1 before deleting parent1')
1101
transform.cancel_creation(child2)
1102
transform.create_directory(parent1)
1104
transform.cancel_creation(parent1)
1105
# If the transform incorrectly believes that child2 is still in
1106
# parent1's limbo directory, it will try to rename it and fail
1107
# because was already moved by the first cancel_creation.
1109
self.fail('Transform still thinks child2 is a child of parent1')
1110
parent2 = transform.new_directory('parent2', root)
1111
transform.adjust_path('child1', parent2, child1)
1113
self.failIfExists(self.wt.abspath('parent1'))
1114
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1115
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1116
self.failUnlessEqual(2, transform.rename_count)
1118
def test_adjust_and_cancel(self):
1119
"""Make sure adjust_path keeps track of limbo children properly"""
1120
transform, root = self.get_transform()
1121
parent1 = transform.new_directory('parent1', root)
1122
child1 = transform.new_file('child1', parent1, 'contents')
1123
parent2 = transform.new_directory('parent2', root)
1124
transform.adjust_path('child1', parent2, child1)
1125
transform.cancel_creation(child1)
1127
transform.cancel_creation(parent1)
1128
# if the transform thinks child1 is still in parent1's limbo
1129
# directory, it will attempt to move it and fail.
1131
self.fail('Transform still thinks child1 is a child of parent1')
1132
transform.finalize()
1134
def test_noname_contents(self):
1135
"""TreeTransform should permit deferring naming files."""
1136
transform, root = self.get_transform()
1137
parent = transform.trans_id_file_id('parent-id')
1139
transform.create_directory(parent)
1141
self.fail("Can't handle contents with no name")
1142
transform.finalize()
1144
def test_noname_contents_nested(self):
1145
"""TreeTransform should permit deferring naming files."""
1146
transform, root = self.get_transform()
1147
parent = transform.trans_id_file_id('parent-id')
1149
transform.create_directory(parent)
1151
self.fail("Can't handle contents with no name")
1152
child = transform.new_directory('child', parent)
1153
transform.adjust_path('parent', root, parent)
1155
self.failUnlessExists(self.wt.abspath('parent/child'))
1156
self.assertEqual(1, transform.rename_count)
1158
def test_reuse_name(self):
1159
"""Avoid reusing the same limbo name for different files"""
1160
transform, root = self.get_transform()
1161
parent = transform.new_directory('parent', root)
1162
child1 = transform.new_directory('child', parent)
1164
child2 = transform.new_directory('child', parent)
1166
self.fail('Tranform tried to use the same limbo name twice')
1167
transform.adjust_path('child2', parent, child2)
1169
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1170
# child2 is put into top-level limbo because child1 has already
1171
# claimed the direct limbo path when child2 is created. There is no
1172
# advantage in renaming files once they're in top-level limbo, except
1174
self.assertEqual(2, transform.rename_count)
1176
def test_reuse_when_first_moved(self):
1177
"""Don't avoid direct paths when it is safe to use them"""
1178
transform, root = self.get_transform()
1179
parent = transform.new_directory('parent', root)
1180
child1 = transform.new_directory('child', parent)
1181
transform.adjust_path('child1', parent, child1)
1182
child2 = transform.new_directory('child', parent)
1184
# limbo/new-1 => parent
1185
self.assertEqual(1, transform.rename_count)
1187
def test_reuse_after_cancel(self):
1188
"""Don't avoid direct paths when it is safe to use them"""
1189
transform, root = self.get_transform()
1190
parent2 = transform.new_directory('parent2', root)
1191
child1 = transform.new_directory('child1', parent2)
1192
transform.cancel_creation(parent2)
1193
transform.create_directory(parent2)
1194
child2 = transform.new_directory('child1', parent2)
1195
transform.adjust_path('child2', parent2, child1)
1197
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1198
self.assertEqual(2, transform.rename_count)
1200
def test_finalize_order(self):
1201
"""Finalize must be done in child-to-parent order"""
1202
transform, root = self.get_transform()
1203
parent = transform.new_directory('parent', root)
1204
child = transform.new_directory('child', parent)
1206
transform.finalize()
1208
self.fail('Tried to remove parent before child1')
1210
def test_cancel_with_cancelled_child_should_succeed(self):
1211
transform, root = self.get_transform()
1212
parent = transform.new_directory('parent', root)
1213
child = transform.new_directory('child', parent)
1214
transform.cancel_creation(child)
1215
transform.cancel_creation(parent)
1216
transform.finalize()
1218
def test_rollback_on_directory_clash(self):
1220
wt = self.make_branch_and_tree('.')
1221
tt = TreeTransform(wt) # TreeTransform obtains write lock
1223
foo = tt.new_directory('foo', tt.root)
1224
tt.new_file('bar', foo, 'foobar')
1225
baz = tt.new_directory('baz', tt.root)
1226
tt.new_file('qux', baz, 'quux')
1227
# Ask for a rename 'foo' -> 'baz'
1228
tt.adjust_path('baz', tt.root, foo)
1229
# Lie to tt that we've already resolved all conflicts.
1230
tt.apply(no_conflicts=True)
1234
# The rename will fail because the target directory is not empty (but
1235
# raises FileExists anyway).
1236
err = self.assertRaises(errors.FileExists, tt_helper)
1237
self.assertContainsRe(str(err),
1238
"^File exists: .+/baz")
1240
def test_two_directories_clash(self):
1242
wt = self.make_branch_and_tree('.')
1243
tt = TreeTransform(wt) # TreeTransform obtains write lock
1245
foo_1 = tt.new_directory('foo', tt.root)
1246
tt.new_directory('bar', foo_1)
1247
# Adding the same directory with a different content
1248
foo_2 = tt.new_directory('foo', tt.root)
1249
tt.new_directory('baz', foo_2)
1250
# Lie to tt that we've already resolved all conflicts.
1251
tt.apply(no_conflicts=True)
1255
err = self.assertRaises(errors.FileExists, tt_helper)
1256
self.assertContainsRe(str(err),
1257
"^File exists: .+/foo")
1259
def test_two_directories_clash_finalize(self):
1261
wt = self.make_branch_and_tree('.')
1262
tt = TreeTransform(wt) # TreeTransform obtains write lock
1264
foo_1 = tt.new_directory('foo', tt.root)
1265
tt.new_directory('bar', foo_1)
1266
# Adding the same directory with a different content
1267
foo_2 = tt.new_directory('foo', tt.root)
1268
tt.new_directory('baz', foo_2)
1269
# Lie to tt that we've already resolved all conflicts.
1270
tt.apply(no_conflicts=True)
1274
err = self.assertRaises(errors.FileExists, tt_helper)
1275
self.assertContainsRe(str(err),
1276
"^File exists: .+/foo")
1278
def test_file_to_directory(self):
1279
wt = self.make_branch_and_tree('.')
1280
self.build_tree(['foo'])
1283
tt = TreeTransform(wt)
1284
self.addCleanup(tt.finalize)
1285
foo_trans_id = tt.trans_id_tree_path("foo")
1286
tt.delete_contents(foo_trans_id)
1287
tt.create_directory(foo_trans_id)
1288
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1289
tt.create_file(["aa\n"], bar_trans_id)
1290
tt.version_file("bar-1", bar_trans_id)
1292
self.failUnlessExists("foo/bar")
1295
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1300
changes = wt.changes_from(wt.basis_tree())
1301
self.assertFalse(changes.has_changed(), changes)
1303
def test_file_to_symlink(self):
1304
self.requireFeature(SymlinkFeature)
1305
wt = self.make_branch_and_tree('.')
1306
self.build_tree(['foo'])
1309
tt = TreeTransform(wt)
1310
self.addCleanup(tt.finalize)
1311
foo_trans_id = tt.trans_id_tree_path("foo")
1312
tt.delete_contents(foo_trans_id)
1313
tt.create_symlink("bar", foo_trans_id)
1315
self.failUnlessExists("foo")
1317
self.addCleanup(wt.unlock)
1318
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1321
def test_dir_to_file(self):
1322
wt = self.make_branch_and_tree('.')
1323
self.build_tree(['foo/', 'foo/bar'])
1324
wt.add(['foo', 'foo/bar'])
1326
tt = TreeTransform(wt)
1327
self.addCleanup(tt.finalize)
1328
foo_trans_id = tt.trans_id_tree_path("foo")
1329
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1330
tt.delete_contents(foo_trans_id)
1331
tt.delete_versioned(bar_trans_id)
1332
tt.create_file(["aa\n"], foo_trans_id)
1334
self.failUnlessExists("foo")
1336
self.addCleanup(wt.unlock)
1337
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1340
def test_dir_to_hardlink(self):
1341
self.requireFeature(HardlinkFeature)
1342
wt = self.make_branch_and_tree('.')
1343
self.build_tree(['foo/', 'foo/bar'])
1344
wt.add(['foo', 'foo/bar'])
1346
tt = TreeTransform(wt)
1347
self.addCleanup(tt.finalize)
1348
foo_trans_id = tt.trans_id_tree_path("foo")
1349
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1350
tt.delete_contents(foo_trans_id)
1351
tt.delete_versioned(bar_trans_id)
1352
self.build_tree(['baz'])
1353
tt.create_hardlink("baz", foo_trans_id)
1355
self.failUnlessExists("foo")
1356
self.failUnlessExists("baz")
1358
self.addCleanup(wt.unlock)
1359
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1362
def test_no_final_path(self):
1363
transform, root = self.get_transform()
1364
trans_id = transform.trans_id_file_id('foo')
1365
transform.create_file('bar', trans_id)
1366
transform.cancel_creation(trans_id)
1369
def test_create_from_tree(self):
1370
tree1 = self.make_branch_and_tree('tree1')
1371
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1372
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1373
tree2 = self.make_branch_and_tree('tree2')
1374
tt = TreeTransform(tree2)
1375
foo_trans_id = tt.create_path('foo', tt.root)
1376
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1377
bar_trans_id = tt.create_path('bar', tt.root)
1378
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1380
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1381
self.assertFileEqual('baz', 'tree2/bar')
1383
def test_create_from_tree_bytes(self):
1384
"""Provided lines are used instead of tree content."""
1385
tree1 = self.make_branch_and_tree('tree1')
1386
self.build_tree_contents([('tree1/foo', 'bar'),])
1387
tree1.add('foo', 'foo-id')
1388
tree2 = self.make_branch_and_tree('tree2')
1389
tt = TreeTransform(tree2)
1390
foo_trans_id = tt.create_path('foo', tt.root)
1391
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1393
self.assertFileEqual('qux', 'tree2/foo')
1395
def test_create_from_tree_symlink(self):
1396
self.requireFeature(SymlinkFeature)
1397
tree1 = self.make_branch_and_tree('tree1')
1398
os.symlink('bar', 'tree1/foo')
1399
tree1.add('foo', 'foo-id')
1400
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1401
foo_trans_id = tt.create_path('foo', tt.root)
1402
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1404
self.assertEqual('bar', os.readlink('tree2/foo'))
1407
class TransformGroup(object):
1409
def __init__(self, dirname, root_id):
1412
self.wt = BzrDir.create_standalone_workingtree(dirname)
1413
self.wt.set_root_id(root_id)
1414
self.b = self.wt.branch
1415
self.tt = TreeTransform(self.wt)
1416
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1419
def conflict_text(tree, merge):
1420
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1421
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1424
class TestTransformMerge(TestCaseInTempDir):
1426
def test_text_merge(self):
1427
root_id = generate_ids.gen_root_id()
1428
base = TransformGroup("base", root_id)
1429
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1430
base.tt.new_file('b', base.root, 'b1', 'b')
1431
base.tt.new_file('c', base.root, 'c', 'c')
1432
base.tt.new_file('d', base.root, 'd', 'd')
1433
base.tt.new_file('e', base.root, 'e', 'e')
1434
base.tt.new_file('f', base.root, 'f', 'f')
1435
base.tt.new_directory('g', base.root, 'g')
1436
base.tt.new_directory('h', base.root, 'h')
1438
other = TransformGroup("other", root_id)
1439
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1440
other.tt.new_file('b', other.root, 'b2', 'b')
1441
other.tt.new_file('c', other.root, 'c2', 'c')
1442
other.tt.new_file('d', other.root, 'd', 'd')
1443
other.tt.new_file('e', other.root, 'e2', 'e')
1444
other.tt.new_file('f', other.root, 'f', 'f')
1445
other.tt.new_file('g', other.root, 'g', 'g')
1446
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1447
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1449
this = TransformGroup("this", root_id)
1450
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1451
this.tt.new_file('b', this.root, 'b', 'b')
1452
this.tt.new_file('c', this.root, 'c', 'c')
1453
this.tt.new_file('d', this.root, 'd2', 'd')
1454
this.tt.new_file('e', this.root, 'e2', 'e')
1455
this.tt.new_file('f', this.root, 'f', 'f')
1456
this.tt.new_file('g', this.root, 'g', 'g')
1457
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1458
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1460
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1463
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1464
# three-way text conflict
1465
self.assertEqual(this.wt.get_file('b').read(),
1466
conflict_text('b', 'b2'))
1468
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1470
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1471
# Ambigious clean merge
1472
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1474
self.assertEqual(this.wt.get_file('f').read(), 'f')
1475
# Correct correct results when THIS == OTHER
1476
self.assertEqual(this.wt.get_file('g').read(), 'g')
1477
# Text conflict when THIS & OTHER are text and BASE is dir
1478
self.assertEqual(this.wt.get_file('h').read(),
1479
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1480
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1482
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1484
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1485
self.assertEqual(this.wt.get_file('i').read(),
1486
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1487
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1489
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1491
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1492
modified = ['a', 'b', 'c', 'h', 'i']
1493
merge_modified = this.wt.merge_modified()
1494
self.assertSubset(merge_modified, modified)
1495
self.assertEqual(len(merge_modified), len(modified))
1496
file(this.wt.id2abspath('a'), 'wb').write('booga')
1498
merge_modified = this.wt.merge_modified()
1499
self.assertSubset(merge_modified, modified)
1500
self.assertEqual(len(merge_modified), len(modified))
1504
def test_file_merge(self):
1505
self.requireFeature(SymlinkFeature)
1506
root_id = generate_ids.gen_root_id()
1507
base = TransformGroup("BASE", root_id)
1508
this = TransformGroup("THIS", root_id)
1509
other = TransformGroup("OTHER", root_id)
1510
for tg in this, base, other:
1511
tg.tt.new_directory('a', tg.root, 'a')
1512
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1513
tg.tt.new_file('c', tg.root, 'c', 'c')
1514
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1515
targets = ((base, 'base-e', 'base-f', None, None),
1516
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1517
(other, 'other-e', None, 'other-g', 'other-h'))
1518
for tg, e_target, f_target, g_target, h_target in targets:
1519
for link, target in (('e', e_target), ('f', f_target),
1520
('g', g_target), ('h', h_target)):
1521
if target is not None:
1522
tg.tt.new_symlink(link, tg.root, target, link)
1524
for tg in this, base, other:
1526
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1527
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1528
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1529
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1530
for suffix in ('THIS', 'BASE', 'OTHER'):
1531
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1532
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1533
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1534
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1535
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1536
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1537
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1538
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1539
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1540
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1541
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1542
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1543
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1544
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1546
def test_filename_merge(self):
1547
root_id = generate_ids.gen_root_id()
1548
base = TransformGroup("BASE", root_id)
1549
this = TransformGroup("THIS", root_id)
1550
other = TransformGroup("OTHER", root_id)
1551
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1552
for t in [base, this, other]]
1553
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1554
for t in [base, this, other]]
1555
base.tt.new_directory('c', base_a, 'c')
1556
this.tt.new_directory('c1', this_a, 'c')
1557
other.tt.new_directory('c', other_b, 'c')
1559
base.tt.new_directory('d', base_a, 'd')
1560
this.tt.new_directory('d1', this_b, 'd')
1561
other.tt.new_directory('d', other_a, 'd')
1563
base.tt.new_directory('e', base_a, 'e')
1564
this.tt.new_directory('e', this_a, 'e')
1565
other.tt.new_directory('e1', other_b, 'e')
1567
base.tt.new_directory('f', base_a, 'f')
1568
this.tt.new_directory('f1', this_b, 'f')
1569
other.tt.new_directory('f1', other_b, 'f')
1571
for tg in [this, base, other]:
1573
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1574
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1575
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1576
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1577
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1579
def test_filename_merge_conflicts(self):
1580
root_id = generate_ids.gen_root_id()
1581
base = TransformGroup("BASE", root_id)
1582
this = TransformGroup("THIS", root_id)
1583
other = TransformGroup("OTHER", root_id)
1584
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1585
for t in [base, this, other]]
1586
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1587
for t in [base, this, other]]
1589
base.tt.new_file('g', base_a, 'g', 'g')
1590
other.tt.new_file('g1', other_b, 'g1', 'g')
1592
base.tt.new_file('h', base_a, 'h', 'h')
1593
this.tt.new_file('h1', this_b, 'h1', 'h')
1595
base.tt.new_file('i', base.root, 'i', 'i')
1596
other.tt.new_directory('i1', this_b, 'i')
1598
for tg in [this, base, other]:
1600
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1602
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1603
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1604
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1605
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1606
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1607
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1608
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1611
class TestBuildTree(tests.TestCaseWithTransport):
1613
def test_build_tree_with_symlinks(self):
1614
self.requireFeature(SymlinkFeature)
1616
a = BzrDir.create_standalone_workingtree('a')
1618
file('a/foo/bar', 'wb').write('contents')
1619
os.symlink('a/foo/bar', 'a/foo/baz')
1620
a.add(['foo', 'foo/bar', 'foo/baz'])
1621
a.commit('initial commit')
1622
b = BzrDir.create_standalone_workingtree('b')
1623
basis = a.basis_tree()
1625
self.addCleanup(basis.unlock)
1626
build_tree(basis, b)
1627
self.assertIs(os.path.isdir('b/foo'), True)
1628
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1629
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1631
def test_build_with_references(self):
1632
tree = self.make_branch_and_tree('source',
1633
format='dirstate-with-subtree')
1634
subtree = self.make_branch_and_tree('source/subtree',
1635
format='dirstate-with-subtree')
1636
tree.add_reference(subtree)
1637
tree.commit('a revision')
1638
tree.branch.create_checkout('target')
1639
self.failUnlessExists('target')
1640
self.failUnlessExists('target/subtree')
1642
def test_file_conflict_handling(self):
1643
"""Ensure that when building trees, conflict handling is done"""
1644
source = self.make_branch_and_tree('source')
1645
target = self.make_branch_and_tree('target')
1646
self.build_tree(['source/file', 'target/file'])
1647
source.add('file', 'new-file')
1648
source.commit('added file')
1649
build_tree(source.basis_tree(), target)
1650
self.assertEqual([DuplicateEntry('Moved existing file to',
1651
'file.moved', 'file', None, 'new-file')],
1653
target2 = self.make_branch_and_tree('target2')
1654
target_file = file('target2/file', 'wb')
1656
source_file = file('source/file', 'rb')
1658
target_file.write(source_file.read())
1663
build_tree(source.basis_tree(), target2)
1664
self.assertEqual([], target2.conflicts())
1666
def test_symlink_conflict_handling(self):
1667
"""Ensure that when building trees, conflict handling is done"""
1668
self.requireFeature(SymlinkFeature)
1669
source = self.make_branch_and_tree('source')
1670
os.symlink('foo', 'source/symlink')
1671
source.add('symlink', 'new-symlink')
1672
source.commit('added file')
1673
target = self.make_branch_and_tree('target')
1674
os.symlink('bar', 'target/symlink')
1675
build_tree(source.basis_tree(), target)
1676
self.assertEqual([DuplicateEntry('Moved existing file to',
1677
'symlink.moved', 'symlink', None, 'new-symlink')],
1679
target = self.make_branch_and_tree('target2')
1680
os.symlink('foo', 'target2/symlink')
1681
build_tree(source.basis_tree(), target)
1682
self.assertEqual([], target.conflicts())
1684
def test_directory_conflict_handling(self):
1685
"""Ensure that when building trees, conflict handling is done"""
1686
source = self.make_branch_and_tree('source')
1687
target = self.make_branch_and_tree('target')
1688
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1689
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1690
source.commit('added file')
1691
build_tree(source.basis_tree(), target)
1692
self.assertEqual([], target.conflicts())
1693
self.failUnlessExists('target/dir1/file')
1695
# Ensure contents are merged
1696
target = self.make_branch_and_tree('target2')
1697
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1698
build_tree(source.basis_tree(), target)
1699
self.assertEqual([], target.conflicts())
1700
self.failUnlessExists('target2/dir1/file2')
1701
self.failUnlessExists('target2/dir1/file')
1703
# Ensure new contents are suppressed for existing branches
1704
target = self.make_branch_and_tree('target3')
1705
self.make_branch('target3/dir1')
1706
self.build_tree(['target3/dir1/file2'])
1707
build_tree(source.basis_tree(), target)
1708
self.failIfExists('target3/dir1/file')
1709
self.failUnlessExists('target3/dir1/file2')
1710
self.failUnlessExists('target3/dir1.diverted/file')
1711
self.assertEqual([DuplicateEntry('Diverted to',
1712
'dir1.diverted', 'dir1', 'new-dir1', None)],
1715
target = self.make_branch_and_tree('target4')
1716
self.build_tree(['target4/dir1/'])
1717
self.make_branch('target4/dir1/file')
1718
build_tree(source.basis_tree(), target)
1719
self.failUnlessExists('target4/dir1/file')
1720
self.assertEqual('directory', file_kind('target4/dir1/file'))
1721
self.failUnlessExists('target4/dir1/file.diverted')
1722
self.assertEqual([DuplicateEntry('Diverted to',
1723
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1726
def test_mixed_conflict_handling(self):
1727
"""Ensure that when building trees, conflict handling is done"""
1728
source = self.make_branch_and_tree('source')
1729
target = self.make_branch_and_tree('target')
1730
self.build_tree(['source/name', 'target/name/'])
1731
source.add('name', 'new-name')
1732
source.commit('added file')
1733
build_tree(source.basis_tree(), target)
1734
self.assertEqual([DuplicateEntry('Moved existing file to',
1735
'name.moved', 'name', None, 'new-name')], target.conflicts())
1737
def test_raises_in_populated(self):
1738
source = self.make_branch_and_tree('source')
1739
self.build_tree(['source/name'])
1741
source.commit('added name')
1742
target = self.make_branch_and_tree('target')
1743
self.build_tree(['target/name'])
1745
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1746
build_tree, source.basis_tree(), target)
1748
def test_build_tree_rename_count(self):
1749
source = self.make_branch_and_tree('source')
1750
self.build_tree(['source/file1', 'source/dir1/'])
1751
source.add(['file1', 'dir1'])
1752
source.commit('add1')
1753
target1 = self.make_branch_and_tree('target1')
1754
transform_result = build_tree(source.basis_tree(), target1)
1755
self.assertEqual(2, transform_result.rename_count)
1757
self.build_tree(['source/dir1/file2'])
1758
source.add(['dir1/file2'])
1759
source.commit('add3')
1760
target2 = self.make_branch_and_tree('target2')
1761
transform_result = build_tree(source.basis_tree(), target2)
1762
# children of non-root directories should not be renamed
1763
self.assertEqual(2, transform_result.rename_count)
1765
def create_ab_tree(self):
1766
"""Create a committed test tree with two files"""
1767
source = self.make_branch_and_tree('source')
1768
self.build_tree_contents([('source/file1', 'A')])
1769
self.build_tree_contents([('source/file2', 'B')])
1770
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1771
source.commit('commit files')
1773
self.addCleanup(source.unlock)
1776
def test_build_tree_accelerator_tree(self):
1777
source = self.create_ab_tree()
1778
self.build_tree_contents([('source/file2', 'C')])
1780
real_source_get_file = source.get_file
1781
def get_file(file_id, path=None):
1782
calls.append(file_id)
1783
return real_source_get_file(file_id, path)
1784
source.get_file = get_file
1785
target = self.make_branch_and_tree('target')
1786
revision_tree = source.basis_tree()
1787
revision_tree.lock_read()
1788
self.addCleanup(revision_tree.unlock)
1789
build_tree(revision_tree, target, source)
1790
self.assertEqual(['file1-id'], calls)
1792
self.addCleanup(target.unlock)
1793
self.assertEqual([], list(target.iter_changes(revision_tree)))
1795
def test_build_tree_accelerator_tree_missing_file(self):
1796
source = self.create_ab_tree()
1797
os.unlink('source/file1')
1798
source.remove(['file2'])
1799
target = self.make_branch_and_tree('target')
1800
revision_tree = source.basis_tree()
1801
revision_tree.lock_read()
1802
self.addCleanup(revision_tree.unlock)
1803
build_tree(revision_tree, target, source)
1805
self.addCleanup(target.unlock)
1806
self.assertEqual([], list(target.iter_changes(revision_tree)))
1808
def test_build_tree_accelerator_wrong_kind(self):
1809
self.requireFeature(SymlinkFeature)
1810
source = self.make_branch_and_tree('source')
1811
self.build_tree_contents([('source/file1', '')])
1812
self.build_tree_contents([('source/file2', '')])
1813
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1814
source.commit('commit files')
1815
os.unlink('source/file2')
1816
self.build_tree_contents([('source/file2/', 'C')])
1817
os.unlink('source/file1')
1818
os.symlink('file2', 'source/file1')
1820
real_source_get_file = source.get_file
1821
def get_file(file_id, path=None):
1822
calls.append(file_id)
1823
return real_source_get_file(file_id, path)
1824
source.get_file = get_file
1825
target = self.make_branch_and_tree('target')
1826
revision_tree = source.basis_tree()
1827
revision_tree.lock_read()
1828
self.addCleanup(revision_tree.unlock)
1829
build_tree(revision_tree, target, source)
1830
self.assertEqual([], calls)
1832
self.addCleanup(target.unlock)
1833
self.assertEqual([], list(target.iter_changes(revision_tree)))
1835
def test_build_tree_hardlink(self):
1836
self.requireFeature(HardlinkFeature)
1837
source = self.create_ab_tree()
1838
target = self.make_branch_and_tree('target')
1839
revision_tree = source.basis_tree()
1840
revision_tree.lock_read()
1841
self.addCleanup(revision_tree.unlock)
1842
build_tree(revision_tree, target, source, hardlink=True)
1844
self.addCleanup(target.unlock)
1845
self.assertEqual([], list(target.iter_changes(revision_tree)))
1846
source_stat = os.stat('source/file1')
1847
target_stat = os.stat('target/file1')
1848
self.assertEqual(source_stat, target_stat)
1850
# Explicitly disallowing hardlinks should prevent them.
1851
target2 = self.make_branch_and_tree('target2')
1852
build_tree(revision_tree, target2, source, hardlink=False)
1854
self.addCleanup(target2.unlock)
1855
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1856
source_stat = os.stat('source/file1')
1857
target2_stat = os.stat('target2/file1')
1858
self.assertNotEqual(source_stat, target2_stat)
1860
def test_build_tree_accelerator_tree_moved(self):
1861
source = self.make_branch_and_tree('source')
1862
self.build_tree_contents([('source/file1', 'A')])
1863
source.add(['file1'], ['file1-id'])
1864
source.commit('commit files')
1865
source.rename_one('file1', 'file2')
1867
self.addCleanup(source.unlock)
1868
target = self.make_branch_and_tree('target')
1869
revision_tree = source.basis_tree()
1870
revision_tree.lock_read()
1871
self.addCleanup(revision_tree.unlock)
1872
build_tree(revision_tree, target, source)
1874
self.addCleanup(target.unlock)
1875
self.assertEqual([], list(target.iter_changes(revision_tree)))
1877
def test_build_tree_hardlinks_preserve_execute(self):
1878
self.requireFeature(HardlinkFeature)
1879
source = self.create_ab_tree()
1880
tt = TreeTransform(source)
1881
trans_id = tt.trans_id_tree_file_id('file1-id')
1882
tt.set_executability(True, trans_id)
1884
self.assertTrue(source.is_executable('file1-id'))
1885
target = self.make_branch_and_tree('target')
1886
revision_tree = source.basis_tree()
1887
revision_tree.lock_read()
1888
self.addCleanup(revision_tree.unlock)
1889
build_tree(revision_tree, target, source, hardlink=True)
1891
self.addCleanup(target.unlock)
1892
self.assertEqual([], list(target.iter_changes(revision_tree)))
1893
self.assertTrue(source.is_executable('file1-id'))
1895
def install_rot13_content_filter(self, pattern):
1896
original_registry = filters._reset_registry()
1897
def restore_registry():
1898
filters._reset_registry(original_registry)
1899
self.addCleanup(restore_registry)
1900
def rot13(chunks, context=None):
1901
return [''.join(chunks).encode('rot13')]
1902
rot13filter = filters.ContentFilter(rot13, rot13)
1903
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1904
os.mkdir(self.test_home_dir + '/.bazaar')
1905
rules_filename = self.test_home_dir + '/.bazaar/rules'
1906
f = open(rules_filename, 'wb')
1907
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1909
def uninstall_rules():
1910
os.remove(rules_filename)
1912
self.addCleanup(uninstall_rules)
1915
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1916
"""build_tree will not hardlink files that have content filtering rules
1917
applied to them (but will still hardlink other files from the same tree
1920
self.requireFeature(HardlinkFeature)
1921
self.install_rot13_content_filter('file1')
1922
source = self.create_ab_tree()
1923
target = self.make_branch_and_tree('target')
1924
revision_tree = source.basis_tree()
1925
revision_tree.lock_read()
1926
self.addCleanup(revision_tree.unlock)
1927
build_tree(revision_tree, target, source, hardlink=True)
1929
self.addCleanup(target.unlock)
1930
self.assertEqual([], list(target.iter_changes(revision_tree)))
1931
source_stat = os.stat('source/file1')
1932
target_stat = os.stat('target/file1')
1933
self.assertNotEqual(source_stat, target_stat)
1934
source_stat = os.stat('source/file2')
1935
target_stat = os.stat('target/file2')
1936
self.assertEqualStat(source_stat, target_stat)
1938
def test_case_insensitive_build_tree_inventory(self):
1939
if (tests.CaseInsensitiveFilesystemFeature.available()
1940
or tests.CaseInsCasePresFilenameFeature.available()):
1941
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1942
source = self.make_branch_and_tree('source')
1943
self.build_tree(['source/file', 'source/FILE'])
1944
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1945
source.commit('added files')
1946
# Don't try this at home, kids!
1947
# Force the tree to report that it is case insensitive
1948
target = self.make_branch_and_tree('target')
1949
target.case_sensitive = False
1950
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1951
self.assertEqual('file.moved', target.id2path('lower-id'))
1952
self.assertEqual('FILE', target.id2path('upper-id'))
1955
class TestCommitTransform(tests.TestCaseWithTransport):
1957
def get_branch(self):
1958
tree = self.make_branch_and_tree('tree')
1960
self.addCleanup(tree.unlock)
1961
tree.commit('empty commit')
1964
def get_branch_and_transform(self):
1965
branch = self.get_branch()
1966
tt = TransformPreview(branch.basis_tree())
1967
self.addCleanup(tt.finalize)
1970
def test_commit_wrong_basis(self):
1971
branch = self.get_branch()
1972
basis = branch.repository.revision_tree(
1973
_mod_revision.NULL_REVISION)
1974
tt = TransformPreview(basis)
1975
self.addCleanup(tt.finalize)
1976
e = self.assertRaises(ValueError, tt.commit, branch, '')
1977
self.assertEqual('TreeTransform not based on branch basis: null:',
1980
def test_empy_commit(self):
1981
branch, tt = self.get_branch_and_transform()
1982
rev = tt.commit(branch, 'my message')
1983
self.assertEqual(2, branch.revno())
1984
repo = branch.repository
1985
self.assertEqual('my message', repo.get_revision(rev).message)
1987
def test_merge_parents(self):
1988
branch, tt = self.get_branch_and_transform()
1989
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
1990
self.assertEqual(['rev1b', 'rev1c'],
1991
branch.basis_tree().get_parent_ids()[1:])
1993
def test_first_commit(self):
1994
branch = self.make_branch('branch')
1996
self.addCleanup(branch.unlock)
1997
tt = TransformPreview(branch.basis_tree())
1998
self.addCleanup(tt.finalize)
1999
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2000
rev = tt.commit(branch, 'my message')
2001
self.assertEqual([], branch.basis_tree().get_parent_ids())
2002
self.assertNotEqual(_mod_revision.NULL_REVISION,
2003
branch.last_revision())
2005
def test_first_commit_with_merge_parents(self):
2006
branch = self.make_branch('branch')
2008
self.addCleanup(branch.unlock)
2009
tt = TransformPreview(branch.basis_tree())
2010
self.addCleanup(tt.finalize)
2011
e = self.assertRaises(ValueError, tt.commit, branch,
2012
'my message', ['rev1b-id'])
2013
self.assertEqual('Cannot supply merge parents for first commit.',
2015
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2017
def test_add_files(self):
2018
branch, tt = self.get_branch_and_transform()
2019
tt.new_file('file', tt.root, 'contents', 'file-id')
2020
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2021
if SymlinkFeature.available():
2022
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2023
rev = tt.commit(branch, 'message')
2024
tree = branch.basis_tree()
2025
self.assertEqual('file', tree.id2path('file-id'))
2026
self.assertEqual('contents', tree.get_file_text('file-id'))
2027
self.assertEqual('dir', tree.id2path('dir-id'))
2028
if SymlinkFeature.available():
2029
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2030
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2032
def test_add_unversioned(self):
2033
branch, tt = self.get_branch_and_transform()
2034
tt.new_file('file', tt.root, 'contents')
2035
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2036
'message', strict=True)
2038
def test_modify_strict(self):
2039
branch, tt = self.get_branch_and_transform()
2040
tt.new_file('file', tt.root, 'contents', 'file-id')
2041
tt.commit(branch, 'message', strict=True)
2042
tt = TransformPreview(branch.basis_tree())
2043
self.addCleanup(tt.finalize)
2044
trans_id = tt.trans_id_file_id('file-id')
2045
tt.delete_contents(trans_id)
2046
tt.create_file('contents', trans_id)
2047
tt.commit(branch, 'message', strict=True)
2049
def test_commit_malformed(self):
2050
"""Committing a malformed transform should raise an exception.
2052
In this case, we are adding a file without adding its parent.
2054
branch, tt = self.get_branch_and_transform()
2055
parent_id = tt.trans_id_file_id('parent-id')
2056
tt.new_file('file', parent_id, 'contents', 'file-id')
2057
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2061
class MockTransform(object):
2063
def has_named_child(self, by_parent, parent_id, name):
2064
for child_id in by_parent[parent_id]:
2068
elif name == "name.~%s~" % child_id:
2073
class MockEntry(object):
2075
object.__init__(self)
2079
class TestGetBackupName(TestCase):
2080
def test_get_backup_name(self):
2081
tt = MockTransform()
2082
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
2083
self.assertEqual(name, 'name.~1~')
2084
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
2085
self.assertEqual(name, 'name.~2~')
2086
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
2087
self.assertEqual(name, 'name.~1~')
2088
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
2089
self.assertEqual(name, 'name.~1~')
2090
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2091
self.assertEqual(name, 'name.~4~')
2094
class TestFileMover(tests.TestCaseWithTransport):
2096
def test_file_mover(self):
2097
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2098
mover = _FileMover()
2099
mover.rename('a', 'q')
2100
self.failUnlessExists('q')
2101
self.failIfExists('a')
2102
self.failUnlessExists('q/b')
2103
self.failUnlessExists('c')
2104
self.failUnlessExists('c/d')
2106
def test_pre_delete_rollback(self):
2107
self.build_tree(['a/'])
2108
mover = _FileMover()
2109
mover.pre_delete('a', 'q')
2110
self.failUnlessExists('q')
2111
self.failIfExists('a')
2113
self.failIfExists('q')
2114
self.failUnlessExists('a')
2116
def test_apply_deletions(self):
2117
self.build_tree(['a/', 'b/'])
2118
mover = _FileMover()
2119
mover.pre_delete('a', 'q')
2120
mover.pre_delete('b', 'r')
2121
self.failUnlessExists('q')
2122
self.failUnlessExists('r')
2123
self.failIfExists('a')
2124
self.failIfExists('b')
2125
mover.apply_deletions()
2126
self.failIfExists('q')
2127
self.failIfExists('r')
2128
self.failIfExists('a')
2129
self.failIfExists('b')
2131
def test_file_mover_rollback(self):
2132
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2133
mover = _FileMover()
2134
mover.rename('c/d', 'c/f')
2135
mover.rename('c/e', 'c/d')
2137
mover.rename('a', 'c')
2138
except errors.FileExists, e:
2140
self.failUnlessExists('a')
2141
self.failUnlessExists('c/d')
2144
class Bogus(Exception):
2148
class TestTransformRollback(tests.TestCaseWithTransport):
2150
class ExceptionFileMover(_FileMover):
2152
def __init__(self, bad_source=None, bad_target=None):
2153
_FileMover.__init__(self)
2154
self.bad_source = bad_source
2155
self.bad_target = bad_target
2157
def rename(self, source, target):
2158
if (self.bad_source is not None and
2159
source.endswith(self.bad_source)):
2161
elif (self.bad_target is not None and
2162
target.endswith(self.bad_target)):
2165
_FileMover.rename(self, source, target)
2167
def test_rollback_rename(self):
2168
tree = self.make_branch_and_tree('.')
2169
self.build_tree(['a/', 'a/b'])
2170
tt = TreeTransform(tree)
2171
self.addCleanup(tt.finalize)
2172
a_id = tt.trans_id_tree_path('a')
2173
tt.adjust_path('c', tt.root, a_id)
2174
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2175
self.assertRaises(Bogus, tt.apply,
2176
_mover=self.ExceptionFileMover(bad_source='a'))
2177
self.failUnlessExists('a')
2178
self.failUnlessExists('a/b')
2180
self.failUnlessExists('c')
2181
self.failUnlessExists('c/d')
2183
def test_rollback_rename_into_place(self):
2184
tree = self.make_branch_and_tree('.')
2185
self.build_tree(['a/', 'a/b'])
2186
tt = TreeTransform(tree)
2187
self.addCleanup(tt.finalize)
2188
a_id = tt.trans_id_tree_path('a')
2189
tt.adjust_path('c', tt.root, a_id)
2190
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2191
self.assertRaises(Bogus, tt.apply,
2192
_mover=self.ExceptionFileMover(bad_target='c/d'))
2193
self.failUnlessExists('a')
2194
self.failUnlessExists('a/b')
2196
self.failUnlessExists('c')
2197
self.failUnlessExists('c/d')
2199
def test_rollback_deletion(self):
2200
tree = self.make_branch_and_tree('.')
2201
self.build_tree(['a/', 'a/b'])
2202
tt = TreeTransform(tree)
2203
self.addCleanup(tt.finalize)
2204
a_id = tt.trans_id_tree_path('a')
2205
tt.delete_contents(a_id)
2206
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2207
self.assertRaises(Bogus, tt.apply,
2208
_mover=self.ExceptionFileMover(bad_target='d'))
2209
self.failUnlessExists('a')
2210
self.failUnlessExists('a/b')
2212
def test_resolve_no_parent(self):
2213
wt = self.make_branch_and_tree('.')
2214
tt = TreeTransform(wt)
2215
self.addCleanup(tt.finalize)
2216
parent = tt.trans_id_file_id('parent-id')
2217
tt.new_file('file', parent, 'Contents')
2218
resolve_conflicts(tt)
2221
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2222
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2224
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2225
('', ''), ('directory', 'directory'), (False, None))
2228
class TestTransformPreview(tests.TestCaseWithTransport):
2230
def create_tree(self):
2231
tree = self.make_branch_and_tree('.')
2232
self.build_tree_contents([('a', 'content 1')])
2233
tree.set_root_id('TREE_ROOT')
2234
tree.add('a', 'a-id')
2235
tree.commit('rev1', rev_id='rev1')
2236
return tree.branch.repository.revision_tree('rev1')
2238
def get_empty_preview(self):
2239
repository = self.make_repository('repo')
2240
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2241
preview = TransformPreview(tree)
2242
self.addCleanup(preview.finalize)
2245
def test_transform_preview(self):
2246
revision_tree = self.create_tree()
2247
preview = TransformPreview(revision_tree)
2248
self.addCleanup(preview.finalize)
2250
def test_transform_preview_tree(self):
2251
revision_tree = self.create_tree()
2252
preview = TransformPreview(revision_tree)
2253
self.addCleanup(preview.finalize)
2254
preview.get_preview_tree()
2256
def test_transform_new_file(self):
2257
revision_tree = self.create_tree()
2258
preview = TransformPreview(revision_tree)
2259
self.addCleanup(preview.finalize)
2260
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2261
preview_tree = preview.get_preview_tree()
2262
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2264
preview_tree.get_file('file2-id').read(), 'content B\n')
2266
def test_diff_preview_tree(self):
2267
revision_tree = self.create_tree()
2268
preview = TransformPreview(revision_tree)
2269
self.addCleanup(preview.finalize)
2270
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2271
preview_tree = preview.get_preview_tree()
2273
show_diff_trees(revision_tree, preview_tree, out)
2274
lines = out.getvalue().splitlines()
2275
self.assertEqual(lines[0], "=== added file 'file2'")
2276
# 3 lines of diff administrivia
2277
self.assertEqual(lines[4], "+content B")
2279
def test_transform_conflicts(self):
2280
revision_tree = self.create_tree()
2281
preview = TransformPreview(revision_tree)
2282
self.addCleanup(preview.finalize)
2283
preview.new_file('a', preview.root, 'content 2')
2284
resolve_conflicts(preview)
2285
trans_id = preview.trans_id_file_id('a-id')
2286
self.assertEqual('a.moved', preview.final_name(trans_id))
2288
def get_tree_and_preview_tree(self):
2289
revision_tree = self.create_tree()
2290
preview = TransformPreview(revision_tree)
2291
self.addCleanup(preview.finalize)
2292
a_trans_id = preview.trans_id_file_id('a-id')
2293
preview.delete_contents(a_trans_id)
2294
preview.create_file('b content', a_trans_id)
2295
preview_tree = preview.get_preview_tree()
2296
return revision_tree, preview_tree
2298
def test_iter_changes(self):
2299
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2300
root = revision_tree.inventory.root.file_id
2301
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2302
(root, root), ('a', 'a'), ('file', 'file'),
2304
list(preview_tree.iter_changes(revision_tree)))
2306
def test_include_unchanged_succeeds(self):
2307
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2308
changes = preview_tree.iter_changes(revision_tree,
2309
include_unchanged=True)
2310
root = revision_tree.inventory.root.file_id
2312
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2314
def test_specific_files(self):
2315
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2316
changes = preview_tree.iter_changes(revision_tree,
2317
specific_files=[''])
2318
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2320
def test_want_unversioned(self):
2321
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2322
changes = preview_tree.iter_changes(revision_tree,
2323
want_unversioned=True)
2324
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2326
def test_ignore_extra_trees_no_specific_files(self):
2327
# extra_trees is harmless without specific_files, so we'll silently
2328
# accept it, even though we won't use it.
2329
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2330
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2332
def test_ignore_require_versioned_no_specific_files(self):
2333
# require_versioned is meaningless without specific_files.
2334
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2335
preview_tree.iter_changes(revision_tree, require_versioned=False)
2337
def test_ignore_pb(self):
2338
# pb could be supported, but TT.iter_changes doesn't support it.
2339
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2340
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2342
def test_kind(self):
2343
revision_tree = self.create_tree()
2344
preview = TransformPreview(revision_tree)
2345
self.addCleanup(preview.finalize)
2346
preview.new_file('file', preview.root, 'contents', 'file-id')
2347
preview.new_directory('directory', preview.root, 'dir-id')
2348
preview_tree = preview.get_preview_tree()
2349
self.assertEqual('file', preview_tree.kind('file-id'))
2350
self.assertEqual('directory', preview_tree.kind('dir-id'))
2352
def test_get_file_mtime(self):
2353
preview = self.get_empty_preview()
2354
file_trans_id = preview.new_file('file', preview.root, 'contents',
2356
limbo_path = preview._limbo_name(file_trans_id)
2357
preview_tree = preview.get_preview_tree()
2358
self.assertEqual(os.stat(limbo_path).st_mtime,
2359
preview_tree.get_file_mtime('file-id'))
2361
def test_get_file_mtime_renamed(self):
2362
work_tree = self.make_branch_and_tree('tree')
2363
self.build_tree(['tree/file'])
2364
work_tree.add('file', 'file-id')
2365
preview = TransformPreview(work_tree)
2366
self.addCleanup(preview.finalize)
2367
file_trans_id = preview.trans_id_tree_file_id('file-id')
2368
preview.adjust_path('renamed', preview.root, file_trans_id)
2369
preview_tree = preview.get_preview_tree()
2370
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2371
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2373
def test_get_file(self):
2374
preview = self.get_empty_preview()
2375
preview.new_file('file', preview.root, 'contents', 'file-id')
2376
preview_tree = preview.get_preview_tree()
2377
tree_file = preview_tree.get_file('file-id')
2379
self.assertEqual('contents', tree_file.read())
2383
def test_get_symlink_target(self):
2384
self.requireFeature(SymlinkFeature)
2385
preview = self.get_empty_preview()
2386
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2387
preview_tree = preview.get_preview_tree()
2388
self.assertEqual('target',
2389
preview_tree.get_symlink_target('symlink-id'))
2391
def test_all_file_ids(self):
2392
tree = self.make_branch_and_tree('tree')
2393
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2394
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2395
preview = TransformPreview(tree)
2396
self.addCleanup(preview.finalize)
2397
preview.unversion_file(preview.trans_id_file_id('b-id'))
2398
c_trans_id = preview.trans_id_file_id('c-id')
2399
preview.unversion_file(c_trans_id)
2400
preview.version_file('c-id', c_trans_id)
2401
preview_tree = preview.get_preview_tree()
2402
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2403
preview_tree.all_file_ids())
2405
def test_path2id_deleted_unchanged(self):
2406
tree = self.make_branch_and_tree('tree')
2407
self.build_tree(['tree/unchanged', 'tree/deleted'])
2408
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2409
preview = TransformPreview(tree)
2410
self.addCleanup(preview.finalize)
2411
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2412
preview_tree = preview.get_preview_tree()
2413
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2414
self.assertIs(None, preview_tree.path2id('deleted'))
2416
def test_path2id_created(self):
2417
tree = self.make_branch_and_tree('tree')
2418
self.build_tree(['tree/unchanged'])
2419
tree.add(['unchanged'], ['unchanged-id'])
2420
preview = TransformPreview(tree)
2421
self.addCleanup(preview.finalize)
2422
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2423
'contents', 'new-id')
2424
preview_tree = preview.get_preview_tree()
2425
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2427
def test_path2id_moved(self):
2428
tree = self.make_branch_and_tree('tree')
2429
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2430
tree.add(['old_parent', 'old_parent/child'],
2431
['old_parent-id', 'child-id'])
2432
preview = TransformPreview(tree)
2433
self.addCleanup(preview.finalize)
2434
new_parent = preview.new_directory('new_parent', preview.root,
2436
preview.adjust_path('child', new_parent,
2437
preview.trans_id_file_id('child-id'))
2438
preview_tree = preview.get_preview_tree()
2439
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2440
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2442
def test_path2id_renamed_parent(self):
2443
tree = self.make_branch_and_tree('tree')
2444
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2445
tree.add(['old_name', 'old_name/child'],
2446
['parent-id', 'child-id'])
2447
preview = TransformPreview(tree)
2448
self.addCleanup(preview.finalize)
2449
preview.adjust_path('new_name', preview.root,
2450
preview.trans_id_file_id('parent-id'))
2451
preview_tree = preview.get_preview_tree()
2452
self.assertIs(None, preview_tree.path2id('old_name/child'))
2453
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2455
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2456
preview_tree = tt.get_preview_tree()
2457
preview_result = list(preview_tree.iter_entries_by_dir(
2461
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2462
self.assertEqual(actual_result, preview_result)
2464
def test_iter_entries_by_dir_new(self):
2465
tree = self.make_branch_and_tree('tree')
2466
tt = TreeTransform(tree)
2467
tt.new_file('new', tt.root, 'contents', 'new-id')
2468
self.assertMatchingIterEntries(tt)
2470
def test_iter_entries_by_dir_deleted(self):
2471
tree = self.make_branch_and_tree('tree')
2472
self.build_tree(['tree/deleted'])
2473
tree.add('deleted', 'deleted-id')
2474
tt = TreeTransform(tree)
2475
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2476
self.assertMatchingIterEntries(tt)
2478
def test_iter_entries_by_dir_unversioned(self):
2479
tree = self.make_branch_and_tree('tree')
2480
self.build_tree(['tree/removed'])
2481
tree.add('removed', 'removed-id')
2482
tt = TreeTransform(tree)
2483
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2484
self.assertMatchingIterEntries(tt)
2486
def test_iter_entries_by_dir_moved(self):
2487
tree = self.make_branch_and_tree('tree')
2488
self.build_tree(['tree/moved', 'tree/new_parent/'])
2489
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2490
tt = TreeTransform(tree)
2491
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2492
tt.trans_id_file_id('moved-id'))
2493
self.assertMatchingIterEntries(tt)
2495
def test_iter_entries_by_dir_specific_file_ids(self):
2496
tree = self.make_branch_and_tree('tree')
2497
tree.set_root_id('tree-root-id')
2498
self.build_tree(['tree/parent/', 'tree/parent/child'])
2499
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2500
tt = TreeTransform(tree)
2501
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2503
def test_symlink_content_summary(self):
2504
self.requireFeature(SymlinkFeature)
2505
preview = self.get_empty_preview()
2506
preview.new_symlink('path', preview.root, 'target', 'path-id')
2507
summary = preview.get_preview_tree().path_content_summary('path')
2508
self.assertEqual(('symlink', None, None, 'target'), summary)
2510
def test_missing_content_summary(self):
2511
preview = self.get_empty_preview()
2512
summary = preview.get_preview_tree().path_content_summary('path')
2513
self.assertEqual(('missing', None, None, None), summary)
2515
def test_deleted_content_summary(self):
2516
tree = self.make_branch_and_tree('tree')
2517
self.build_tree(['tree/path/'])
2519
preview = TransformPreview(tree)
2520
self.addCleanup(preview.finalize)
2521
preview.delete_contents(preview.trans_id_tree_path('path'))
2522
summary = preview.get_preview_tree().path_content_summary('path')
2523
self.assertEqual(('missing', None, None, None), summary)
2525
def test_file_content_summary_executable(self):
2526
preview = self.get_empty_preview()
2527
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2528
preview.set_executability(True, path_id)
2529
summary = preview.get_preview_tree().path_content_summary('path')
2530
self.assertEqual(4, len(summary))
2531
self.assertEqual('file', summary[0])
2532
# size must be known
2533
self.assertEqual(len('contents'), summary[1])
2535
self.assertEqual(True, summary[2])
2536
# will not have hash (not cheap to determine)
2537
self.assertIs(None, summary[3])
2539
def test_change_executability(self):
2540
tree = self.make_branch_and_tree('tree')
2541
self.build_tree(['tree/path'])
2543
preview = TransformPreview(tree)
2544
self.addCleanup(preview.finalize)
2545
path_id = preview.trans_id_tree_path('path')
2546
preview.set_executability(True, path_id)
2547
summary = preview.get_preview_tree().path_content_summary('path')
2548
self.assertEqual(True, summary[2])
2550
def test_file_content_summary_non_exec(self):
2551
preview = self.get_empty_preview()
2552
preview.new_file('path', preview.root, 'contents', 'path-id')
2553
summary = preview.get_preview_tree().path_content_summary('path')
2554
self.assertEqual(4, len(summary))
2555
self.assertEqual('file', summary[0])
2556
# size must be known
2557
self.assertEqual(len('contents'), summary[1])
2559
self.assertEqual(False, summary[2])
2560
# will not have hash (not cheap to determine)
2561
self.assertIs(None, summary[3])
2563
def test_dir_content_summary(self):
2564
preview = self.get_empty_preview()
2565
preview.new_directory('path', preview.root, 'path-id')
2566
summary = preview.get_preview_tree().path_content_summary('path')
2567
self.assertEqual(('directory', None, None, None), summary)
2569
def test_tree_content_summary(self):
2570
preview = self.get_empty_preview()
2571
path = preview.new_directory('path', preview.root, 'path-id')
2572
preview.set_tree_reference('rev-1', path)
2573
summary = preview.get_preview_tree().path_content_summary('path')
2574
self.assertEqual(4, len(summary))
2575
self.assertEqual('tree-reference', summary[0])
2577
def test_annotate(self):
2578
tree = self.make_branch_and_tree('tree')
2579
self.build_tree_contents([('tree/file', 'a\n')])
2580
tree.add('file', 'file-id')
2581
tree.commit('a', rev_id='one')
2582
self.build_tree_contents([('tree/file', 'a\nb\n')])
2583
preview = TransformPreview(tree)
2584
self.addCleanup(preview.finalize)
2585
file_trans_id = preview.trans_id_file_id('file-id')
2586
preview.delete_contents(file_trans_id)
2587
preview.create_file('a\nb\nc\n', file_trans_id)
2588
preview_tree = preview.get_preview_tree()
2594
annotation = preview_tree.annotate_iter('file-id', 'me:')
2595
self.assertEqual(expected, annotation)
2597
def test_annotate_missing(self):
2598
preview = self.get_empty_preview()
2599
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2600
preview_tree = preview.get_preview_tree()
2606
annotation = preview_tree.annotate_iter('file-id', 'me:')
2607
self.assertEqual(expected, annotation)
2609
def test_annotate_rename(self):
2610
tree = self.make_branch_and_tree('tree')
2611
self.build_tree_contents([('tree/file', 'a\n')])
2612
tree.add('file', 'file-id')
2613
tree.commit('a', rev_id='one')
2614
preview = TransformPreview(tree)
2615
self.addCleanup(preview.finalize)
2616
file_trans_id = preview.trans_id_file_id('file-id')
2617
preview.adjust_path('newname', preview.root, file_trans_id)
2618
preview_tree = preview.get_preview_tree()
2622
annotation = preview_tree.annotate_iter('file-id', 'me:')
2623
self.assertEqual(expected, annotation)
2625
def test_annotate_deleted(self):
2626
tree = self.make_branch_and_tree('tree')
2627
self.build_tree_contents([('tree/file', 'a\n')])
2628
tree.add('file', 'file-id')
2629
tree.commit('a', rev_id='one')
2630
self.build_tree_contents([('tree/file', 'a\nb\n')])
2631
preview = TransformPreview(tree)
2632
self.addCleanup(preview.finalize)
2633
file_trans_id = preview.trans_id_file_id('file-id')
2634
preview.delete_contents(file_trans_id)
2635
preview_tree = preview.get_preview_tree()
2636
annotation = preview_tree.annotate_iter('file-id', 'me:')
2637
self.assertIs(None, annotation)
2639
def test_stored_kind(self):
2640
preview = self.get_empty_preview()
2641
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2642
preview_tree = preview.get_preview_tree()
2643
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2645
def test_is_executable(self):
2646
preview = self.get_empty_preview()
2647
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2648
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2649
preview_tree = preview.get_preview_tree()
2650
self.assertEqual(True, preview_tree.is_executable('file-id'))
2652
def test_get_set_parent_ids(self):
2653
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2654
self.assertEqual([], preview_tree.get_parent_ids())
2655
preview_tree.set_parent_ids(['rev-1'])
2656
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2658
def test_plan_file_merge(self):
2659
work_a = self.make_branch_and_tree('wta')
2660
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2661
work_a.add('file', 'file-id')
2662
base_id = work_a.commit('base version')
2663
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2664
preview = TransformPreview(work_a)
2665
self.addCleanup(preview.finalize)
2666
trans_id = preview.trans_id_file_id('file-id')
2667
preview.delete_contents(trans_id)
2668
preview.create_file('b\nc\nd\ne\n', trans_id)
2669
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2670
tree_a = preview.get_preview_tree()
2671
tree_a.set_parent_ids([base_id])
2673
('killed-a', 'a\n'),
2674
('killed-b', 'b\n'),
2675
('unchanged', 'c\n'),
2676
('unchanged', 'd\n'),
2679
], list(tree_a.plan_file_merge('file-id', tree_b)))
2681
def test_plan_file_merge_revision_tree(self):
2682
work_a = self.make_branch_and_tree('wta')
2683
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2684
work_a.add('file', 'file-id')
2685
base_id = work_a.commit('base version')
2686
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2687
preview = TransformPreview(work_a.basis_tree())
2688
self.addCleanup(preview.finalize)
2689
trans_id = preview.trans_id_file_id('file-id')
2690
preview.delete_contents(trans_id)
2691
preview.create_file('b\nc\nd\ne\n', trans_id)
2692
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2693
tree_a = preview.get_preview_tree()
2694
tree_a.set_parent_ids([base_id])
2696
('killed-a', 'a\n'),
2697
('killed-b', 'b\n'),
2698
('unchanged', 'c\n'),
2699
('unchanged', 'd\n'),
2702
], list(tree_a.plan_file_merge('file-id', tree_b)))
2704
def test_walkdirs(self):
2705
preview = self.get_empty_preview()
2706
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2707
# FIXME: new_directory should mark root.
2708
preview.adjust_path('', ROOT_PARENT, root)
2709
preview_tree = preview.get_preview_tree()
2710
file_trans_id = preview.new_file('a', preview.root, 'contents',
2712
expected = [(('', 'tree-root'),
2713
[('a', 'a', 'file', None, 'a-id', 'file')])]
2714
self.assertEqual(expected, list(preview_tree.walkdirs()))
2716
def test_extras(self):
2717
work_tree = self.make_branch_and_tree('tree')
2718
self.build_tree(['tree/removed-file', 'tree/existing-file',
2719
'tree/not-removed-file'])
2720
work_tree.add(['removed-file', 'not-removed-file'])
2721
preview = TransformPreview(work_tree)
2722
self.addCleanup(preview.finalize)
2723
preview.new_file('new-file', preview.root, 'contents')
2724
preview.new_file('new-versioned-file', preview.root, 'contents',
2726
tree = preview.get_preview_tree()
2727
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2728
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2731
def test_merge_into_preview(self):
2732
work_tree = self.make_branch_and_tree('tree')
2733
self.build_tree_contents([('tree/file','b\n')])
2734
work_tree.add('file', 'file-id')
2735
work_tree.commit('first commit')
2736
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2737
self.build_tree_contents([('child/file','b\nc\n')])
2738
child_tree.commit('child commit')
2739
child_tree.lock_write()
2740
self.addCleanup(child_tree.unlock)
2741
work_tree.lock_write()
2742
self.addCleanup(work_tree.unlock)
2743
preview = TransformPreview(work_tree)
2744
self.addCleanup(preview.finalize)
2745
file_trans_id = preview.trans_id_file_id('file-id')
2746
preview.delete_contents(file_trans_id)
2747
preview.create_file('a\nb\n', file_trans_id)
2748
pb = progress.DummyProgress()
2749
preview_tree = preview.get_preview_tree()
2750
merger = Merger.from_revision_ids(pb, preview_tree,
2751
child_tree.branch.last_revision(),
2752
other_branch=child_tree.branch,
2753
tree_branch=work_tree.branch)
2754
merger.merge_type = Merge3Merger
2755
tt = merger.make_merger().make_preview_transform()
2756
self.addCleanup(tt.finalize)
2757
final_tree = tt.get_preview_tree()
2758
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2760
def test_merge_preview_into_workingtree(self):
2761
tree = self.make_branch_and_tree('tree')
2762
tree.set_root_id('TREE_ROOT')
2763
tt = TransformPreview(tree)
2764
self.addCleanup(tt.finalize)
2765
tt.new_file('name', tt.root, 'content', 'file-id')
2766
tree2 = self.make_branch_and_tree('tree2')
2767
tree2.set_root_id('TREE_ROOT')
2768
pb = progress.DummyProgress()
2769
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2770
pb, tree.basis_tree())
2771
merger.merge_type = Merge3Merger
2774
def test_merge_preview_into_workingtree_handles_conflicts(self):
2775
tree = self.make_branch_and_tree('tree')
2776
self.build_tree_contents([('tree/foo', 'bar')])
2777
tree.add('foo', 'foo-id')
2779
tt = TransformPreview(tree)
2780
self.addCleanup(tt.finalize)
2781
trans_id = tt.trans_id_file_id('foo-id')
2782
tt.delete_contents(trans_id)
2783
tt.create_file('baz', trans_id)
2784
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2785
self.build_tree_contents([('tree2/foo', 'qux')])
2786
pb = progress.DummyProgress()
2787
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2788
pb, tree.basis_tree())
2789
merger.merge_type = Merge3Merger
2792
def test_is_executable(self):
2793
tree = self.make_branch_and_tree('tree')
2794
preview = TransformPreview(tree)
2795
self.addCleanup(preview.finalize)
2796
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2797
preview_tree = preview.get_preview_tree()
2798
self.assertEqual(False, preview_tree.is_executable('baz-id',
2800
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2802
def test_commit_preview_tree(self):
2803
tree = self.make_branch_and_tree('tree')
2804
rev_id = tree.commit('rev1')
2805
tree.branch.lock_write()
2806
self.addCleanup(tree.branch.unlock)
2807
tt = TransformPreview(tree)
2808
tt.new_file('file', tt.root, 'contents', 'file_id')
2809
self.addCleanup(tt.finalize)
2810
preview = tt.get_preview_tree()
2811
preview.set_parent_ids([rev_id])
2812
builder = tree.branch.get_commit_builder([rev_id])
2813
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2814
builder.finish_inventory()
2815
rev2_id = builder.commit('rev2')
2816
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2817
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2819
def test_ascii_limbo_paths(self):
2820
self.requireFeature(tests.UnicodeFilenameFeature)
2821
branch = self.make_branch('any')
2822
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2823
tt = TransformPreview(tree)
2824
self.addCleanup(tt.finalize)
2825
foo_id = tt.new_directory('', ROOT_PARENT)
2826
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2827
limbo_path = tt._limbo_name(bar_id)
2828
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2831
class FakeSerializer(object):
2832
"""Serializer implementation that simply returns the input.
2834
The input is returned in the order used by pack.ContainerPushParser.
2837
def bytes_record(bytes, names):
2841
class TestSerializeTransform(tests.TestCaseWithTransport):
2843
_test_needs_features = [tests.UnicodeFilenameFeature]
2845
def get_preview(self, tree=None):
2847
tree = self.make_branch_and_tree('tree')
2848
tt = TransformPreview(tree)
2849
self.addCleanup(tt.finalize)
2852
def assertSerializesTo(self, expected, tt):
2853
records = list(tt.serialize(FakeSerializer()))
2854
self.assertEqual(expected, records)
2857
def default_attribs():
2862
'_new_executability': {},
2864
'_tree_path_ids': {'': 'new-0'},
2866
'_removed_contents': [],
2867
'_non_present_ids': {},
2870
def make_records(self, attribs, contents):
2872
(((('attribs'),),), bencode.bencode(attribs))]
2873
records.extend([(((n, k),), c) for n, k, c in contents])
2876
def creation_records(self):
2877
attribs = self.default_attribs()
2878
attribs['_id_number'] = 3
2879
attribs['_new_name'] = {
2880
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2881
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2882
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2883
attribs['_new_executability'] = {'new-1': 1}
2885
('new-1', 'file', 'i 1\nbar\n'),
2886
('new-2', 'directory', ''),
2888
return self.make_records(attribs, contents)
2890
def test_serialize_creation(self):
2891
tt = self.get_preview()
2892
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2893
tt.new_directory('qux', tt.root, 'quxx')
2894
self.assertSerializesTo(self.creation_records(), tt)
2896
def test_deserialize_creation(self):
2897
tt = self.get_preview()
2898
tt.deserialize(iter(self.creation_records()))
2899
self.assertEqual(3, tt._id_number)
2900
self.assertEqual({'new-1': u'foo\u1234',
2901
'new-2': 'qux'}, tt._new_name)
2902
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2903
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2904
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2905
self.assertEqual({'new-1': True}, tt._new_executability)
2906
self.assertEqual({'new-1': 'file',
2907
'new-2': 'directory'}, tt._new_contents)
2908
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2910
foo_content = foo_limbo.read()
2913
self.assertEqual('bar', foo_content)
2915
def symlink_creation_records(self):
2916
attribs = self.default_attribs()
2917
attribs['_id_number'] = 2
2918
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2919
attribs['_new_parent'] = {'new-1': 'new-0'}
2920
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2921
return self.make_records(attribs, contents)
2923
def test_serialize_symlink_creation(self):
2924
self.requireFeature(tests.SymlinkFeature)
2925
tt = self.get_preview()
2926
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2927
self.assertSerializesTo(self.symlink_creation_records(), tt)
2929
def test_deserialize_symlink_creation(self):
2930
self.requireFeature(tests.SymlinkFeature)
2931
tt = self.get_preview()
2932
tt.deserialize(iter(self.symlink_creation_records()))
2933
abspath = tt._limbo_name('new-1')
2934
foo_content = osutils.readlink(abspath)
2935
self.assertEqual(u'bar\u1234', foo_content)
2937
def make_destruction_preview(self):
2938
tree = self.make_branch_and_tree('.')
2939
self.build_tree([u'foo\u1234', 'bar'])
2940
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2941
return self.get_preview(tree)
2943
def destruction_records(self):
2944
attribs = self.default_attribs()
2945
attribs['_id_number'] = 3
2946
attribs['_removed_id'] = ['new-1']
2947
attribs['_removed_contents'] = ['new-2']
2948
attribs['_tree_path_ids'] = {
2950
u'foo\u1234'.encode('utf-8'): 'new-1',
2953
return self.make_records(attribs, [])
2955
def test_serialize_destruction(self):
2956
tt = self.make_destruction_preview()
2957
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2958
tt.unversion_file(foo_trans_id)
2959
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2960
tt.delete_contents(bar_trans_id)
2961
self.assertSerializesTo(self.destruction_records(), tt)
2963
def test_deserialize_destruction(self):
2964
tt = self.make_destruction_preview()
2965
tt.deserialize(iter(self.destruction_records()))
2966
self.assertEqual({u'foo\u1234': 'new-1',
2968
'': tt.root}, tt._tree_path_ids)
2969
self.assertEqual({'new-1': u'foo\u1234',
2971
tt.root: ''}, tt._tree_id_paths)
2972
self.assertEqual(set(['new-1']), tt._removed_id)
2973
self.assertEqual(set(['new-2']), tt._removed_contents)
2975
def missing_records(self):
2976
attribs = self.default_attribs()
2977
attribs['_id_number'] = 2
2978
attribs['_non_present_ids'] = {
2980
return self.make_records(attribs, [])
2982
def test_serialize_missing(self):
2983
tt = self.get_preview()
2984
boo_trans_id = tt.trans_id_file_id('boo')
2985
self.assertSerializesTo(self.missing_records(), tt)
2987
def test_deserialize_missing(self):
2988
tt = self.get_preview()
2989
tt.deserialize(iter(self.missing_records()))
2990
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2992
def make_modification_preview(self):
2993
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2994
LINES_TWO = 'z\nbb\nx\ndd\n'
2995
tree = self.make_branch_and_tree('tree')
2996
self.build_tree_contents([('tree/file', LINES_ONE)])
2997
tree.add('file', 'file-id')
2998
return self.get_preview(tree), LINES_TWO
3000
def modification_records(self):
3001
attribs = self.default_attribs()
3002
attribs['_id_number'] = 2
3003
attribs['_tree_path_ids'] = {
3006
attribs['_removed_contents'] = ['new-1']
3007
contents = [('new-1', 'file',
3008
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3009
return self.make_records(attribs, contents)
3011
def test_serialize_modification(self):
3012
tt, LINES = self.make_modification_preview()
3013
trans_id = tt.trans_id_file_id('file-id')
3014
tt.delete_contents(trans_id)
3015
tt.create_file(LINES, trans_id)
3016
self.assertSerializesTo(self.modification_records(), tt)
3018
def test_deserialize_modification(self):
3019
tt, LINES = self.make_modification_preview()
3020
tt.deserialize(iter(self.modification_records()))
3021
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3023
def make_kind_change_preview(self):
3024
LINES = 'a\nb\nc\nd\n'
3025
tree = self.make_branch_and_tree('tree')
3026
self.build_tree(['tree/foo/'])
3027
tree.add('foo', 'foo-id')
3028
return self.get_preview(tree), LINES
3030
def kind_change_records(self):
3031
attribs = self.default_attribs()
3032
attribs['_id_number'] = 2
3033
attribs['_tree_path_ids'] = {
3036
attribs['_removed_contents'] = ['new-1']
3037
contents = [('new-1', 'file',
3038
'i 4\na\nb\nc\nd\n\n')]
3039
return self.make_records(attribs, contents)
3041
def test_serialize_kind_change(self):
3042
tt, LINES = self.make_kind_change_preview()
3043
trans_id = tt.trans_id_file_id('foo-id')
3044
tt.delete_contents(trans_id)
3045
tt.create_file(LINES, trans_id)
3046
self.assertSerializesTo(self.kind_change_records(), tt)
3048
def test_deserialize_kind_change(self):
3049
tt, LINES = self.make_kind_change_preview()
3050
tt.deserialize(iter(self.kind_change_records()))
3051
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3053
def make_add_contents_preview(self):
3054
LINES = 'a\nb\nc\nd\n'
3055
tree = self.make_branch_and_tree('tree')
3056
self.build_tree(['tree/foo'])
3058
os.unlink('tree/foo')
3059
return self.get_preview(tree), LINES
3061
def add_contents_records(self):
3062
attribs = self.default_attribs()
3063
attribs['_id_number'] = 2
3064
attribs['_tree_path_ids'] = {
3067
contents = [('new-1', 'file',
3068
'i 4\na\nb\nc\nd\n\n')]
3069
return self.make_records(attribs, contents)
3071
def test_serialize_add_contents(self):
3072
tt, LINES = self.make_add_contents_preview()
3073
trans_id = tt.trans_id_tree_path('foo')
3074
tt.create_file(LINES, trans_id)
3075
self.assertSerializesTo(self.add_contents_records(), tt)
3077
def test_deserialize_add_contents(self):
3078
tt, LINES = self.make_add_contents_preview()
3079
tt.deserialize(iter(self.add_contents_records()))
3080
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3082
def test_get_parents_lines(self):
3083
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3084
LINES_TWO = 'z\nbb\nx\ndd\n'
3085
tree = self.make_branch_and_tree('tree')
3086
self.build_tree_contents([('tree/file', LINES_ONE)])
3087
tree.add('file', 'file-id')
3088
tt = self.get_preview(tree)
3089
trans_id = tt.trans_id_tree_path('file')
3090
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3091
tt._get_parents_lines(trans_id))
3093
def test_get_parents_texts(self):
3094
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3095
LINES_TWO = 'z\nbb\nx\ndd\n'
3096
tree = self.make_branch_and_tree('tree')
3097
self.build_tree_contents([('tree/file', LINES_ONE)])
3098
tree.add('file', 'file-id')
3099
tt = self.get_preview(tree)
3100
trans_id = tt.trans_id_tree_path('file')
3101
self.assertEqual((LINES_ONE,),
3102
tt._get_parents_texts(trans_id))