1
# Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from StringIO import StringIO
27
revision as _mod_revision,
32
from bzrlib.bzrdir import BzrDir
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
UnversionedParent, ParentLoop, DeletingParent,
36
from bzrlib.diff import show_diff_trees
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
39
PathsNotVersionedError, ExistingLimbo,
40
ExistingPendingDeletion, ImmortalLimbo,
41
ImmortalPendingDeletion, LockError)
42
from bzrlib.osutils import file_kind, pathjoin
43
from bzrlib.merge import Merge3Merger
44
from bzrlib.tests import (
45
CaseInsensitiveFilesystemFeature,
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
resolve_conflicts, cook_conflicts,
54
find_interesting, build_tree, get_backup_name,
55
change_entry, _FileMover, resolve_checkout,
58
class TestTreeTransform(tests.TestCaseWithTransport):
61
super(TestTreeTransform, self).setUp()
62
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
65
def get_transform(self):
66
transform = TreeTransform(self.wt)
67
#self.addCleanup(transform.finalize)
68
return transform, transform.root
70
def test_existing_limbo(self):
71
transform, root = self.get_transform()
72
limbo_name = transform._limbodir
73
deletion_path = transform._deletiondir
74
os.mkdir(pathjoin(limbo_name, 'hehe'))
75
self.assertRaises(ImmortalLimbo, transform.apply)
76
self.assertRaises(LockError, self.wt.unlock)
77
self.assertRaises(ExistingLimbo, self.get_transform)
78
self.assertRaises(LockError, self.wt.unlock)
79
os.rmdir(pathjoin(limbo_name, 'hehe'))
81
os.rmdir(deletion_path)
82
transform, root = self.get_transform()
85
def test_existing_pending_deletion(self):
86
transform, root = self.get_transform()
87
deletion_path = self._limbodir = urlutils.local_path_from_url(
88
transform._tree._control_files.controlfilename('pending-deletion'))
89
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
90
self.assertRaises(ImmortalPendingDeletion, transform.apply)
91
self.assertRaises(LockError, self.wt.unlock)
92
self.assertRaises(ExistingPendingDeletion, self.get_transform)
95
transform, root = self.get_transform()
96
self.wt.lock_tree_write()
97
self.addCleanup(self.wt.unlock)
98
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
99
imaginary_id = transform.trans_id_tree_path('imaginary')
100
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
101
self.assertEqual(imaginary_id, imaginary_id2)
102
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
103
self.assertEqual(transform.final_kind(root), 'directory')
104
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
105
trans_id = transform.create_path('name', root)
106
self.assertIs(transform.final_file_id(trans_id), None)
107
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
108
transform.create_file('contents', trans_id)
109
transform.set_executability(True, trans_id)
110
transform.version_file('my_pretties', trans_id)
111
self.assertRaises(DuplicateKey, transform.version_file,
112
'my_pretties', trans_id)
113
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
114
self.assertEqual(transform.final_parent(trans_id), root)
115
self.assertIs(transform.final_parent(root), ROOT_PARENT)
116
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
117
oz_id = transform.create_path('oz', root)
118
transform.create_directory(oz_id)
119
transform.version_file('ozzie', oz_id)
120
trans_id2 = transform.create_path('name2', root)
121
transform.create_file('contents', trans_id2)
122
transform.set_executability(False, trans_id2)
123
transform.version_file('my_pretties2', trans_id2)
124
modified_paths = transform.apply().modified_paths
125
self.assertEqual('contents', self.wt.get_file_byname('name').read())
126
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
127
self.assertIs(self.wt.is_executable('my_pretties'), True)
128
self.assertIs(self.wt.is_executable('my_pretties2'), False)
129
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
130
self.assertEqual(len(modified_paths), 3)
131
tree_mod_paths = [self.wt.id2abspath(f) for f in
132
('ozzie', 'my_pretties', 'my_pretties2')]
133
self.assertSubset(tree_mod_paths, modified_paths)
134
# is it safe to finalize repeatedly?
138
def test_hardlink(self):
139
self.requireFeature(HardlinkFeature)
140
transform, root = self.get_transform()
141
transform.new_file('file1', root, 'contents')
143
target = self.make_branch_and_tree('target')
144
target_transform = TreeTransform(target)
145
trans_id = target_transform.create_path('file1', target_transform.root)
146
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
147
target_transform.apply()
148
self.failUnlessExists('target/file1')
149
source_stat = os.stat(self.wt.abspath('file1'))
150
target_stat = os.stat('target/file1')
151
self.assertEqual(source_stat, target_stat)
153
def test_convenience(self):
154
transform, root = self.get_transform()
155
self.wt.lock_tree_write()
156
self.addCleanup(self.wt.unlock)
157
trans_id = transform.new_file('name', root, 'contents',
159
oz = transform.new_directory('oz', root, 'oz-id')
160
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
161
toto = transform.new_file('toto', dorothy, 'toto-contents',
164
self.assertEqual(len(transform.find_conflicts()), 0)
166
self.assertRaises(ReusingTransform, transform.find_conflicts)
167
self.assertEqual('contents', file(self.wt.abspath('name')).read())
168
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
169
self.assertIs(self.wt.is_executable('my_pretties'), True)
170
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
171
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
172
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
174
self.assertEqual('toto-contents',
175
self.wt.get_file_byname('oz/dorothy/toto').read())
176
self.assertIs(self.wt.is_executable('toto-id'), False)
178
def test_tree_reference(self):
179
transform, root = self.get_transform()
180
tree = transform._tree
181
trans_id = transform.new_directory('reference', root, 'subtree-id')
182
transform.set_tree_reference('subtree-revision', trans_id)
185
self.addCleanup(tree.unlock)
186
self.assertEqual('subtree-revision',
187
tree.inventory['subtree-id'].reference_revision)
189
def test_conflicts(self):
190
transform, root = self.get_transform()
191
trans_id = transform.new_file('name', root, 'contents',
193
self.assertEqual(len(transform.find_conflicts()), 0)
194
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
195
self.assertEqual(transform.find_conflicts(),
196
[('duplicate', trans_id, trans_id2, 'name')])
197
self.assertRaises(MalformedTransform, transform.apply)
198
transform.adjust_path('name', trans_id, trans_id2)
199
self.assertEqual(transform.find_conflicts(),
200
[('non-directory parent', trans_id)])
201
tinman_id = transform.trans_id_tree_path('tinman')
202
transform.adjust_path('name', tinman_id, trans_id2)
203
self.assertEqual(transform.find_conflicts(),
204
[('unversioned parent', tinman_id),
205
('missing parent', tinman_id)])
206
lion_id = transform.create_path('lion', root)
207
self.assertEqual(transform.find_conflicts(),
208
[('unversioned parent', tinman_id),
209
('missing parent', tinman_id)])
210
transform.adjust_path('name', lion_id, trans_id2)
211
self.assertEqual(transform.find_conflicts(),
212
[('unversioned parent', lion_id),
213
('missing parent', lion_id)])
214
transform.version_file("Courage", lion_id)
215
self.assertEqual(transform.find_conflicts(),
216
[('missing parent', lion_id),
217
('versioning no contents', lion_id)])
218
transform.adjust_path('name2', root, trans_id2)
219
self.assertEqual(transform.find_conflicts(),
220
[('versioning no contents', lion_id)])
221
transform.create_file('Contents, okay?', lion_id)
222
transform.adjust_path('name2', trans_id2, trans_id2)
223
self.assertEqual(transform.find_conflicts(),
224
[('parent loop', trans_id2),
225
('non-directory parent', trans_id2)])
226
transform.adjust_path('name2', root, trans_id2)
227
oz_id = transform.new_directory('oz', root)
228
transform.set_executability(True, oz_id)
229
self.assertEqual(transform.find_conflicts(),
230
[('unversioned executability', oz_id)])
231
transform.version_file('oz-id', oz_id)
232
self.assertEqual(transform.find_conflicts(),
233
[('non-file executability', oz_id)])
234
transform.set_executability(None, oz_id)
235
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
237
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
238
self.assertEqual('contents', file(self.wt.abspath('name')).read())
239
transform2, root = self.get_transform()
240
oz_id = transform2.trans_id_tree_file_id('oz-id')
241
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
242
result = transform2.find_conflicts()
243
fp = FinalPaths(transform2)
244
self.assert_('oz/tip' in transform2._tree_path_ids)
245
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
246
self.assertEqual(len(result), 2)
247
self.assertEqual((result[0][0], result[0][1]),
248
('duplicate', newtip))
249
self.assertEqual((result[1][0], result[1][2]),
250
('duplicate id', newtip))
251
transform2.finalize()
252
transform3 = TreeTransform(self.wt)
253
self.addCleanup(transform3.finalize)
254
oz_id = transform3.trans_id_tree_file_id('oz-id')
255
transform3.delete_contents(oz_id)
256
self.assertEqual(transform3.find_conflicts(),
257
[('missing parent', oz_id)])
258
root_id = transform3.root
259
tip_id = transform3.trans_id_tree_file_id('tip-id')
260
transform3.adjust_path('tip', root_id, tip_id)
263
def test_conflict_on_case_insensitive(self):
264
tree = self.make_branch_and_tree('tree')
265
# Don't try this at home, kids!
266
# Force the tree to report that it is case sensitive, for conflict
268
tree.case_sensitive = True
269
transform = TreeTransform(tree)
270
self.addCleanup(transform.finalize)
271
transform.new_file('file', transform.root, 'content')
272
transform.new_file('FiLe', transform.root, 'content')
273
result = transform.find_conflicts()
274
self.assertEqual([], result)
276
# Force the tree to report that it is case insensitive, for conflict
278
tree.case_sensitive = False
279
transform = TreeTransform(tree)
280
self.addCleanup(transform.finalize)
281
transform.new_file('file', transform.root, 'content')
282
transform.new_file('FiLe', transform.root, 'content')
283
result = transform.find_conflicts()
284
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
286
def test_conflict_on_case_insensitive_existing(self):
287
tree = self.make_branch_and_tree('tree')
288
self.build_tree(['tree/FiLe'])
289
# Don't try this at home, kids!
290
# Force the tree to report that it is case sensitive, for conflict
292
tree.case_sensitive = True
293
transform = TreeTransform(tree)
294
self.addCleanup(transform.finalize)
295
transform.new_file('file', transform.root, 'content')
296
result = transform.find_conflicts()
297
self.assertEqual([], result)
299
# Force the tree to report that it is case insensitive, for conflict
301
tree.case_sensitive = False
302
transform = TreeTransform(tree)
303
self.addCleanup(transform.finalize)
304
transform.new_file('file', transform.root, 'content')
305
result = transform.find_conflicts()
306
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
308
def test_resolve_case_insensitive_conflict(self):
309
tree = self.make_branch_and_tree('tree')
310
# Don't try this at home, kids!
311
# Force the tree to report that it is case insensitive, for conflict
313
tree.case_sensitive = False
314
transform = TreeTransform(tree)
315
self.addCleanup(transform.finalize)
316
transform.new_file('file', transform.root, 'content')
317
transform.new_file('FiLe', transform.root, 'content')
318
resolve_conflicts(transform)
320
self.failUnlessExists('tree/file')
321
self.failUnlessExists('tree/FiLe.moved')
323
def test_resolve_checkout_case_conflict(self):
324
tree = self.make_branch_and_tree('tree')
325
# Don't try this at home, kids!
326
# Force the tree to report that it is case insensitive, for conflict
328
tree.case_sensitive = False
329
transform = TreeTransform(tree)
330
self.addCleanup(transform.finalize)
331
transform.new_file('file', transform.root, 'content')
332
transform.new_file('FiLe', transform.root, 'content')
333
resolve_conflicts(transform,
334
pass_func=lambda t, c: resolve_checkout(t, c, []))
336
self.failUnlessExists('tree/file')
337
self.failUnlessExists('tree/FiLe.moved')
339
def test_apply_case_conflict(self):
340
"""Ensure that a transform with case conflicts can always be applied"""
341
tree = self.make_branch_and_tree('tree')
342
transform = TreeTransform(tree)
343
self.addCleanup(transform.finalize)
344
transform.new_file('file', transform.root, 'content')
345
transform.new_file('FiLe', transform.root, 'content')
346
dir = transform.new_directory('dir', transform.root)
347
transform.new_file('dirfile', dir, 'content')
348
transform.new_file('dirFiLe', dir, 'content')
349
resolve_conflicts(transform)
351
self.failUnlessExists('tree/file')
352
if not os.path.exists('tree/FiLe.moved'):
353
self.failUnlessExists('tree/FiLe')
354
self.failUnlessExists('tree/dir/dirfile')
355
if not os.path.exists('tree/dir/dirFiLe.moved'):
356
self.failUnlessExists('tree/dir/dirFiLe')
358
def test_case_insensitive_limbo(self):
359
tree = self.make_branch_and_tree('tree')
360
# Don't try this at home, kids!
361
# Force the tree to report that it is case insensitive
362
tree.case_sensitive = False
363
transform = TreeTransform(tree)
364
self.addCleanup(transform.finalize)
365
dir = transform.new_directory('dir', transform.root)
366
first = transform.new_file('file', dir, 'content')
367
second = transform.new_file('FiLe', dir, 'content')
368
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
369
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
371
def test_add_del(self):
372
start, root = self.get_transform()
373
start.new_directory('a', root, 'a')
375
transform, root = self.get_transform()
376
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
377
transform.new_directory('a', root, 'a')
380
def test_unversioning(self):
381
create_tree, root = self.get_transform()
382
parent_id = create_tree.new_directory('parent', root, 'parent-id')
383
create_tree.new_file('child', parent_id, 'child', 'child-id')
385
unversion = TreeTransform(self.wt)
386
self.addCleanup(unversion.finalize)
387
parent = unversion.trans_id_tree_path('parent')
388
unversion.unversion_file(parent)
389
self.assertEqual(unversion.find_conflicts(),
390
[('unversioned parent', parent_id)])
391
file_id = unversion.trans_id_tree_file_id('child-id')
392
unversion.unversion_file(file_id)
395
def test_name_invariants(self):
396
create_tree, root = self.get_transform()
398
root = create_tree.root
399
create_tree.new_file('name1', root, 'hello1', 'name1')
400
create_tree.new_file('name2', root, 'hello2', 'name2')
401
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
402
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
403
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
404
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
407
mangle_tree,root = self.get_transform()
408
root = mangle_tree.root
410
name1 = mangle_tree.trans_id_tree_file_id('name1')
411
name2 = mangle_tree.trans_id_tree_file_id('name2')
412
mangle_tree.adjust_path('name2', root, name1)
413
mangle_tree.adjust_path('name1', root, name2)
415
#tests for deleting parent directories
416
ddir = mangle_tree.trans_id_tree_file_id('ddir')
417
mangle_tree.delete_contents(ddir)
418
dfile = mangle_tree.trans_id_tree_file_id('dfile')
419
mangle_tree.delete_versioned(dfile)
420
mangle_tree.unversion_file(dfile)
421
mfile = mangle_tree.trans_id_tree_file_id('mfile')
422
mangle_tree.adjust_path('mfile', root, mfile)
424
#tests for adding parent directories
425
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
426
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
427
mangle_tree.adjust_path('mfile2', newdir, mfile2)
428
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
429
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
430
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
431
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
433
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
434
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
435
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
436
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
437
self.assertEqual(file(mfile2_path).read(), 'later2')
438
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
439
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
440
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
441
self.assertEqual(file(newfile_path).read(), 'hello3')
442
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
443
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
444
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
446
def test_both_rename(self):
447
create_tree,root = self.get_transform()
448
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
449
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
451
mangle_tree,root = self.get_transform()
452
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
453
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
454
mangle_tree.adjust_path('test', root, selftest)
455
mangle_tree.adjust_path('test_too_much', root, selftest)
456
mangle_tree.set_executability(True, blackbox)
459
def test_both_rename2(self):
460
create_tree,root = self.get_transform()
461
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
462
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
463
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
464
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
467
mangle_tree,root = self.get_transform()
468
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
469
tests = mangle_tree.trans_id_tree_file_id('tests-id')
470
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
471
mangle_tree.adjust_path('selftest', bzrlib, tests)
472
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
473
mangle_tree.set_executability(True, test_too_much)
476
def test_both_rename3(self):
477
create_tree,root = self.get_transform()
478
tests = create_tree.new_directory('tests', root, 'tests-id')
479
create_tree.new_file('test_too_much.py', tests, 'hello1',
482
mangle_tree,root = self.get_transform()
483
tests = mangle_tree.trans_id_tree_file_id('tests-id')
484
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
485
mangle_tree.adjust_path('selftest', root, tests)
486
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
487
mangle_tree.set_executability(True, test_too_much)
490
def test_move_dangling_ie(self):
491
create_tree, root = self.get_transform()
493
root = create_tree.root
494
create_tree.new_file('name1', root, 'hello1', 'name1')
496
delete_contents, root = self.get_transform()
497
file = delete_contents.trans_id_tree_file_id('name1')
498
delete_contents.delete_contents(file)
499
delete_contents.apply()
500
move_id, root = self.get_transform()
501
name1 = move_id.trans_id_tree_file_id('name1')
502
newdir = move_id.new_directory('dir', root, 'newdir')
503
move_id.adjust_path('name2', newdir, name1)
506
def test_replace_dangling_ie(self):
507
create_tree, root = self.get_transform()
509
root = create_tree.root
510
create_tree.new_file('name1', root, 'hello1', 'name1')
512
delete_contents = TreeTransform(self.wt)
513
self.addCleanup(delete_contents.finalize)
514
file = delete_contents.trans_id_tree_file_id('name1')
515
delete_contents.delete_contents(file)
516
delete_contents.apply()
517
delete_contents.finalize()
518
replace = TreeTransform(self.wt)
519
self.addCleanup(replace.finalize)
520
name2 = replace.new_file('name2', root, 'hello2', 'name1')
521
conflicts = replace.find_conflicts()
522
name1 = replace.trans_id_tree_file_id('name1')
523
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
524
resolve_conflicts(replace)
527
def test_symlinks(self):
528
self.requireFeature(SymlinkFeature)
529
transform,root = self.get_transform()
530
oz_id = transform.new_directory('oz', root, 'oz-id')
531
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
533
wiz_id = transform.create_path('wizard2', oz_id)
534
transform.create_symlink('behind_curtain', wiz_id)
535
transform.version_file('wiz-id2', wiz_id)
536
transform.set_executability(True, wiz_id)
537
self.assertEqual(transform.find_conflicts(),
538
[('non-file executability', wiz_id)])
539
transform.set_executability(None, wiz_id)
541
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
542
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
543
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
545
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
548
def test_unable_create_symlink(self):
550
wt = self.make_branch_and_tree('.')
551
tt = TreeTransform(wt) # TreeTransform obtains write lock
553
tt.new_symlink('foo', tt.root, 'bar')
557
os_symlink = getattr(os, 'symlink', None)
560
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
562
"Unable to create symlink 'foo' on this platform",
566
os.symlink = os_symlink
568
def get_conflicted(self):
569
create,root = self.get_transform()
570
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
571
oz = create.new_directory('oz', root, 'oz-id')
572
create.new_directory('emeraldcity', oz, 'emerald-id')
574
conflicts,root = self.get_transform()
575
# set up duplicate entry, duplicate id
576
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
578
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
579
oz = conflicts.trans_id_tree_file_id('oz-id')
580
# set up DeletedParent parent conflict
581
conflicts.delete_versioned(oz)
582
emerald = conflicts.trans_id_tree_file_id('emerald-id')
583
# set up MissingParent conflict
584
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
585
conflicts.adjust_path('munchkincity', root, munchkincity)
586
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
588
conflicts.adjust_path('emeraldcity', emerald, emerald)
589
return conflicts, emerald, oz, old_dorothy, new_dorothy
591
def test_conflict_resolution(self):
592
conflicts, emerald, oz, old_dorothy, new_dorothy =\
593
self.get_conflicted()
594
resolve_conflicts(conflicts)
595
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
596
self.assertIs(conflicts.final_file_id(old_dorothy), None)
597
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
598
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
599
self.assertEqual(conflicts.final_parent(emerald), oz)
602
def test_cook_conflicts(self):
603
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
604
raw_conflicts = resolve_conflicts(tt)
605
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
606
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
607
'dorothy', None, 'dorothy-id')
608
self.assertEqual(cooked_conflicts[0], duplicate)
609
duplicate_id = DuplicateID('Unversioned existing file',
610
'dorothy.moved', 'dorothy', None,
612
self.assertEqual(cooked_conflicts[1], duplicate_id)
613
missing_parent = MissingParent('Created directory', 'munchkincity',
615
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
616
self.assertEqual(cooked_conflicts[2], missing_parent)
617
unversioned_parent = UnversionedParent('Versioned directory',
620
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
622
self.assertEqual(cooked_conflicts[3], unversioned_parent)
623
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
624
'oz/emeraldcity', 'emerald-id', 'emerald-id')
625
self.assertEqual(cooked_conflicts[4], deleted_parent)
626
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
627
self.assertEqual(cooked_conflicts[6], parent_loop)
628
self.assertEqual(len(cooked_conflicts), 7)
631
def test_string_conflicts(self):
632
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
633
raw_conflicts = resolve_conflicts(tt)
634
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
636
conflicts_s = [str(c) for c in cooked_conflicts]
637
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
638
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
639
'Moved existing file to '
641
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
642
'Unversioned existing file '
644
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
645
' munchkincity. Created directory.')
646
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
647
' versioned, but has versioned'
648
' children. Versioned directory.')
649
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
650
" is not empty. Not deleting.")
651
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
652
' versioned, but has versioned'
653
' children. Versioned directory.')
654
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
655
' oz/emeraldcity. Cancelled move.')
657
def prepare_wrong_parent_kind(self):
658
tt, root = self.get_transform()
659
tt.new_file('parent', root, 'contents', 'parent-id')
661
tt, root = self.get_transform()
662
parent_id = tt.trans_id_file_id('parent-id')
663
tt.new_file('child,', parent_id, 'contents2', 'file-id')
666
def test_find_conflicts_wrong_parent_kind(self):
667
tt = self.prepare_wrong_parent_kind()
670
def test_resolve_conflicts_wrong_existing_parent_kind(self):
671
tt = self.prepare_wrong_parent_kind()
672
raw_conflicts = resolve_conflicts(tt)
673
self.assertEqual(set([('non-directory parent', 'Created directory',
674
'new-3')]), raw_conflicts)
675
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
676
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
677
'parent-id')], cooked_conflicts)
679
self.assertEqual(None, self.wt.path2id('parent'))
680
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
682
def test_resolve_conflicts_wrong_new_parent_kind(self):
683
tt, root = self.get_transform()
684
parent_id = tt.new_directory('parent', root, 'parent-id')
685
tt.new_file('child,', parent_id, 'contents2', 'file-id')
687
tt, root = self.get_transform()
688
parent_id = tt.trans_id_file_id('parent-id')
689
tt.delete_contents(parent_id)
690
tt.create_file('contents', parent_id)
691
raw_conflicts = resolve_conflicts(tt)
692
self.assertEqual(set([('non-directory parent', 'Created directory',
693
'new-3')]), raw_conflicts)
695
self.assertEqual(None, self.wt.path2id('parent'))
696
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
698
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
699
tt, root = self.get_transform()
700
parent_id = tt.new_directory('parent', root)
701
tt.new_file('child,', parent_id, 'contents2')
703
tt, root = self.get_transform()
704
parent_id = tt.trans_id_tree_path('parent')
705
tt.delete_contents(parent_id)
706
tt.create_file('contents', parent_id)
707
resolve_conflicts(tt)
709
self.assertIs(None, self.wt.path2id('parent'))
710
self.assertIs(None, self.wt.path2id('parent.new'))
712
def test_moving_versioned_directories(self):
713
create, root = self.get_transform()
714
kansas = create.new_directory('kansas', root, 'kansas-id')
715
create.new_directory('house', kansas, 'house-id')
716
create.new_directory('oz', root, 'oz-id')
718
cyclone, root = self.get_transform()
719
oz = cyclone.trans_id_tree_file_id('oz-id')
720
house = cyclone.trans_id_tree_file_id('house-id')
721
cyclone.adjust_path('house', oz, house)
724
def test_moving_root(self):
725
create, root = self.get_transform()
726
fun = create.new_directory('fun', root, 'fun-id')
727
create.new_directory('sun', root, 'sun-id')
728
create.new_directory('moon', root, 'moon')
730
transform, root = self.get_transform()
731
transform.adjust_root_path('oldroot', fun)
732
new_root=transform.trans_id_tree_path('')
733
transform.version_file('new-root', new_root)
736
def test_renames(self):
737
create, root = self.get_transform()
738
old = create.new_directory('old-parent', root, 'old-id')
739
intermediate = create.new_directory('intermediate', old, 'im-id')
740
myfile = create.new_file('myfile', intermediate, 'myfile-text',
743
rename, root = self.get_transform()
744
old = rename.trans_id_file_id('old-id')
745
rename.adjust_path('new', root, old)
746
myfile = rename.trans_id_file_id('myfile-id')
747
rename.set_executability(True, myfile)
750
def test_find_interesting(self):
751
create, root = self.get_transform()
753
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
754
create.new_file('uvfile', root, 'othertext')
756
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
757
find_interesting, wt, wt, ['vfile'])
758
self.assertEqual(result, set(['myfile-id']))
760
def test_set_executability_order(self):
761
"""Ensure that executability behaves the same, no matter what order.
763
- create file and set executability simultaneously
764
- create file and set executability afterward
765
- unsetting the executability of a file whose executability has not been
766
declared should throw an exception (this may happen when a
767
merge attempts to create a file with a duplicate ID)
769
transform, root = self.get_transform()
772
self.addCleanup(wt.unlock)
773
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
775
sac = transform.new_file('set_after_creation', root,
776
'Set after creation', 'sac')
777
transform.set_executability(True, sac)
778
uws = transform.new_file('unset_without_set', root, 'Unset badly',
780
self.assertRaises(KeyError, transform.set_executability, None, uws)
782
self.assertTrue(wt.is_executable('soc'))
783
self.assertTrue(wt.is_executable('sac'))
785
def test_preserve_mode(self):
786
"""File mode is preserved when replacing content"""
787
if sys.platform == 'win32':
788
raise TestSkipped('chmod has no effect on win32')
789
transform, root = self.get_transform()
790
transform.new_file('file1', root, 'contents', 'file1-id', True)
793
self.addCleanup(self.wt.unlock)
794
self.assertTrue(self.wt.is_executable('file1-id'))
795
transform, root = self.get_transform()
796
file1_id = transform.trans_id_tree_file_id('file1-id')
797
transform.delete_contents(file1_id)
798
transform.create_file('contents2', file1_id)
800
self.assertTrue(self.wt.is_executable('file1-id'))
802
def test__set_mode_stats_correctly(self):
803
"""_set_mode stats to determine file mode."""
804
if sys.platform == 'win32':
805
raise TestSkipped('chmod has no effect on win32')
809
def instrumented_stat(path):
810
stat_paths.append(path)
811
return real_stat(path)
813
transform, root = self.get_transform()
815
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
816
file_id='bar-id-1', executable=False)
819
transform, root = self.get_transform()
820
bar1_id = transform.trans_id_tree_path('bar')
821
bar2_id = transform.trans_id_tree_path('bar2')
823
os.stat = instrumented_stat
824
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
829
bar1_abspath = self.wt.abspath('bar')
830
self.assertEqual([bar1_abspath], stat_paths)
832
def test_iter_changes(self):
833
self.wt.set_root_id('eert_toor')
834
transform, root = self.get_transform()
835
transform.new_file('old', root, 'blah', 'id-1', True)
837
transform, root = self.get_transform()
839
self.assertEqual([], list(transform.iter_changes()))
840
old = transform.trans_id_tree_file_id('id-1')
841
transform.unversion_file(old)
842
self.assertEqual([('id-1', ('old', None), False, (True, False),
843
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
844
(True, True))], list(transform.iter_changes()))
845
transform.new_directory('new', root, 'id-1')
846
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
847
('eert_toor', 'eert_toor'), ('old', 'new'),
848
('file', 'directory'),
849
(True, False))], list(transform.iter_changes()))
853
def test_iter_changes_new(self):
854
self.wt.set_root_id('eert_toor')
855
transform, root = self.get_transform()
856
transform.new_file('old', root, 'blah')
858
transform, root = self.get_transform()
860
old = transform.trans_id_tree_path('old')
861
transform.version_file('id-1', old)
862
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
863
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
864
(False, False))], list(transform.iter_changes()))
868
def test_iter_changes_modifications(self):
869
self.wt.set_root_id('eert_toor')
870
transform, root = self.get_transform()
871
transform.new_file('old', root, 'blah', 'id-1')
872
transform.new_file('new', root, 'blah')
873
transform.new_directory('subdir', root, 'subdir-id')
875
transform, root = self.get_transform()
877
old = transform.trans_id_tree_path('old')
878
subdir = transform.trans_id_tree_file_id('subdir-id')
879
new = transform.trans_id_tree_path('new')
880
self.assertEqual([], list(transform.iter_changes()))
883
transform.delete_contents(old)
884
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
885
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
886
(False, False))], list(transform.iter_changes()))
889
transform.create_file('blah', old)
890
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
891
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
892
(False, False))], list(transform.iter_changes()))
893
transform.cancel_deletion(old)
894
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
895
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
896
(False, False))], list(transform.iter_changes()))
897
transform.cancel_creation(old)
899
# move file_id to a different file
900
self.assertEqual([], list(transform.iter_changes()))
901
transform.unversion_file(old)
902
transform.version_file('id-1', new)
903
transform.adjust_path('old', root, new)
904
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
905
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
906
(False, False))], list(transform.iter_changes()))
907
transform.cancel_versioning(new)
908
transform._removed_id = set()
911
self.assertEqual([], list(transform.iter_changes()))
912
transform.set_executability(True, old)
913
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
914
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
915
(False, True))], list(transform.iter_changes()))
916
transform.set_executability(None, old)
919
self.assertEqual([], list(transform.iter_changes()))
920
transform.adjust_path('new', root, old)
921
transform._new_parent = {}
922
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
923
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
924
(False, False))], list(transform.iter_changes()))
925
transform._new_name = {}
928
self.assertEqual([], list(transform.iter_changes()))
929
transform.adjust_path('new', subdir, old)
930
transform._new_name = {}
931
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
932
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
933
('file', 'file'), (False, False))],
934
list(transform.iter_changes()))
935
transform._new_path = {}
940
def test_iter_changes_modified_bleed(self):
941
self.wt.set_root_id('eert_toor')
942
"""Modified flag should not bleed from one change to another"""
943
# unfortunately, we have no guarantee that file1 (which is modified)
944
# will be applied before file2. And if it's applied after file2, it
945
# obviously can't bleed into file2's change output. But for now, it
947
transform, root = self.get_transform()
948
transform.new_file('file1', root, 'blah', 'id-1')
949
transform.new_file('file2', root, 'blah', 'id-2')
951
transform, root = self.get_transform()
953
transform.delete_contents(transform.trans_id_file_id('id-1'))
954
transform.set_executability(True,
955
transform.trans_id_file_id('id-2'))
956
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
957
('eert_toor', 'eert_toor'), ('file1', u'file1'),
958
('file', None), (False, False)),
959
('id-2', (u'file2', u'file2'), False, (True, True),
960
('eert_toor', 'eert_toor'), ('file2', u'file2'),
961
('file', 'file'), (False, True))],
962
list(transform.iter_changes()))
966
def test_iter_changes_move_missing(self):
967
"""Test moving ids with no files around"""
968
self.wt.set_root_id('toor_eert')
969
# Need two steps because versioning a non-existant file is a conflict.
970
transform, root = self.get_transform()
971
transform.new_directory('floater', root, 'floater-id')
973
transform, root = self.get_transform()
974
transform.delete_contents(transform.trans_id_tree_path('floater'))
976
transform, root = self.get_transform()
977
floater = transform.trans_id_tree_path('floater')
979
transform.adjust_path('flitter', root, floater)
980
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
981
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
982
(None, None), (False, False))], list(transform.iter_changes()))
986
def test_iter_changes_pointless(self):
987
"""Ensure that no-ops are not treated as modifications"""
988
self.wt.set_root_id('eert_toor')
989
transform, root = self.get_transform()
990
transform.new_file('old', root, 'blah', 'id-1')
991
transform.new_directory('subdir', root, 'subdir-id')
993
transform, root = self.get_transform()
995
old = transform.trans_id_tree_path('old')
996
subdir = transform.trans_id_tree_file_id('subdir-id')
997
self.assertEqual([], list(transform.iter_changes()))
998
transform.delete_contents(subdir)
999
transform.create_directory(subdir)
1000
transform.set_executability(False, old)
1001
transform.unversion_file(old)
1002
transform.version_file('id-1', old)
1003
transform.adjust_path('old', root, old)
1004
self.assertEqual([], list(transform.iter_changes()))
1006
transform.finalize()
1008
def test_rename_count(self):
1009
transform, root = self.get_transform()
1010
transform.new_file('name1', root, 'contents')
1011
self.assertEqual(transform.rename_count, 0)
1013
self.assertEqual(transform.rename_count, 1)
1014
transform2, root = self.get_transform()
1015
transform2.adjust_path('name2', root,
1016
transform2.trans_id_tree_path('name1'))
1017
self.assertEqual(transform2.rename_count, 0)
1019
self.assertEqual(transform2.rename_count, 2)
1021
def test_change_parent(self):
1022
"""Ensure that after we change a parent, the results are still right.
1024
Renames and parent changes on pending transforms can happen as part
1025
of conflict resolution, and are explicitly permitted by the
1028
This test ensures they work correctly with the rename-avoidance
1031
transform, root = self.get_transform()
1032
parent1 = transform.new_directory('parent1', root)
1033
child1 = transform.new_file('child1', parent1, 'contents')
1034
parent2 = transform.new_directory('parent2', root)
1035
transform.adjust_path('child1', parent2, child1)
1037
self.failIfExists(self.wt.abspath('parent1/child1'))
1038
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1039
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1040
# no rename for child1 (counting only renames during apply)
1041
self.failUnlessEqual(2, transform.rename_count)
1043
def test_cancel_parent(self):
1044
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1046
This is like the test_change_parent, except that we cancel the parent
1047
before adjusting the path. The transform must detect that the
1048
directory is non-empty, and move children to safe locations.
1050
transform, root = self.get_transform()
1051
parent1 = transform.new_directory('parent1', root)
1052
child1 = transform.new_file('child1', parent1, 'contents')
1053
child2 = transform.new_file('child2', parent1, 'contents')
1055
transform.cancel_creation(parent1)
1057
self.fail('Failed to move child1 before deleting parent1')
1058
transform.cancel_creation(child2)
1059
transform.create_directory(parent1)
1061
transform.cancel_creation(parent1)
1062
# If the transform incorrectly believes that child2 is still in
1063
# parent1's limbo directory, it will try to rename it and fail
1064
# because was already moved by the first cancel_creation.
1066
self.fail('Transform still thinks child2 is a child of parent1')
1067
parent2 = transform.new_directory('parent2', root)
1068
transform.adjust_path('child1', parent2, child1)
1070
self.failIfExists(self.wt.abspath('parent1'))
1071
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1072
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1073
self.failUnlessEqual(2, transform.rename_count)
1075
def test_adjust_and_cancel(self):
1076
"""Make sure adjust_path keeps track of limbo children properly"""
1077
transform, root = self.get_transform()
1078
parent1 = transform.new_directory('parent1', root)
1079
child1 = transform.new_file('child1', parent1, 'contents')
1080
parent2 = transform.new_directory('parent2', root)
1081
transform.adjust_path('child1', parent2, child1)
1082
transform.cancel_creation(child1)
1084
transform.cancel_creation(parent1)
1085
# if the transform thinks child1 is still in parent1's limbo
1086
# directory, it will attempt to move it and fail.
1088
self.fail('Transform still thinks child1 is a child of parent1')
1089
transform.finalize()
1091
def test_noname_contents(self):
1092
"""TreeTransform should permit deferring naming files."""
1093
transform, root = self.get_transform()
1094
parent = transform.trans_id_file_id('parent-id')
1096
transform.create_directory(parent)
1098
self.fail("Can't handle contents with no name")
1099
transform.finalize()
1101
def test_noname_contents_nested(self):
1102
"""TreeTransform should permit deferring naming files."""
1103
transform, root = self.get_transform()
1104
parent = transform.trans_id_file_id('parent-id')
1106
transform.create_directory(parent)
1108
self.fail("Can't handle contents with no name")
1109
child = transform.new_directory('child', parent)
1110
transform.adjust_path('parent', root, parent)
1112
self.failUnlessExists(self.wt.abspath('parent/child'))
1113
self.assertEqual(1, transform.rename_count)
1115
def test_reuse_name(self):
1116
"""Avoid reusing the same limbo name for different files"""
1117
transform, root = self.get_transform()
1118
parent = transform.new_directory('parent', root)
1119
child1 = transform.new_directory('child', parent)
1121
child2 = transform.new_directory('child', parent)
1123
self.fail('Tranform tried to use the same limbo name twice')
1124
transform.adjust_path('child2', parent, child2)
1126
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1127
# child2 is put into top-level limbo because child1 has already
1128
# claimed the direct limbo path when child2 is created. There is no
1129
# advantage in renaming files once they're in top-level limbo, except
1131
self.assertEqual(2, transform.rename_count)
1133
def test_reuse_when_first_moved(self):
1134
"""Don't avoid direct paths when it is safe to use them"""
1135
transform, root = self.get_transform()
1136
parent = transform.new_directory('parent', root)
1137
child1 = transform.new_directory('child', parent)
1138
transform.adjust_path('child1', parent, child1)
1139
child2 = transform.new_directory('child', parent)
1141
# limbo/new-1 => parent
1142
self.assertEqual(1, transform.rename_count)
1144
def test_reuse_after_cancel(self):
1145
"""Don't avoid direct paths when it is safe to use them"""
1146
transform, root = self.get_transform()
1147
parent2 = transform.new_directory('parent2', root)
1148
child1 = transform.new_directory('child1', parent2)
1149
transform.cancel_creation(parent2)
1150
transform.create_directory(parent2)
1151
child2 = transform.new_directory('child1', parent2)
1152
transform.adjust_path('child2', parent2, child1)
1154
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1155
self.assertEqual(2, transform.rename_count)
1157
def test_finalize_order(self):
1158
"""Finalize must be done in child-to-parent order"""
1159
transform, root = self.get_transform()
1160
parent = transform.new_directory('parent', root)
1161
child = transform.new_directory('child', parent)
1163
transform.finalize()
1165
self.fail('Tried to remove parent before child1')
1167
def test_cancel_with_cancelled_child_should_succeed(self):
1168
transform, root = self.get_transform()
1169
parent = transform.new_directory('parent', root)
1170
child = transform.new_directory('child', parent)
1171
transform.cancel_creation(child)
1172
transform.cancel_creation(parent)
1173
transform.finalize()
1175
def test_change_entry(self):
1176
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1177
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1180
def test_case_insensitive_clash(self):
1181
self.requireFeature(CaseInsensitiveFilesystemFeature)
1183
wt = self.make_branch_and_tree('.')
1184
tt = TreeTransform(wt) # TreeTransform obtains write lock
1186
tt.new_file('foo', tt.root, 'bar')
1187
tt.new_file('Foo', tt.root, 'spam')
1188
# Lie to tt that we've already resolved all conflicts.
1189
tt.apply(no_conflicts=True)
1193
err = self.assertRaises(errors.FileExists, tt_helper)
1194
self.assertContainsRe(str(err),
1195
"^File exists: .+/foo")
1197
def test_two_directories_clash(self):
1199
wt = self.make_branch_and_tree('.')
1200
tt = TreeTransform(wt) # TreeTransform obtains write lock
1202
foo_1 = tt.new_directory('foo', tt.root)
1203
tt.new_directory('bar', foo_1)
1204
foo_2 = tt.new_directory('foo', tt.root)
1205
tt.new_directory('baz', foo_2)
1206
# Lie to tt that we've already resolved all conflicts.
1207
tt.apply(no_conflicts=True)
1211
err = self.assertRaises(errors.FileExists, tt_helper)
1212
self.assertContainsRe(str(err),
1213
"^File exists: .+/foo")
1215
def test_two_directories_clash_finalize(self):
1217
wt = self.make_branch_and_tree('.')
1218
tt = TreeTransform(wt) # TreeTransform obtains write lock
1220
foo_1 = tt.new_directory('foo', tt.root)
1221
tt.new_directory('bar', foo_1)
1222
foo_2 = tt.new_directory('foo', tt.root)
1223
tt.new_directory('baz', foo_2)
1224
# Lie to tt that we've already resolved all conflicts.
1225
tt.apply(no_conflicts=True)
1229
err = self.assertRaises(errors.FileExists, tt_helper)
1230
self.assertContainsRe(str(err),
1231
"^File exists: .+/foo")
1234
class TransformGroup(object):
1236
def __init__(self, dirname, root_id):
1239
self.wt = BzrDir.create_standalone_workingtree(dirname)
1240
self.wt.set_root_id(root_id)
1241
self.b = self.wt.branch
1242
self.tt = TreeTransform(self.wt)
1243
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1246
def conflict_text(tree, merge):
1247
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1248
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1251
class TestTransformMerge(TestCaseInTempDir):
1253
def test_text_merge(self):
1254
root_id = generate_ids.gen_root_id()
1255
base = TransformGroup("base", root_id)
1256
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1257
base.tt.new_file('b', base.root, 'b1', 'b')
1258
base.tt.new_file('c', base.root, 'c', 'c')
1259
base.tt.new_file('d', base.root, 'd', 'd')
1260
base.tt.new_file('e', base.root, 'e', 'e')
1261
base.tt.new_file('f', base.root, 'f', 'f')
1262
base.tt.new_directory('g', base.root, 'g')
1263
base.tt.new_directory('h', base.root, 'h')
1265
other = TransformGroup("other", root_id)
1266
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1267
other.tt.new_file('b', other.root, 'b2', 'b')
1268
other.tt.new_file('c', other.root, 'c2', 'c')
1269
other.tt.new_file('d', other.root, 'd', 'd')
1270
other.tt.new_file('e', other.root, 'e2', 'e')
1271
other.tt.new_file('f', other.root, 'f', 'f')
1272
other.tt.new_file('g', other.root, 'g', 'g')
1273
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1274
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1276
this = TransformGroup("this", root_id)
1277
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1278
this.tt.new_file('b', this.root, 'b', 'b')
1279
this.tt.new_file('c', this.root, 'c', 'c')
1280
this.tt.new_file('d', this.root, 'd2', 'd')
1281
this.tt.new_file('e', this.root, 'e2', 'e')
1282
this.tt.new_file('f', this.root, 'f', 'f')
1283
this.tt.new_file('g', this.root, 'g', 'g')
1284
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1285
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1287
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1290
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1291
# three-way text conflict
1292
self.assertEqual(this.wt.get_file('b').read(),
1293
conflict_text('b', 'b2'))
1295
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1297
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1298
# Ambigious clean merge
1299
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1301
self.assertEqual(this.wt.get_file('f').read(), 'f')
1302
# Correct correct results when THIS == OTHER
1303
self.assertEqual(this.wt.get_file('g').read(), 'g')
1304
# Text conflict when THIS & OTHER are text and BASE is dir
1305
self.assertEqual(this.wt.get_file('h').read(),
1306
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1307
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1309
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1311
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1312
self.assertEqual(this.wt.get_file('i').read(),
1313
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1314
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1316
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1318
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1319
modified = ['a', 'b', 'c', 'h', 'i']
1320
merge_modified = this.wt.merge_modified()
1321
self.assertSubset(merge_modified, modified)
1322
self.assertEqual(len(merge_modified), len(modified))
1323
file(this.wt.id2abspath('a'), 'wb').write('booga')
1325
merge_modified = this.wt.merge_modified()
1326
self.assertSubset(merge_modified, modified)
1327
self.assertEqual(len(merge_modified), len(modified))
1331
def test_file_merge(self):
1332
self.requireFeature(SymlinkFeature)
1333
root_id = generate_ids.gen_root_id()
1334
base = TransformGroup("BASE", root_id)
1335
this = TransformGroup("THIS", root_id)
1336
other = TransformGroup("OTHER", root_id)
1337
for tg in this, base, other:
1338
tg.tt.new_directory('a', tg.root, 'a')
1339
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1340
tg.tt.new_file('c', tg.root, 'c', 'c')
1341
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1342
targets = ((base, 'base-e', 'base-f', None, None),
1343
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1344
(other, 'other-e', None, 'other-g', 'other-h'))
1345
for tg, e_target, f_target, g_target, h_target in targets:
1346
for link, target in (('e', e_target), ('f', f_target),
1347
('g', g_target), ('h', h_target)):
1348
if target is not None:
1349
tg.tt.new_symlink(link, tg.root, target, link)
1351
for tg in this, base, other:
1353
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1354
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1355
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1356
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1357
for suffix in ('THIS', 'BASE', 'OTHER'):
1358
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1359
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1360
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1361
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1362
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1363
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1364
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1365
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1366
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1367
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1368
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1369
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1370
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1371
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1373
def test_filename_merge(self):
1374
root_id = generate_ids.gen_root_id()
1375
base = TransformGroup("BASE", root_id)
1376
this = TransformGroup("THIS", root_id)
1377
other = TransformGroup("OTHER", root_id)
1378
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1379
for t in [base, this, other]]
1380
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1381
for t in [base, this, other]]
1382
base.tt.new_directory('c', base_a, 'c')
1383
this.tt.new_directory('c1', this_a, 'c')
1384
other.tt.new_directory('c', other_b, 'c')
1386
base.tt.new_directory('d', base_a, 'd')
1387
this.tt.new_directory('d1', this_b, 'd')
1388
other.tt.new_directory('d', other_a, 'd')
1390
base.tt.new_directory('e', base_a, 'e')
1391
this.tt.new_directory('e', this_a, 'e')
1392
other.tt.new_directory('e1', other_b, 'e')
1394
base.tt.new_directory('f', base_a, 'f')
1395
this.tt.new_directory('f1', this_b, 'f')
1396
other.tt.new_directory('f1', other_b, 'f')
1398
for tg in [this, base, other]:
1400
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1401
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1402
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1403
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1404
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1406
def test_filename_merge_conflicts(self):
1407
root_id = generate_ids.gen_root_id()
1408
base = TransformGroup("BASE", root_id)
1409
this = TransformGroup("THIS", root_id)
1410
other = TransformGroup("OTHER", root_id)
1411
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1412
for t in [base, this, other]]
1413
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1414
for t in [base, this, other]]
1416
base.tt.new_file('g', base_a, 'g', 'g')
1417
other.tt.new_file('g1', other_b, 'g1', 'g')
1419
base.tt.new_file('h', base_a, 'h', 'h')
1420
this.tt.new_file('h1', this_b, 'h1', 'h')
1422
base.tt.new_file('i', base.root, 'i', 'i')
1423
other.tt.new_directory('i1', this_b, 'i')
1425
for tg in [this, base, other]:
1427
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1429
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1430
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1431
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1432
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1433
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1434
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1435
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1438
class TestBuildTree(tests.TestCaseWithTransport):
1440
def test_build_tree_with_symlinks(self):
1441
self.requireFeature(SymlinkFeature)
1443
a = BzrDir.create_standalone_workingtree('a')
1445
file('a/foo/bar', 'wb').write('contents')
1446
os.symlink('a/foo/bar', 'a/foo/baz')
1447
a.add(['foo', 'foo/bar', 'foo/baz'])
1448
a.commit('initial commit')
1449
b = BzrDir.create_standalone_workingtree('b')
1450
basis = a.basis_tree()
1452
self.addCleanup(basis.unlock)
1453
build_tree(basis, b)
1454
self.assertIs(os.path.isdir('b/foo'), True)
1455
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1456
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1458
def test_build_with_references(self):
1459
tree = self.make_branch_and_tree('source',
1460
format='dirstate-with-subtree')
1461
subtree = self.make_branch_and_tree('source/subtree',
1462
format='dirstate-with-subtree')
1463
tree.add_reference(subtree)
1464
tree.commit('a revision')
1465
tree.branch.create_checkout('target')
1466
self.failUnlessExists('target')
1467
self.failUnlessExists('target/subtree')
1469
def test_file_conflict_handling(self):
1470
"""Ensure that when building trees, conflict handling is done"""
1471
source = self.make_branch_and_tree('source')
1472
target = self.make_branch_and_tree('target')
1473
self.build_tree(['source/file', 'target/file'])
1474
source.add('file', 'new-file')
1475
source.commit('added file')
1476
build_tree(source.basis_tree(), target)
1477
self.assertEqual([DuplicateEntry('Moved existing file to',
1478
'file.moved', 'file', None, 'new-file')],
1480
target2 = self.make_branch_and_tree('target2')
1481
target_file = file('target2/file', 'wb')
1483
source_file = file('source/file', 'rb')
1485
target_file.write(source_file.read())
1490
build_tree(source.basis_tree(), target2)
1491
self.assertEqual([], target2.conflicts())
1493
def test_symlink_conflict_handling(self):
1494
"""Ensure that when building trees, conflict handling is done"""
1495
self.requireFeature(SymlinkFeature)
1496
source = self.make_branch_and_tree('source')
1497
os.symlink('foo', 'source/symlink')
1498
source.add('symlink', 'new-symlink')
1499
source.commit('added file')
1500
target = self.make_branch_and_tree('target')
1501
os.symlink('bar', 'target/symlink')
1502
build_tree(source.basis_tree(), target)
1503
self.assertEqual([DuplicateEntry('Moved existing file to',
1504
'symlink.moved', 'symlink', None, 'new-symlink')],
1506
target = self.make_branch_and_tree('target2')
1507
os.symlink('foo', 'target2/symlink')
1508
build_tree(source.basis_tree(), target)
1509
self.assertEqual([], target.conflicts())
1511
def test_directory_conflict_handling(self):
1512
"""Ensure that when building trees, conflict handling is done"""
1513
source = self.make_branch_and_tree('source')
1514
target = self.make_branch_and_tree('target')
1515
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1516
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1517
source.commit('added file')
1518
build_tree(source.basis_tree(), target)
1519
self.assertEqual([], target.conflicts())
1520
self.failUnlessExists('target/dir1/file')
1522
# Ensure contents are merged
1523
target = self.make_branch_and_tree('target2')
1524
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1525
build_tree(source.basis_tree(), target)
1526
self.assertEqual([], target.conflicts())
1527
self.failUnlessExists('target2/dir1/file2')
1528
self.failUnlessExists('target2/dir1/file')
1530
# Ensure new contents are suppressed for existing branches
1531
target = self.make_branch_and_tree('target3')
1532
self.make_branch('target3/dir1')
1533
self.build_tree(['target3/dir1/file2'])
1534
build_tree(source.basis_tree(), target)
1535
self.failIfExists('target3/dir1/file')
1536
self.failUnlessExists('target3/dir1/file2')
1537
self.failUnlessExists('target3/dir1.diverted/file')
1538
self.assertEqual([DuplicateEntry('Diverted to',
1539
'dir1.diverted', 'dir1', 'new-dir1', None)],
1542
target = self.make_branch_and_tree('target4')
1543
self.build_tree(['target4/dir1/'])
1544
self.make_branch('target4/dir1/file')
1545
build_tree(source.basis_tree(), target)
1546
self.failUnlessExists('target4/dir1/file')
1547
self.assertEqual('directory', file_kind('target4/dir1/file'))
1548
self.failUnlessExists('target4/dir1/file.diverted')
1549
self.assertEqual([DuplicateEntry('Diverted to',
1550
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1553
def test_mixed_conflict_handling(self):
1554
"""Ensure that when building trees, conflict handling is done"""
1555
source = self.make_branch_and_tree('source')
1556
target = self.make_branch_and_tree('target')
1557
self.build_tree(['source/name', 'target/name/'])
1558
source.add('name', 'new-name')
1559
source.commit('added file')
1560
build_tree(source.basis_tree(), target)
1561
self.assertEqual([DuplicateEntry('Moved existing file to',
1562
'name.moved', 'name', None, 'new-name')], target.conflicts())
1564
def test_raises_in_populated(self):
1565
source = self.make_branch_and_tree('source')
1566
self.build_tree(['source/name'])
1568
source.commit('added name')
1569
target = self.make_branch_and_tree('target')
1570
self.build_tree(['target/name'])
1572
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1573
build_tree, source.basis_tree(), target)
1575
def test_build_tree_rename_count(self):
1576
source = self.make_branch_and_tree('source')
1577
self.build_tree(['source/file1', 'source/dir1/'])
1578
source.add(['file1', 'dir1'])
1579
source.commit('add1')
1580
target1 = self.make_branch_and_tree('target1')
1581
transform_result = build_tree(source.basis_tree(), target1)
1582
self.assertEqual(2, transform_result.rename_count)
1584
self.build_tree(['source/dir1/file2'])
1585
source.add(['dir1/file2'])
1586
source.commit('add3')
1587
target2 = self.make_branch_and_tree('target2')
1588
transform_result = build_tree(source.basis_tree(), target2)
1589
# children of non-root directories should not be renamed
1590
self.assertEqual(2, transform_result.rename_count)
1592
def create_ab_tree(self):
1593
"""Create a committed test tree with two files"""
1594
source = self.make_branch_and_tree('source')
1595
self.build_tree_contents([('source/file1', 'A')])
1596
self.build_tree_contents([('source/file2', 'B')])
1597
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1598
source.commit('commit files')
1600
self.addCleanup(source.unlock)
1603
def test_build_tree_accelerator_tree(self):
1604
source = self.create_ab_tree()
1605
self.build_tree_contents([('source/file2', 'C')])
1607
real_source_get_file = source.get_file
1608
def get_file(file_id, path=None):
1609
calls.append(file_id)
1610
return real_source_get_file(file_id, path)
1611
source.get_file = get_file
1612
target = self.make_branch_and_tree('target')
1613
revision_tree = source.basis_tree()
1614
revision_tree.lock_read()
1615
self.addCleanup(revision_tree.unlock)
1616
build_tree(revision_tree, target, source)
1617
self.assertEqual(['file1-id'], calls)
1619
self.addCleanup(target.unlock)
1620
self.assertEqual([], list(target.iter_changes(revision_tree)))
1622
def test_build_tree_accelerator_tree_missing_file(self):
1623
source = self.create_ab_tree()
1624
os.unlink('source/file1')
1625
source.remove(['file2'])
1626
target = self.make_branch_and_tree('target')
1627
revision_tree = source.basis_tree()
1628
revision_tree.lock_read()
1629
self.addCleanup(revision_tree.unlock)
1630
build_tree(revision_tree, target, source)
1632
self.addCleanup(target.unlock)
1633
self.assertEqual([], list(target.iter_changes(revision_tree)))
1635
def test_build_tree_accelerator_wrong_kind(self):
1636
self.requireFeature(SymlinkFeature)
1637
source = self.make_branch_and_tree('source')
1638
self.build_tree_contents([('source/file1', '')])
1639
self.build_tree_contents([('source/file2', '')])
1640
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1641
source.commit('commit files')
1642
os.unlink('source/file2')
1643
self.build_tree_contents([('source/file2/', 'C')])
1644
os.unlink('source/file1')
1645
os.symlink('file2', 'source/file1')
1647
real_source_get_file = source.get_file
1648
def get_file(file_id, path=None):
1649
calls.append(file_id)
1650
return real_source_get_file(file_id, path)
1651
source.get_file = get_file
1652
target = self.make_branch_and_tree('target')
1653
revision_tree = source.basis_tree()
1654
revision_tree.lock_read()
1655
self.addCleanup(revision_tree.unlock)
1656
build_tree(revision_tree, target, source)
1657
self.assertEqual([], calls)
1659
self.addCleanup(target.unlock)
1660
self.assertEqual([], list(target.iter_changes(revision_tree)))
1662
def test_build_tree_hardlink(self):
1663
self.requireFeature(HardlinkFeature)
1664
source = self.create_ab_tree()
1665
target = self.make_branch_and_tree('target')
1666
revision_tree = source.basis_tree()
1667
revision_tree.lock_read()
1668
self.addCleanup(revision_tree.unlock)
1669
build_tree(revision_tree, target, source, hardlink=True)
1671
self.addCleanup(target.unlock)
1672
self.assertEqual([], list(target.iter_changes(revision_tree)))
1673
source_stat = os.stat('source/file1')
1674
target_stat = os.stat('target/file1')
1675
self.assertEqual(source_stat, target_stat)
1677
# Explicitly disallowing hardlinks should prevent them.
1678
target2 = self.make_branch_and_tree('target2')
1679
build_tree(revision_tree, target2, source, hardlink=False)
1681
self.addCleanup(target2.unlock)
1682
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1683
source_stat = os.stat('source/file1')
1684
target2_stat = os.stat('target2/file1')
1685
self.assertNotEqual(source_stat, target2_stat)
1687
def test_build_tree_accelerator_tree_moved(self):
1688
source = self.make_branch_and_tree('source')
1689
self.build_tree_contents([('source/file1', 'A')])
1690
source.add(['file1'], ['file1-id'])
1691
source.commit('commit files')
1692
source.rename_one('file1', 'file2')
1694
self.addCleanup(source.unlock)
1695
target = self.make_branch_and_tree('target')
1696
revision_tree = source.basis_tree()
1697
revision_tree.lock_read()
1698
self.addCleanup(revision_tree.unlock)
1699
build_tree(revision_tree, target, source)
1701
self.addCleanup(target.unlock)
1702
self.assertEqual([], list(target.iter_changes(revision_tree)))
1704
def test_build_tree_hardlinks_preserve_execute(self):
1705
self.requireFeature(HardlinkFeature)
1706
source = self.create_ab_tree()
1707
tt = TreeTransform(source)
1708
trans_id = tt.trans_id_tree_file_id('file1-id')
1709
tt.set_executability(True, trans_id)
1711
self.assertTrue(source.is_executable('file1-id'))
1712
target = self.make_branch_and_tree('target')
1713
revision_tree = source.basis_tree()
1714
revision_tree.lock_read()
1715
self.addCleanup(revision_tree.unlock)
1716
build_tree(revision_tree, target, source, hardlink=True)
1718
self.addCleanup(target.unlock)
1719
self.assertEqual([], list(target.iter_changes(revision_tree)))
1720
self.assertTrue(source.is_executable('file1-id'))
1723
class MockTransform(object):
1725
def has_named_child(self, by_parent, parent_id, name):
1726
for child_id in by_parent[parent_id]:
1730
elif name == "name.~%s~" % child_id:
1735
class MockEntry(object):
1737
object.__init__(self)
1741
class TestGetBackupName(TestCase):
1742
def test_get_backup_name(self):
1743
tt = MockTransform()
1744
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1745
self.assertEqual(name, 'name.~1~')
1746
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1747
self.assertEqual(name, 'name.~2~')
1748
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1749
self.assertEqual(name, 'name.~1~')
1750
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1751
self.assertEqual(name, 'name.~1~')
1752
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1753
self.assertEqual(name, 'name.~4~')
1756
class TestFileMover(tests.TestCaseWithTransport):
1758
def test_file_mover(self):
1759
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1760
mover = _FileMover()
1761
mover.rename('a', 'q')
1762
self.failUnlessExists('q')
1763
self.failIfExists('a')
1764
self.failUnlessExists('q/b')
1765
self.failUnlessExists('c')
1766
self.failUnlessExists('c/d')
1768
def test_pre_delete_rollback(self):
1769
self.build_tree(['a/'])
1770
mover = _FileMover()
1771
mover.pre_delete('a', 'q')
1772
self.failUnlessExists('q')
1773
self.failIfExists('a')
1775
self.failIfExists('q')
1776
self.failUnlessExists('a')
1778
def test_apply_deletions(self):
1779
self.build_tree(['a/', 'b/'])
1780
mover = _FileMover()
1781
mover.pre_delete('a', 'q')
1782
mover.pre_delete('b', 'r')
1783
self.failUnlessExists('q')
1784
self.failUnlessExists('r')
1785
self.failIfExists('a')
1786
self.failIfExists('b')
1787
mover.apply_deletions()
1788
self.failIfExists('q')
1789
self.failIfExists('r')
1790
self.failIfExists('a')
1791
self.failIfExists('b')
1793
def test_file_mover_rollback(self):
1794
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1795
mover = _FileMover()
1796
mover.rename('c/d', 'c/f')
1797
mover.rename('c/e', 'c/d')
1799
mover.rename('a', 'c')
1800
except errors.FileExists, e:
1802
self.failUnlessExists('a')
1803
self.failUnlessExists('c/d')
1806
class Bogus(Exception):
1810
class TestTransformRollback(tests.TestCaseWithTransport):
1812
class ExceptionFileMover(_FileMover):
1814
def __init__(self, bad_source=None, bad_target=None):
1815
_FileMover.__init__(self)
1816
self.bad_source = bad_source
1817
self.bad_target = bad_target
1819
def rename(self, source, target):
1820
if (self.bad_source is not None and
1821
source.endswith(self.bad_source)):
1823
elif (self.bad_target is not None and
1824
target.endswith(self.bad_target)):
1827
_FileMover.rename(self, source, target)
1829
def test_rollback_rename(self):
1830
tree = self.make_branch_and_tree('.')
1831
self.build_tree(['a/', 'a/b'])
1832
tt = TreeTransform(tree)
1833
self.addCleanup(tt.finalize)
1834
a_id = tt.trans_id_tree_path('a')
1835
tt.adjust_path('c', tt.root, a_id)
1836
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1837
self.assertRaises(Bogus, tt.apply,
1838
_mover=self.ExceptionFileMover(bad_source='a'))
1839
self.failUnlessExists('a')
1840
self.failUnlessExists('a/b')
1842
self.failUnlessExists('c')
1843
self.failUnlessExists('c/d')
1845
def test_rollback_rename_into_place(self):
1846
tree = self.make_branch_and_tree('.')
1847
self.build_tree(['a/', 'a/b'])
1848
tt = TreeTransform(tree)
1849
self.addCleanup(tt.finalize)
1850
a_id = tt.trans_id_tree_path('a')
1851
tt.adjust_path('c', tt.root, a_id)
1852
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1853
self.assertRaises(Bogus, tt.apply,
1854
_mover=self.ExceptionFileMover(bad_target='c/d'))
1855
self.failUnlessExists('a')
1856
self.failUnlessExists('a/b')
1858
self.failUnlessExists('c')
1859
self.failUnlessExists('c/d')
1861
def test_rollback_deletion(self):
1862
tree = self.make_branch_and_tree('.')
1863
self.build_tree(['a/', 'a/b'])
1864
tt = TreeTransform(tree)
1865
self.addCleanup(tt.finalize)
1866
a_id = tt.trans_id_tree_path('a')
1867
tt.delete_contents(a_id)
1868
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1869
self.assertRaises(Bogus, tt.apply,
1870
_mover=self.ExceptionFileMover(bad_target='d'))
1871
self.failUnlessExists('a')
1872
self.failUnlessExists('a/b')
1874
def test_resolve_no_parent(self):
1875
wt = self.make_branch_and_tree('.')
1876
tt = TreeTransform(wt)
1877
self.addCleanup(tt.finalize)
1878
parent = tt.trans_id_file_id('parent-id')
1879
tt.new_file('file', parent, 'Contents')
1880
resolve_conflicts(tt)
1883
class TestTransformPreview(tests.TestCaseWithTransport):
1885
def create_tree(self):
1886
tree = self.make_branch_and_tree('.')
1887
self.build_tree_contents([('a', 'content 1')])
1888
tree.add('a', 'a-id')
1889
tree.commit('rev1', rev_id='rev1')
1890
return tree.branch.repository.revision_tree('rev1')
1892
def get_empty_preview(self):
1893
repository = self.make_repository('repo')
1894
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
1895
preview = TransformPreview(tree)
1896
self.addCleanup(preview.finalize)
1899
def test_transform_preview(self):
1900
revision_tree = self.create_tree()
1901
preview = TransformPreview(revision_tree)
1902
self.addCleanup(preview.finalize)
1904
def test_transform_preview_tree(self):
1905
revision_tree = self.create_tree()
1906
preview = TransformPreview(revision_tree)
1907
self.addCleanup(preview.finalize)
1908
preview.get_preview_tree()
1910
def test_transform_new_file(self):
1911
revision_tree = self.create_tree()
1912
preview = TransformPreview(revision_tree)
1913
self.addCleanup(preview.finalize)
1914
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1915
preview_tree = preview.get_preview_tree()
1916
self.assertEqual(preview_tree.kind('file2-id'), 'file')
1918
preview_tree.get_file('file2-id').read(), 'content B\n')
1920
def test_diff_preview_tree(self):
1921
revision_tree = self.create_tree()
1922
preview = TransformPreview(revision_tree)
1923
self.addCleanup(preview.finalize)
1924
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1925
preview_tree = preview.get_preview_tree()
1927
show_diff_trees(revision_tree, preview_tree, out)
1928
lines = out.getvalue().splitlines()
1929
self.assertEqual(lines[0], "=== added file 'file2'")
1930
# 3 lines of diff administrivia
1931
self.assertEqual(lines[4], "+content B")
1933
def test_transform_conflicts(self):
1934
revision_tree = self.create_tree()
1935
preview = TransformPreview(revision_tree)
1936
self.addCleanup(preview.finalize)
1937
preview.new_file('a', preview.root, 'content 2')
1938
resolve_conflicts(preview)
1939
trans_id = preview.trans_id_file_id('a-id')
1940
self.assertEqual('a.moved', preview.final_name(trans_id))
1942
def get_tree_and_preview_tree(self):
1943
revision_tree = self.create_tree()
1944
preview = TransformPreview(revision_tree)
1945
self.addCleanup(preview.finalize)
1946
a_trans_id = preview.trans_id_file_id('a-id')
1947
preview.delete_contents(a_trans_id)
1948
preview.create_file('b content', a_trans_id)
1949
preview_tree = preview.get_preview_tree()
1950
return revision_tree, preview_tree
1952
def test_iter_changes(self):
1953
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1954
root = revision_tree.inventory.root.file_id
1955
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
1956
(root, root), ('a', 'a'), ('file', 'file'),
1958
list(preview_tree.iter_changes(revision_tree)))
1960
def test_wrong_tree_value_error(self):
1961
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1962
e = self.assertRaises(ValueError, preview_tree.iter_changes,
1964
self.assertEqual('from_tree must be transform source tree.', str(e))
1966
def test_include_unchanged_value_error(self):
1967
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1968
e = self.assertRaises(ValueError, preview_tree.iter_changes,
1969
revision_tree, include_unchanged=True)
1970
self.assertEqual('include_unchanged is not supported', str(e))
1972
def test_specific_files(self):
1973
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1974
e = self.assertRaises(ValueError, preview_tree.iter_changes,
1975
revision_tree, specific_files=['pete'])
1976
self.assertEqual('specific_files is not supported', str(e))
1978
def test_want_unversioned_value_error(self):
1979
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1980
e = self.assertRaises(ValueError, preview_tree.iter_changes,
1981
revision_tree, want_unversioned=True)
1982
self.assertEqual('want_unversioned is not supported', str(e))
1984
def test_ignore_extra_trees_no_specific_files(self):
1985
# extra_trees is harmless without specific_files, so we'll silently
1986
# accept it, even though we won't use it.
1987
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1988
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
1990
def test_ignore_require_versioned_no_specific_files(self):
1991
# require_versioned is meaningless without specific_files.
1992
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1993
preview_tree.iter_changes(revision_tree, require_versioned=False)
1995
def test_ignore_pb(self):
1996
# pb could be supported, but TT.iter_changes doesn't support it.
1997
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1998
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2000
def test_kind(self):
2001
revision_tree = self.create_tree()
2002
preview = TransformPreview(revision_tree)
2003
self.addCleanup(preview.finalize)
2004
preview.new_file('file', preview.root, 'contents', 'file-id')
2005
preview.new_directory('directory', preview.root, 'dir-id')
2006
preview_tree = preview.get_preview_tree()
2007
self.assertEqual('file', preview_tree.kind('file-id'))
2008
self.assertEqual('directory', preview_tree.kind('dir-id'))
2010
def test_get_file_mtime(self):
2011
preview = self.get_empty_preview()
2012
file_trans_id = preview.new_file('file', preview.root, 'contents',
2014
limbo_path = preview._limbo_name(file_trans_id)
2015
preview_tree = preview.get_preview_tree()
2016
self.assertEqual(os.stat(limbo_path).st_mtime,
2017
preview_tree.get_file_mtime('file-id'))
2019
def test_get_file(self):
2020
preview = self.get_empty_preview()
2021
preview.new_file('file', preview.root, 'contents', 'file-id')
2022
preview_tree = preview.get_preview_tree()
2023
tree_file = preview_tree.get_file('file-id')
2025
self.assertEqual('contents', tree_file.read())
2029
def test_get_symlink_target(self):
2030
self.requireFeature(SymlinkFeature)
2031
preview = self.get_empty_preview()
2032
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2033
preview_tree = preview.get_preview_tree()
2034
self.assertEqual('target',
2035
preview_tree.get_symlink_target('symlink-id'))