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
28
from bzrlib.bzrdir import BzrDir
29
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
UnversionedParent, ParentLoop, DeletingParent,)
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
ReusingTransform, CantMoveRoot,
33
PathsNotVersionedError, ExistingLimbo,
34
ExistingPendingDeletion, ImmortalLimbo,
35
ImmortalPendingDeletion, LockError)
36
from bzrlib.osutils import file_kind, pathjoin
37
from bzrlib.merge import Merge3Merger
38
from bzrlib.tests import (
39
CaseInsensitiveFilesystemFeature,
45
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
46
resolve_conflicts, cook_conflicts,
47
find_interesting, build_tree, get_backup_name,
48
change_entry, _FileMover, resolve_checkout)
51
class TestTreeTransform(tests.TestCaseWithTransport):
54
super(TestTreeTransform, self).setUp()
55
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
58
def get_transform(self):
59
transform = TreeTransform(self.wt)
60
#self.addCleanup(transform.finalize)
61
return transform, transform.root
63
def test_existing_limbo(self):
64
transform, root = self.get_transform()
65
limbo_name = transform._limbodir
66
deletion_path = transform._deletiondir
67
os.mkdir(pathjoin(limbo_name, 'hehe'))
68
self.assertRaises(ImmortalLimbo, transform.apply)
69
self.assertRaises(LockError, self.wt.unlock)
70
self.assertRaises(ExistingLimbo, self.get_transform)
71
self.assertRaises(LockError, self.wt.unlock)
72
os.rmdir(pathjoin(limbo_name, 'hehe'))
74
os.rmdir(deletion_path)
75
transform, root = self.get_transform()
78
def test_existing_pending_deletion(self):
79
transform, root = self.get_transform()
80
deletion_path = self._limbodir = urlutils.local_path_from_url(
81
transform._tree._control_files.controlfilename('pending-deletion'))
82
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
83
self.assertRaises(ImmortalPendingDeletion, transform.apply)
84
self.assertRaises(LockError, self.wt.unlock)
85
self.assertRaises(ExistingPendingDeletion, self.get_transform)
88
transform, root = self.get_transform()
89
self.wt.lock_tree_write()
90
self.addCleanup(self.wt.unlock)
91
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
92
imaginary_id = transform.trans_id_tree_path('imaginary')
93
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
94
self.assertEqual(imaginary_id, imaginary_id2)
95
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
96
self.assertEqual(transform.final_kind(root), 'directory')
97
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
98
trans_id = transform.create_path('name', root)
99
self.assertIs(transform.final_file_id(trans_id), None)
100
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
101
transform.create_file('contents', trans_id)
102
transform.set_executability(True, trans_id)
103
transform.version_file('my_pretties', trans_id)
104
self.assertRaises(DuplicateKey, transform.version_file,
105
'my_pretties', trans_id)
106
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
107
self.assertEqual(transform.final_parent(trans_id), root)
108
self.assertIs(transform.final_parent(root), ROOT_PARENT)
109
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
110
oz_id = transform.create_path('oz', root)
111
transform.create_directory(oz_id)
112
transform.version_file('ozzie', oz_id)
113
trans_id2 = transform.create_path('name2', root)
114
transform.create_file('contents', trans_id2)
115
transform.set_executability(False, trans_id2)
116
transform.version_file('my_pretties2', trans_id2)
117
modified_paths = transform.apply().modified_paths
118
self.assertEqual('contents', self.wt.get_file_byname('name').read())
119
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
120
self.assertIs(self.wt.is_executable('my_pretties'), True)
121
self.assertIs(self.wt.is_executable('my_pretties2'), False)
122
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
123
self.assertEqual(len(modified_paths), 3)
124
tree_mod_paths = [self.wt.id2abspath(f) for f in
125
('ozzie', 'my_pretties', 'my_pretties2')]
126
self.assertSubset(tree_mod_paths, modified_paths)
127
# is it safe to finalize repeatedly?
131
def test_convenience(self):
132
transform, root = self.get_transform()
133
self.wt.lock_tree_write()
134
self.addCleanup(self.wt.unlock)
135
trans_id = transform.new_file('name', root, 'contents',
137
oz = transform.new_directory('oz', root, 'oz-id')
138
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
139
toto = transform.new_file('toto', dorothy, 'toto-contents',
142
self.assertEqual(len(transform.find_conflicts()), 0)
144
self.assertRaises(ReusingTransform, transform.find_conflicts)
145
self.assertEqual('contents', file(self.wt.abspath('name')).read())
146
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
147
self.assertIs(self.wt.is_executable('my_pretties'), True)
148
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
149
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
150
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
152
self.assertEqual('toto-contents',
153
self.wt.get_file_byname('oz/dorothy/toto').read())
154
self.assertIs(self.wt.is_executable('toto-id'), False)
156
def test_tree_reference(self):
157
transform, root = self.get_transform()
158
tree = transform._tree
159
trans_id = transform.new_directory('reference', root, 'subtree-id')
160
transform.set_tree_reference('subtree-revision', trans_id)
163
self.addCleanup(tree.unlock)
164
self.assertEqual('subtree-revision',
165
tree.inventory['subtree-id'].reference_revision)
167
def test_conflicts(self):
168
transform, root = self.get_transform()
169
trans_id = transform.new_file('name', root, 'contents',
171
self.assertEqual(len(transform.find_conflicts()), 0)
172
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
173
self.assertEqual(transform.find_conflicts(),
174
[('duplicate', trans_id, trans_id2, 'name')])
175
self.assertRaises(MalformedTransform, transform.apply)
176
transform.adjust_path('name', trans_id, trans_id2)
177
self.assertEqual(transform.find_conflicts(),
178
[('non-directory parent', trans_id)])
179
tinman_id = transform.trans_id_tree_path('tinman')
180
transform.adjust_path('name', tinman_id, trans_id2)
181
self.assertEqual(transform.find_conflicts(),
182
[('unversioned parent', tinman_id),
183
('missing parent', tinman_id)])
184
lion_id = transform.create_path('lion', root)
185
self.assertEqual(transform.find_conflicts(),
186
[('unversioned parent', tinman_id),
187
('missing parent', tinman_id)])
188
transform.adjust_path('name', lion_id, trans_id2)
189
self.assertEqual(transform.find_conflicts(),
190
[('unversioned parent', lion_id),
191
('missing parent', lion_id)])
192
transform.version_file("Courage", lion_id)
193
self.assertEqual(transform.find_conflicts(),
194
[('missing parent', lion_id),
195
('versioning no contents', lion_id)])
196
transform.adjust_path('name2', root, trans_id2)
197
self.assertEqual(transform.find_conflicts(),
198
[('versioning no contents', lion_id)])
199
transform.create_file('Contents, okay?', lion_id)
200
transform.adjust_path('name2', trans_id2, trans_id2)
201
self.assertEqual(transform.find_conflicts(),
202
[('parent loop', trans_id2),
203
('non-directory parent', trans_id2)])
204
transform.adjust_path('name2', root, trans_id2)
205
oz_id = transform.new_directory('oz', root)
206
transform.set_executability(True, oz_id)
207
self.assertEqual(transform.find_conflicts(),
208
[('unversioned executability', oz_id)])
209
transform.version_file('oz-id', oz_id)
210
self.assertEqual(transform.find_conflicts(),
211
[('non-file executability', oz_id)])
212
transform.set_executability(None, oz_id)
213
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
215
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
216
self.assertEqual('contents', file(self.wt.abspath('name')).read())
217
transform2, root = self.get_transform()
218
oz_id = transform2.trans_id_tree_file_id('oz-id')
219
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
220
result = transform2.find_conflicts()
221
fp = FinalPaths(transform2)
222
self.assert_('oz/tip' in transform2._tree_path_ids)
223
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
224
self.assertEqual(len(result), 2)
225
self.assertEqual((result[0][0], result[0][1]),
226
('duplicate', newtip))
227
self.assertEqual((result[1][0], result[1][2]),
228
('duplicate id', newtip))
229
transform2.finalize()
230
transform3 = TreeTransform(self.wt)
231
self.addCleanup(transform3.finalize)
232
oz_id = transform3.trans_id_tree_file_id('oz-id')
233
transform3.delete_contents(oz_id)
234
self.assertEqual(transform3.find_conflicts(),
235
[('missing parent', oz_id)])
236
root_id = transform3.root
237
tip_id = transform3.trans_id_tree_file_id('tip-id')
238
transform3.adjust_path('tip', root_id, tip_id)
241
def test_conflict_on_case_insensitive(self):
242
tree = self.make_branch_and_tree('tree')
243
# Don't try this at home, kids!
244
# Force the tree to report that it is case sensitive, for conflict
246
tree.case_sensitive = True
247
transform = TreeTransform(tree)
248
self.addCleanup(transform.finalize)
249
transform.new_file('file', transform.root, 'content')
250
transform.new_file('FiLe', transform.root, 'content')
251
result = transform.find_conflicts()
252
self.assertEqual([], result)
253
# Force the tree to report that it is case insensitive, for conflict
255
tree.case_sensitive = False
256
result = transform.find_conflicts()
257
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
259
def test_conflict_on_case_insensitive_existing(self):
260
tree = self.make_branch_and_tree('tree')
261
self.build_tree(['tree/FiLe'])
262
# Don't try this at home, kids!
263
# Force the tree to report that it is case sensitive, for conflict
265
tree.case_sensitive = True
266
transform = TreeTransform(tree)
267
self.addCleanup(transform.finalize)
268
transform.new_file('file', transform.root, 'content')
269
result = transform.find_conflicts()
270
self.assertEqual([], result)
271
# Force the tree to report that it is case insensitive, for conflict
273
tree.case_sensitive = False
274
result = transform.find_conflicts()
275
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
277
def test_resolve_case_insensitive_conflict(self):
278
tree = self.make_branch_and_tree('tree')
279
# Don't try this at home, kids!
280
# Force the tree to report that it is case insensitive, for conflict
282
tree.case_sensitive = False
283
transform = TreeTransform(tree)
284
self.addCleanup(transform.finalize)
285
transform.new_file('file', transform.root, 'content')
286
transform.new_file('FiLe', transform.root, 'content')
287
resolve_conflicts(transform)
289
self.failUnlessExists('tree/file')
290
self.failUnlessExists('tree/FiLe.moved')
292
def test_resolve_checkout_case_conflict(self):
293
tree = self.make_branch_and_tree('tree')
294
# Don't try this at home, kids!
295
# Force the tree to report that it is case insensitive, for conflict
297
tree.case_sensitive = False
298
transform = TreeTransform(tree)
299
self.addCleanup(transform.finalize)
300
transform.new_file('file', transform.root, 'content')
301
transform.new_file('FiLe', transform.root, 'content')
302
resolve_conflicts(transform,
303
pass_func=lambda t, c: resolve_checkout(t, c, []))
305
self.failUnlessExists('tree/file')
306
self.failUnlessExists('tree/FiLe.moved')
308
def test_apply_case_conflict(self):
309
"""Ensure that a transform with case conflicts can always be applied"""
310
tree = self.make_branch_and_tree('tree')
311
transform = TreeTransform(tree)
312
self.addCleanup(transform.finalize)
313
transform.new_file('file', transform.root, 'content')
314
transform.new_file('FiLe', transform.root, 'content')
315
dir = transform.new_directory('dir', transform.root)
316
transform.new_file('dirfile', dir, 'content')
317
transform.new_file('dirFiLe', dir, 'content')
318
resolve_conflicts(transform)
320
self.failUnlessExists('tree/file')
321
if not os.path.exists('tree/FiLe.moved'):
322
self.failUnlessExists('tree/FiLe')
323
self.failUnlessExists('tree/dir/dirfile')
324
if not os.path.exists('tree/dir/dirFiLe.moved'):
325
self.failUnlessExists('tree/dir/dirFiLe')
327
def test_case_insensitive_limbo(self):
328
tree = self.make_branch_and_tree('tree')
329
# Don't try this at home, kids!
330
# Force the tree to report that it is case insensitive
331
tree.case_sensitive = False
332
transform = TreeTransform(tree)
333
self.addCleanup(transform.finalize)
334
dir = transform.new_directory('dir', transform.root)
335
first = transform.new_file('file', dir, 'content')
336
second = transform.new_file('FiLe', dir, 'content')
337
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
338
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
340
def test_add_del(self):
341
start, root = self.get_transform()
342
start.new_directory('a', root, 'a')
344
transform, root = self.get_transform()
345
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
346
transform.new_directory('a', root, 'a')
349
def test_unversioning(self):
350
create_tree, root = self.get_transform()
351
parent_id = create_tree.new_directory('parent', root, 'parent-id')
352
create_tree.new_file('child', parent_id, 'child', 'child-id')
354
unversion = TreeTransform(self.wt)
355
self.addCleanup(unversion.finalize)
356
parent = unversion.trans_id_tree_path('parent')
357
unversion.unversion_file(parent)
358
self.assertEqual(unversion.find_conflicts(),
359
[('unversioned parent', parent_id)])
360
file_id = unversion.trans_id_tree_file_id('child-id')
361
unversion.unversion_file(file_id)
364
def test_name_invariants(self):
365
create_tree, root = self.get_transform()
367
root = create_tree.root
368
create_tree.new_file('name1', root, 'hello1', 'name1')
369
create_tree.new_file('name2', root, 'hello2', 'name2')
370
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
371
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
372
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
373
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
376
mangle_tree,root = self.get_transform()
377
root = mangle_tree.root
379
name1 = mangle_tree.trans_id_tree_file_id('name1')
380
name2 = mangle_tree.trans_id_tree_file_id('name2')
381
mangle_tree.adjust_path('name2', root, name1)
382
mangle_tree.adjust_path('name1', root, name2)
384
#tests for deleting parent directories
385
ddir = mangle_tree.trans_id_tree_file_id('ddir')
386
mangle_tree.delete_contents(ddir)
387
dfile = mangle_tree.trans_id_tree_file_id('dfile')
388
mangle_tree.delete_versioned(dfile)
389
mangle_tree.unversion_file(dfile)
390
mfile = mangle_tree.trans_id_tree_file_id('mfile')
391
mangle_tree.adjust_path('mfile', root, mfile)
393
#tests for adding parent directories
394
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
395
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
396
mangle_tree.adjust_path('mfile2', newdir, mfile2)
397
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
398
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
399
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
400
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
402
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
403
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
404
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
405
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
406
self.assertEqual(file(mfile2_path).read(), 'later2')
407
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
408
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
409
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
410
self.assertEqual(file(newfile_path).read(), 'hello3')
411
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
412
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
413
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
415
def test_both_rename(self):
416
create_tree,root = self.get_transform()
417
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
418
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
420
mangle_tree,root = self.get_transform()
421
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
422
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
423
mangle_tree.adjust_path('test', root, selftest)
424
mangle_tree.adjust_path('test_too_much', root, selftest)
425
mangle_tree.set_executability(True, blackbox)
428
def test_both_rename2(self):
429
create_tree,root = self.get_transform()
430
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
431
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
432
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
433
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
436
mangle_tree,root = self.get_transform()
437
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
438
tests = mangle_tree.trans_id_tree_file_id('tests-id')
439
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
440
mangle_tree.adjust_path('selftest', bzrlib, tests)
441
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
442
mangle_tree.set_executability(True, test_too_much)
445
def test_both_rename3(self):
446
create_tree,root = self.get_transform()
447
tests = create_tree.new_directory('tests', root, 'tests-id')
448
create_tree.new_file('test_too_much.py', tests, 'hello1',
451
mangle_tree,root = self.get_transform()
452
tests = mangle_tree.trans_id_tree_file_id('tests-id')
453
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
454
mangle_tree.adjust_path('selftest', root, tests)
455
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
456
mangle_tree.set_executability(True, test_too_much)
459
def test_move_dangling_ie(self):
460
create_tree, root = self.get_transform()
462
root = create_tree.root
463
create_tree.new_file('name1', root, 'hello1', 'name1')
465
delete_contents, root = self.get_transform()
466
file = delete_contents.trans_id_tree_file_id('name1')
467
delete_contents.delete_contents(file)
468
delete_contents.apply()
469
move_id, root = self.get_transform()
470
name1 = move_id.trans_id_tree_file_id('name1')
471
newdir = move_id.new_directory('dir', root, 'newdir')
472
move_id.adjust_path('name2', newdir, name1)
475
def test_replace_dangling_ie(self):
476
create_tree, root = self.get_transform()
478
root = create_tree.root
479
create_tree.new_file('name1', root, 'hello1', 'name1')
481
delete_contents = TreeTransform(self.wt)
482
self.addCleanup(delete_contents.finalize)
483
file = delete_contents.trans_id_tree_file_id('name1')
484
delete_contents.delete_contents(file)
485
delete_contents.apply()
486
delete_contents.finalize()
487
replace = TreeTransform(self.wt)
488
self.addCleanup(replace.finalize)
489
name2 = replace.new_file('name2', root, 'hello2', 'name1')
490
conflicts = replace.find_conflicts()
491
name1 = replace.trans_id_tree_file_id('name1')
492
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
493
resolve_conflicts(replace)
496
def test_symlinks(self):
497
self.requireFeature(SymlinkFeature)
498
transform,root = self.get_transform()
499
oz_id = transform.new_directory('oz', root, 'oz-id')
500
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
502
wiz_id = transform.create_path('wizard2', oz_id)
503
transform.create_symlink('behind_curtain', wiz_id)
504
transform.version_file('wiz-id2', wiz_id)
505
transform.set_executability(True, wiz_id)
506
self.assertEqual(transform.find_conflicts(),
507
[('non-file executability', wiz_id)])
508
transform.set_executability(None, wiz_id)
510
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
511
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
512
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
514
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
517
def test_unable_create_symlink(self):
519
wt = self.make_branch_and_tree('.')
520
tt = TreeTransform(wt) # TreeTransform obtains write lock
522
tt.new_symlink('foo', tt.root, 'bar')
526
os_symlink = getattr(os, 'symlink', None)
529
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
531
"Unable to create symlink 'foo' on this platform",
535
os.symlink = os_symlink
537
def get_conflicted(self):
538
create,root = self.get_transform()
539
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
540
oz = create.new_directory('oz', root, 'oz-id')
541
create.new_directory('emeraldcity', oz, 'emerald-id')
543
conflicts,root = self.get_transform()
544
# set up duplicate entry, duplicate id
545
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
547
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
548
oz = conflicts.trans_id_tree_file_id('oz-id')
549
# set up DeletedParent parent conflict
550
conflicts.delete_versioned(oz)
551
emerald = conflicts.trans_id_tree_file_id('emerald-id')
552
# set up MissingParent conflict
553
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
554
conflicts.adjust_path('munchkincity', root, munchkincity)
555
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
557
conflicts.adjust_path('emeraldcity', emerald, emerald)
558
return conflicts, emerald, oz, old_dorothy, new_dorothy
560
def test_conflict_resolution(self):
561
conflicts, emerald, oz, old_dorothy, new_dorothy =\
562
self.get_conflicted()
563
resolve_conflicts(conflicts)
564
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
565
self.assertIs(conflicts.final_file_id(old_dorothy), None)
566
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
567
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
568
self.assertEqual(conflicts.final_parent(emerald), oz)
571
def test_cook_conflicts(self):
572
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
573
raw_conflicts = resolve_conflicts(tt)
574
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
575
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
576
'dorothy', None, 'dorothy-id')
577
self.assertEqual(cooked_conflicts[0], duplicate)
578
duplicate_id = DuplicateID('Unversioned existing file',
579
'dorothy.moved', 'dorothy', None,
581
self.assertEqual(cooked_conflicts[1], duplicate_id)
582
missing_parent = MissingParent('Created directory', 'munchkincity',
584
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
585
self.assertEqual(cooked_conflicts[2], missing_parent)
586
unversioned_parent = UnversionedParent('Versioned directory',
589
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
591
self.assertEqual(cooked_conflicts[3], unversioned_parent)
592
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
593
'oz/emeraldcity', 'emerald-id', 'emerald-id')
594
self.assertEqual(cooked_conflicts[4], deleted_parent)
595
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
596
self.assertEqual(cooked_conflicts[6], parent_loop)
597
self.assertEqual(len(cooked_conflicts), 7)
600
def test_string_conflicts(self):
601
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
602
raw_conflicts = resolve_conflicts(tt)
603
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
605
conflicts_s = [str(c) for c in cooked_conflicts]
606
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
607
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
608
'Moved existing file to '
610
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
611
'Unversioned existing file '
613
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
614
' munchkincity. Created directory.')
615
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
616
' versioned, but has versioned'
617
' children. Versioned directory.')
618
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
619
" is not empty. Not deleting.")
620
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
621
' versioned, but has versioned'
622
' children. Versioned directory.')
623
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
624
' oz/emeraldcity. Cancelled move.')
626
def test_moving_versioned_directories(self):
627
create, root = self.get_transform()
628
kansas = create.new_directory('kansas', root, 'kansas-id')
629
create.new_directory('house', kansas, 'house-id')
630
create.new_directory('oz', root, 'oz-id')
632
cyclone, root = self.get_transform()
633
oz = cyclone.trans_id_tree_file_id('oz-id')
634
house = cyclone.trans_id_tree_file_id('house-id')
635
cyclone.adjust_path('house', oz, house)
638
def test_moving_root(self):
639
create, root = self.get_transform()
640
fun = create.new_directory('fun', root, 'fun-id')
641
create.new_directory('sun', root, 'sun-id')
642
create.new_directory('moon', root, 'moon')
644
transform, root = self.get_transform()
645
transform.adjust_root_path('oldroot', fun)
646
new_root=transform.trans_id_tree_path('')
647
transform.version_file('new-root', new_root)
650
def test_renames(self):
651
create, root = self.get_transform()
652
old = create.new_directory('old-parent', root, 'old-id')
653
intermediate = create.new_directory('intermediate', old, 'im-id')
654
myfile = create.new_file('myfile', intermediate, 'myfile-text',
657
rename, root = self.get_transform()
658
old = rename.trans_id_file_id('old-id')
659
rename.adjust_path('new', root, old)
660
myfile = rename.trans_id_file_id('myfile-id')
661
rename.set_executability(True, myfile)
664
def test_find_interesting(self):
665
create, root = self.get_transform()
667
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
668
create.new_file('uvfile', root, 'othertext')
670
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
671
find_interesting, wt, wt, ['vfile'])
672
self.assertEqual(result, set(['myfile-id']))
674
def test_set_executability_order(self):
675
"""Ensure that executability behaves the same, no matter what order.
677
- create file and set executability simultaneously
678
- create file and set executability afterward
679
- unsetting the executability of a file whose executability has not been
680
declared should throw an exception (this may happen when a
681
merge attempts to create a file with a duplicate ID)
683
transform, root = self.get_transform()
686
self.addCleanup(wt.unlock)
687
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
689
sac = transform.new_file('set_after_creation', root,
690
'Set after creation', 'sac')
691
transform.set_executability(True, sac)
692
uws = transform.new_file('unset_without_set', root, 'Unset badly',
694
self.assertRaises(KeyError, transform.set_executability, None, uws)
696
self.assertTrue(wt.is_executable('soc'))
697
self.assertTrue(wt.is_executable('sac'))
699
def test_preserve_mode(self):
700
"""File mode is preserved when replacing content"""
701
if sys.platform == 'win32':
702
raise TestSkipped('chmod has no effect on win32')
703
transform, root = self.get_transform()
704
transform.new_file('file1', root, 'contents', 'file1-id', True)
706
self.assertTrue(self.wt.is_executable('file1-id'))
707
transform, root = self.get_transform()
708
file1_id = transform.trans_id_tree_file_id('file1-id')
709
transform.delete_contents(file1_id)
710
transform.create_file('contents2', file1_id)
712
self.assertTrue(self.wt.is_executable('file1-id'))
714
def test__set_mode_stats_correctly(self):
715
"""_set_mode stats to determine file mode."""
716
if sys.platform == 'win32':
717
raise TestSkipped('chmod has no effect on win32')
721
def instrumented_stat(path):
722
stat_paths.append(path)
723
return real_stat(path)
725
transform, root = self.get_transform()
727
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
728
file_id='bar-id-1', executable=False)
731
transform, root = self.get_transform()
732
bar1_id = transform.trans_id_tree_path('bar')
733
bar2_id = transform.trans_id_tree_path('bar2')
735
os.stat = instrumented_stat
736
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
741
bar1_abspath = self.wt.abspath('bar')
742
self.assertEqual([bar1_abspath], stat_paths)
744
def test_iter_changes(self):
745
self.wt.set_root_id('eert_toor')
746
transform, root = self.get_transform()
747
transform.new_file('old', root, 'blah', 'id-1', True)
749
transform, root = self.get_transform()
751
self.assertEqual([], list(transform._iter_changes()))
752
old = transform.trans_id_tree_file_id('id-1')
753
transform.unversion_file(old)
754
self.assertEqual([('id-1', ('old', None), False, (True, False),
755
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
756
(True, True))], list(transform._iter_changes()))
757
transform.new_directory('new', root, 'id-1')
758
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
759
('eert_toor', 'eert_toor'), ('old', 'new'),
760
('file', 'directory'),
761
(True, False))], list(transform._iter_changes()))
765
def test_iter_changes_new(self):
766
self.wt.set_root_id('eert_toor')
767
transform, root = self.get_transform()
768
transform.new_file('old', root, 'blah')
770
transform, root = self.get_transform()
772
old = transform.trans_id_tree_path('old')
773
transform.version_file('id-1', old)
774
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
775
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
776
(False, False))], list(transform._iter_changes()))
780
def test_iter_changes_modifications(self):
781
self.wt.set_root_id('eert_toor')
782
transform, root = self.get_transform()
783
transform.new_file('old', root, 'blah', 'id-1')
784
transform.new_file('new', root, 'blah')
785
transform.new_directory('subdir', root, 'subdir-id')
787
transform, root = self.get_transform()
789
old = transform.trans_id_tree_path('old')
790
subdir = transform.trans_id_tree_file_id('subdir-id')
791
new = transform.trans_id_tree_path('new')
792
self.assertEqual([], list(transform._iter_changes()))
795
transform.delete_contents(old)
796
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
797
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
798
(False, False))], list(transform._iter_changes()))
801
transform.create_file('blah', old)
802
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
803
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
804
(False, False))], list(transform._iter_changes()))
805
transform.cancel_deletion(old)
806
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
807
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
808
(False, False))], list(transform._iter_changes()))
809
transform.cancel_creation(old)
811
# move file_id to a different file
812
self.assertEqual([], list(transform._iter_changes()))
813
transform.unversion_file(old)
814
transform.version_file('id-1', new)
815
transform.adjust_path('old', root, new)
816
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
817
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
818
(False, False))], list(transform._iter_changes()))
819
transform.cancel_versioning(new)
820
transform._removed_id = set()
823
self.assertEqual([], list(transform._iter_changes()))
824
transform.set_executability(True, old)
825
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
826
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
827
(False, True))], list(transform._iter_changes()))
828
transform.set_executability(None, old)
831
self.assertEqual([], list(transform._iter_changes()))
832
transform.adjust_path('new', root, old)
833
transform._new_parent = {}
834
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
835
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
836
(False, False))], list(transform._iter_changes()))
837
transform._new_name = {}
840
self.assertEqual([], list(transform._iter_changes()))
841
transform.adjust_path('new', subdir, old)
842
transform._new_name = {}
843
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
844
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
845
('file', 'file'), (False, False))],
846
list(transform._iter_changes()))
847
transform._new_path = {}
852
def test_iter_changes_modified_bleed(self):
853
self.wt.set_root_id('eert_toor')
854
"""Modified flag should not bleed from one change to another"""
855
# unfortunately, we have no guarantee that file1 (which is modified)
856
# will be applied before file2. And if it's applied after file2, it
857
# obviously can't bleed into file2's change output. But for now, it
859
transform, root = self.get_transform()
860
transform.new_file('file1', root, 'blah', 'id-1')
861
transform.new_file('file2', root, 'blah', 'id-2')
863
transform, root = self.get_transform()
865
transform.delete_contents(transform.trans_id_file_id('id-1'))
866
transform.set_executability(True,
867
transform.trans_id_file_id('id-2'))
868
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
869
('eert_toor', 'eert_toor'), ('file1', u'file1'),
870
('file', None), (False, False)),
871
('id-2', (u'file2', u'file2'), False, (True, True),
872
('eert_toor', 'eert_toor'), ('file2', u'file2'),
873
('file', 'file'), (False, True))],
874
list(transform._iter_changes()))
878
def test_iter_changes_move_missing(self):
879
"""Test moving ids with no files around"""
880
self.wt.set_root_id('toor_eert')
881
# Need two steps because versioning a non-existant file is a conflict.
882
transform, root = self.get_transform()
883
transform.new_directory('floater', root, 'floater-id')
885
transform, root = self.get_transform()
886
transform.delete_contents(transform.trans_id_tree_path('floater'))
888
transform, root = self.get_transform()
889
floater = transform.trans_id_tree_path('floater')
891
transform.adjust_path('flitter', root, floater)
892
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
893
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
894
(None, None), (False, False))], list(transform._iter_changes()))
898
def test_iter_changes_pointless(self):
899
"""Ensure that no-ops are not treated as modifications"""
900
self.wt.set_root_id('eert_toor')
901
transform, root = self.get_transform()
902
transform.new_file('old', root, 'blah', 'id-1')
903
transform.new_directory('subdir', root, 'subdir-id')
905
transform, root = self.get_transform()
907
old = transform.trans_id_tree_path('old')
908
subdir = transform.trans_id_tree_file_id('subdir-id')
909
self.assertEqual([], list(transform._iter_changes()))
910
transform.delete_contents(subdir)
911
transform.create_directory(subdir)
912
transform.set_executability(False, old)
913
transform.unversion_file(old)
914
transform.version_file('id-1', old)
915
transform.adjust_path('old', root, old)
916
self.assertEqual([], list(transform._iter_changes()))
920
def test_rename_count(self):
921
transform, root = self.get_transform()
922
transform.new_file('name1', root, 'contents')
923
self.assertEqual(transform.rename_count, 0)
925
self.assertEqual(transform.rename_count, 1)
926
transform2, root = self.get_transform()
927
transform2.adjust_path('name2', root,
928
transform2.trans_id_tree_path('name1'))
929
self.assertEqual(transform2.rename_count, 0)
931
self.assertEqual(transform2.rename_count, 2)
933
def test_change_parent(self):
934
"""Ensure that after we change a parent, the results are still right.
936
Renames and parent changes on pending transforms can happen as part
937
of conflict resolution, and are explicitly permitted by the
940
This test ensures they work correctly with the rename-avoidance
943
transform, root = self.get_transform()
944
parent1 = transform.new_directory('parent1', root)
945
child1 = transform.new_file('child1', parent1, 'contents')
946
parent2 = transform.new_directory('parent2', root)
947
transform.adjust_path('child1', parent2, child1)
949
self.failIfExists(self.wt.abspath('parent1/child1'))
950
self.failUnlessExists(self.wt.abspath('parent2/child1'))
951
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
952
# no rename for child1 (counting only renames during apply)
953
self.failUnlessEqual(2, transform.rename_count)
955
def test_cancel_parent(self):
956
"""Cancelling a parent doesn't cause deletion of a non-empty directory
958
This is like the test_change_parent, except that we cancel the parent
959
before adjusting the path. The transform must detect that the
960
directory is non-empty, and move children to safe locations.
962
transform, root = self.get_transform()
963
parent1 = transform.new_directory('parent1', root)
964
child1 = transform.new_file('child1', parent1, 'contents')
965
child2 = transform.new_file('child2', parent1, 'contents')
967
transform.cancel_creation(parent1)
969
self.fail('Failed to move child1 before deleting parent1')
970
transform.cancel_creation(child2)
971
transform.create_directory(parent1)
973
transform.cancel_creation(parent1)
974
# If the transform incorrectly believes that child2 is still in
975
# parent1's limbo directory, it will try to rename it and fail
976
# because was already moved by the first cancel_creation.
978
self.fail('Transform still thinks child2 is a child of parent1')
979
parent2 = transform.new_directory('parent2', root)
980
transform.adjust_path('child1', parent2, child1)
982
self.failIfExists(self.wt.abspath('parent1'))
983
self.failUnlessExists(self.wt.abspath('parent2/child1'))
984
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
985
self.failUnlessEqual(2, transform.rename_count)
987
def test_adjust_and_cancel(self):
988
"""Make sure adjust_path keeps track of limbo children properly"""
989
transform, root = self.get_transform()
990
parent1 = transform.new_directory('parent1', root)
991
child1 = transform.new_file('child1', parent1, 'contents')
992
parent2 = transform.new_directory('parent2', root)
993
transform.adjust_path('child1', parent2, child1)
994
transform.cancel_creation(child1)
996
transform.cancel_creation(parent1)
997
# if the transform thinks child1 is still in parent1's limbo
998
# directory, it will attempt to move it and fail.
1000
self.fail('Transform still thinks child1 is a child of parent1')
1001
transform.finalize()
1003
def test_noname_contents(self):
1004
"""TreeTransform should permit deferring naming files."""
1005
transform, root = self.get_transform()
1006
parent = transform.trans_id_file_id('parent-id')
1008
transform.create_directory(parent)
1010
self.fail("Can't handle contents with no name")
1011
transform.finalize()
1013
def test_noname_contents_nested(self):
1014
"""TreeTransform should permit deferring naming files."""
1015
transform, root = self.get_transform()
1016
parent = transform.trans_id_file_id('parent-id')
1018
transform.create_directory(parent)
1020
self.fail("Can't handle contents with no name")
1021
child = transform.new_directory('child', parent)
1022
transform.adjust_path('parent', root, parent)
1024
self.failUnlessExists(self.wt.abspath('parent/child'))
1025
self.assertEqual(1, transform.rename_count)
1027
def test_reuse_name(self):
1028
"""Avoid reusing the same limbo name for different files"""
1029
transform, root = self.get_transform()
1030
parent = transform.new_directory('parent', root)
1031
child1 = transform.new_directory('child', parent)
1033
child2 = transform.new_directory('child', parent)
1035
self.fail('Tranform tried to use the same limbo name twice')
1036
transform.adjust_path('child2', parent, child2)
1038
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1039
# child2 is put into top-level limbo because child1 has already
1040
# claimed the direct limbo path when child2 is created. There is no
1041
# advantage in renaming files once they're in top-level limbo, except
1043
self.assertEqual(2, transform.rename_count)
1045
def test_reuse_when_first_moved(self):
1046
"""Don't avoid direct paths when it is safe to use them"""
1047
transform, root = self.get_transform()
1048
parent = transform.new_directory('parent', root)
1049
child1 = transform.new_directory('child', parent)
1050
transform.adjust_path('child1', parent, child1)
1051
child2 = transform.new_directory('child', parent)
1053
# limbo/new-1 => parent
1054
self.assertEqual(1, transform.rename_count)
1056
def test_reuse_after_cancel(self):
1057
"""Don't avoid direct paths when it is safe to use them"""
1058
transform, root = self.get_transform()
1059
parent2 = transform.new_directory('parent2', root)
1060
child1 = transform.new_directory('child1', parent2)
1061
transform.cancel_creation(parent2)
1062
transform.create_directory(parent2)
1063
child2 = transform.new_directory('child1', parent2)
1064
transform.adjust_path('child2', parent2, child1)
1066
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1067
self.assertEqual(2, transform.rename_count)
1069
def test_finalize_order(self):
1070
"""Finalize must be done in child-to-parent order"""
1071
transform, root = self.get_transform()
1072
parent = transform.new_directory('parent', root)
1073
child = transform.new_directory('child', parent)
1075
transform.finalize()
1077
self.fail('Tried to remove parent before child1')
1079
def test_cancel_with_cancelled_child_should_succeed(self):
1080
transform, root = self.get_transform()
1081
parent = transform.new_directory('parent', root)
1082
child = transform.new_directory('child', parent)
1083
transform.cancel_creation(child)
1084
transform.cancel_creation(parent)
1085
transform.finalize()
1087
def test_change_entry(self):
1088
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1089
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1092
def test_case_insensitive_clash(self):
1093
self.requireFeature(CaseInsensitiveFilesystemFeature)
1095
wt = self.make_branch_and_tree('.')
1096
tt = TreeTransform(wt) # TreeTransform obtains write lock
1098
tt.new_file('foo', tt.root, 'bar')
1099
tt.new_file('Foo', tt.root, 'spam')
1100
# Lie to tt that we've already resolved all conflicts.
1101
tt.apply(no_conflicts=True)
1105
err = self.assertRaises(errors.FileExists, tt_helper)
1106
self.assertContainsRe(str(err),
1107
"^File exists: .+/foo")
1109
def test_two_directories_clash(self):
1111
wt = self.make_branch_and_tree('.')
1112
tt = TreeTransform(wt) # TreeTransform obtains write lock
1114
foo_1 = tt.new_directory('foo', tt.root)
1115
tt.new_directory('bar', foo_1)
1116
foo_2 = tt.new_directory('foo', tt.root)
1117
tt.new_directory('baz', foo_2)
1118
# Lie to tt that we've already resolved all conflicts.
1119
tt.apply(no_conflicts=True)
1123
err = self.assertRaises(errors.FileExists, tt_helper)
1124
self.assertContainsRe(str(err),
1125
"^File exists: .+/foo")
1128
class TransformGroup(object):
1130
def __init__(self, dirname, root_id):
1133
self.wt = BzrDir.create_standalone_workingtree(dirname)
1134
self.wt.set_root_id(root_id)
1135
self.b = self.wt.branch
1136
self.tt = TreeTransform(self.wt)
1137
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1140
def conflict_text(tree, merge):
1141
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1142
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1145
class TestTransformMerge(TestCaseInTempDir):
1146
def test_text_merge(self):
1147
root_id = generate_ids.gen_root_id()
1148
base = TransformGroup("base", root_id)
1149
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1150
base.tt.new_file('b', base.root, 'b1', 'b')
1151
base.tt.new_file('c', base.root, 'c', 'c')
1152
base.tt.new_file('d', base.root, 'd', 'd')
1153
base.tt.new_file('e', base.root, 'e', 'e')
1154
base.tt.new_file('f', base.root, 'f', 'f')
1155
base.tt.new_directory('g', base.root, 'g')
1156
base.tt.new_directory('h', base.root, 'h')
1158
other = TransformGroup("other", root_id)
1159
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1160
other.tt.new_file('b', other.root, 'b2', 'b')
1161
other.tt.new_file('c', other.root, 'c2', 'c')
1162
other.tt.new_file('d', other.root, 'd', 'd')
1163
other.tt.new_file('e', other.root, 'e2', 'e')
1164
other.tt.new_file('f', other.root, 'f', 'f')
1165
other.tt.new_file('g', other.root, 'g', 'g')
1166
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1167
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1169
this = TransformGroup("this", root_id)
1170
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1171
this.tt.new_file('b', this.root, 'b', 'b')
1172
this.tt.new_file('c', this.root, 'c', 'c')
1173
this.tt.new_file('d', this.root, 'd2', 'd')
1174
this.tt.new_file('e', this.root, 'e2', 'e')
1175
this.tt.new_file('f', this.root, 'f', 'f')
1176
this.tt.new_file('g', this.root, 'g', 'g')
1177
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1178
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1180
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1182
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1183
# three-way text conflict
1184
self.assertEqual(this.wt.get_file('b').read(),
1185
conflict_text('b', 'b2'))
1187
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1189
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1190
# Ambigious clean merge
1191
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1193
self.assertEqual(this.wt.get_file('f').read(), 'f')
1194
# Correct correct results when THIS == OTHER
1195
self.assertEqual(this.wt.get_file('g').read(), 'g')
1196
# Text conflict when THIS & OTHER are text and BASE is dir
1197
self.assertEqual(this.wt.get_file('h').read(),
1198
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1199
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1201
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1203
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1204
self.assertEqual(this.wt.get_file('i').read(),
1205
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1206
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1208
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1210
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1211
modified = ['a', 'b', 'c', 'h', 'i']
1212
merge_modified = this.wt.merge_modified()
1213
self.assertSubset(merge_modified, modified)
1214
self.assertEqual(len(merge_modified), len(modified))
1215
file(this.wt.id2abspath('a'), 'wb').write('booga')
1217
merge_modified = this.wt.merge_modified()
1218
self.assertSubset(merge_modified, modified)
1219
self.assertEqual(len(merge_modified), len(modified))
1223
def test_file_merge(self):
1224
self.requireFeature(SymlinkFeature)
1225
root_id = generate_ids.gen_root_id()
1226
base = TransformGroup("BASE", root_id)
1227
this = TransformGroup("THIS", root_id)
1228
other = TransformGroup("OTHER", root_id)
1229
for tg in this, base, other:
1230
tg.tt.new_directory('a', tg.root, 'a')
1231
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1232
tg.tt.new_file('c', tg.root, 'c', 'c')
1233
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1234
targets = ((base, 'base-e', 'base-f', None, None),
1235
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1236
(other, 'other-e', None, 'other-g', 'other-h'))
1237
for tg, e_target, f_target, g_target, h_target in targets:
1238
for link, target in (('e', e_target), ('f', f_target),
1239
('g', g_target), ('h', h_target)):
1240
if target is not None:
1241
tg.tt.new_symlink(link, tg.root, target, link)
1243
for tg in this, base, other:
1245
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1246
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1247
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1248
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1249
for suffix in ('THIS', 'BASE', 'OTHER'):
1250
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1251
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1252
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1253
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1254
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1255
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1256
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1257
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1258
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1259
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1260
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1261
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1262
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1263
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1265
def test_filename_merge(self):
1266
root_id = generate_ids.gen_root_id()
1267
base = TransformGroup("BASE", root_id)
1268
this = TransformGroup("THIS", root_id)
1269
other = TransformGroup("OTHER", root_id)
1270
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1271
for t in [base, this, other]]
1272
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1273
for t in [base, this, other]]
1274
base.tt.new_directory('c', base_a, 'c')
1275
this.tt.new_directory('c1', this_a, 'c')
1276
other.tt.new_directory('c', other_b, 'c')
1278
base.tt.new_directory('d', base_a, 'd')
1279
this.tt.new_directory('d1', this_b, 'd')
1280
other.tt.new_directory('d', other_a, 'd')
1282
base.tt.new_directory('e', base_a, 'e')
1283
this.tt.new_directory('e', this_a, 'e')
1284
other.tt.new_directory('e1', other_b, 'e')
1286
base.tt.new_directory('f', base_a, 'f')
1287
this.tt.new_directory('f1', this_b, 'f')
1288
other.tt.new_directory('f1', other_b, 'f')
1290
for tg in [this, base, other]:
1292
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1293
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1294
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1295
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1296
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1298
def test_filename_merge_conflicts(self):
1299
root_id = generate_ids.gen_root_id()
1300
base = TransformGroup("BASE", root_id)
1301
this = TransformGroup("THIS", root_id)
1302
other = TransformGroup("OTHER", root_id)
1303
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1304
for t in [base, this, other]]
1305
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1306
for t in [base, this, other]]
1308
base.tt.new_file('g', base_a, 'g', 'g')
1309
other.tt.new_file('g1', other_b, 'g1', 'g')
1311
base.tt.new_file('h', base_a, 'h', 'h')
1312
this.tt.new_file('h1', this_b, 'h1', 'h')
1314
base.tt.new_file('i', base.root, 'i', 'i')
1315
other.tt.new_directory('i1', this_b, 'i')
1317
for tg in [this, base, other]:
1319
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1321
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1322
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1323
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1324
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1325
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1326
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1327
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1330
class TestBuildTree(tests.TestCaseWithTransport):
1332
def test_build_tree_with_symlinks(self):
1333
self.requireFeature(SymlinkFeature)
1335
a = BzrDir.create_standalone_workingtree('a')
1337
file('a/foo/bar', 'wb').write('contents')
1338
os.symlink('a/foo/bar', 'a/foo/baz')
1339
a.add(['foo', 'foo/bar', 'foo/baz'])
1340
a.commit('initial commit')
1341
b = BzrDir.create_standalone_workingtree('b')
1342
basis = a.basis_tree()
1344
self.addCleanup(basis.unlock)
1345
build_tree(basis, b)
1346
self.assertIs(os.path.isdir('b/foo'), True)
1347
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1348
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1350
def test_build_with_references(self):
1351
tree = self.make_branch_and_tree('source',
1352
format='dirstate-with-subtree')
1353
subtree = self.make_branch_and_tree('source/subtree',
1354
format='dirstate-with-subtree')
1355
tree.add_reference(subtree)
1356
tree.commit('a revision')
1357
tree.branch.create_checkout('target')
1358
self.failUnlessExists('target')
1359
self.failUnlessExists('target/subtree')
1361
def test_file_conflict_handling(self):
1362
"""Ensure that when building trees, conflict handling is done"""
1363
source = self.make_branch_and_tree('source')
1364
target = self.make_branch_and_tree('target')
1365
self.build_tree(['source/file', 'target/file'])
1366
source.add('file', 'new-file')
1367
source.commit('added file')
1368
build_tree(source.basis_tree(), target)
1369
self.assertEqual([DuplicateEntry('Moved existing file to',
1370
'file.moved', 'file', None, 'new-file')],
1372
target2 = self.make_branch_and_tree('target2')
1373
target_file = file('target2/file', 'wb')
1375
source_file = file('source/file', 'rb')
1377
target_file.write(source_file.read())
1382
build_tree(source.basis_tree(), target2)
1383
self.assertEqual([], target2.conflicts())
1385
def test_symlink_conflict_handling(self):
1386
"""Ensure that when building trees, conflict handling is done"""
1387
self.requireFeature(SymlinkFeature)
1388
source = self.make_branch_and_tree('source')
1389
os.symlink('foo', 'source/symlink')
1390
source.add('symlink', 'new-symlink')
1391
source.commit('added file')
1392
target = self.make_branch_and_tree('target')
1393
os.symlink('bar', 'target/symlink')
1394
build_tree(source.basis_tree(), target)
1395
self.assertEqual([DuplicateEntry('Moved existing file to',
1396
'symlink.moved', 'symlink', None, 'new-symlink')],
1398
target = self.make_branch_and_tree('target2')
1399
os.symlink('foo', 'target2/symlink')
1400
build_tree(source.basis_tree(), target)
1401
self.assertEqual([], target.conflicts())
1403
def test_directory_conflict_handling(self):
1404
"""Ensure that when building trees, conflict handling is done"""
1405
source = self.make_branch_and_tree('source')
1406
target = self.make_branch_and_tree('target')
1407
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1408
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1409
source.commit('added file')
1410
build_tree(source.basis_tree(), target)
1411
self.assertEqual([], target.conflicts())
1412
self.failUnlessExists('target/dir1/file')
1414
# Ensure contents are merged
1415
target = self.make_branch_and_tree('target2')
1416
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1417
build_tree(source.basis_tree(), target)
1418
self.assertEqual([], target.conflicts())
1419
self.failUnlessExists('target2/dir1/file2')
1420
self.failUnlessExists('target2/dir1/file')
1422
# Ensure new contents are suppressed for existing branches
1423
target = self.make_branch_and_tree('target3')
1424
self.make_branch('target3/dir1')
1425
self.build_tree(['target3/dir1/file2'])
1426
build_tree(source.basis_tree(), target)
1427
self.failIfExists('target3/dir1/file')
1428
self.failUnlessExists('target3/dir1/file2')
1429
self.failUnlessExists('target3/dir1.diverted/file')
1430
self.assertEqual([DuplicateEntry('Diverted to',
1431
'dir1.diverted', 'dir1', 'new-dir1', None)],
1434
target = self.make_branch_and_tree('target4')
1435
self.build_tree(['target4/dir1/'])
1436
self.make_branch('target4/dir1/file')
1437
build_tree(source.basis_tree(), target)
1438
self.failUnlessExists('target4/dir1/file')
1439
self.assertEqual('directory', file_kind('target4/dir1/file'))
1440
self.failUnlessExists('target4/dir1/file.diverted')
1441
self.assertEqual([DuplicateEntry('Diverted to',
1442
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1445
def test_mixed_conflict_handling(self):
1446
"""Ensure that when building trees, conflict handling is done"""
1447
source = self.make_branch_and_tree('source')
1448
target = self.make_branch_and_tree('target')
1449
self.build_tree(['source/name', 'target/name/'])
1450
source.add('name', 'new-name')
1451
source.commit('added file')
1452
build_tree(source.basis_tree(), target)
1453
self.assertEqual([DuplicateEntry('Moved existing file to',
1454
'name.moved', 'name', None, 'new-name')], target.conflicts())
1456
def test_raises_in_populated(self):
1457
source = self.make_branch_and_tree('source')
1458
self.build_tree(['source/name'])
1460
source.commit('added name')
1461
target = self.make_branch_and_tree('target')
1462
self.build_tree(['target/name'])
1464
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1465
build_tree, source.basis_tree(), target)
1467
def test_build_tree_rename_count(self):
1468
source = self.make_branch_and_tree('source')
1469
self.build_tree(['source/file1', 'source/dir1/'])
1470
source.add(['file1', 'dir1'])
1471
source.commit('add1')
1472
target1 = self.make_branch_and_tree('target1')
1473
transform_result = build_tree(source.basis_tree(), target1)
1474
self.assertEqual(2, transform_result.rename_count)
1476
self.build_tree(['source/dir1/file2'])
1477
source.add(['dir1/file2'])
1478
source.commit('add3')
1479
target2 = self.make_branch_and_tree('target2')
1480
transform_result = build_tree(source.basis_tree(), target2)
1481
# children of non-root directories should not be renamed
1482
self.assertEqual(2, transform_result.rename_count)
1485
class MockTransform(object):
1487
def has_named_child(self, by_parent, parent_id, name):
1488
for child_id in by_parent[parent_id]:
1492
elif name == "name.~%s~" % child_id:
1497
class MockEntry(object):
1499
object.__init__(self)
1503
class TestGetBackupName(TestCase):
1504
def test_get_backup_name(self):
1505
tt = MockTransform()
1506
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1507
self.assertEqual(name, 'name.~1~')
1508
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1509
self.assertEqual(name, 'name.~2~')
1510
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1511
self.assertEqual(name, 'name.~1~')
1512
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1513
self.assertEqual(name, 'name.~1~')
1514
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1515
self.assertEqual(name, 'name.~4~')
1518
class TestFileMover(tests.TestCaseWithTransport):
1520
def test_file_mover(self):
1521
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1522
mover = _FileMover()
1523
mover.rename('a', 'q')
1524
self.failUnlessExists('q')
1525
self.failIfExists('a')
1526
self.failUnlessExists('q/b')
1527
self.failUnlessExists('c')
1528
self.failUnlessExists('c/d')
1530
def test_pre_delete_rollback(self):
1531
self.build_tree(['a/'])
1532
mover = _FileMover()
1533
mover.pre_delete('a', 'q')
1534
self.failUnlessExists('q')
1535
self.failIfExists('a')
1537
self.failIfExists('q')
1538
self.failUnlessExists('a')
1540
def test_apply_deletions(self):
1541
self.build_tree(['a/', 'b/'])
1542
mover = _FileMover()
1543
mover.pre_delete('a', 'q')
1544
mover.pre_delete('b', 'r')
1545
self.failUnlessExists('q')
1546
self.failUnlessExists('r')
1547
self.failIfExists('a')
1548
self.failIfExists('b')
1549
mover.apply_deletions()
1550
self.failIfExists('q')
1551
self.failIfExists('r')
1552
self.failIfExists('a')
1553
self.failIfExists('b')
1555
def test_file_mover_rollback(self):
1556
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1557
mover = _FileMover()
1558
mover.rename('c/d', 'c/f')
1559
mover.rename('c/e', 'c/d')
1561
mover.rename('a', 'c')
1562
except errors.FileExists, e:
1564
self.failUnlessExists('a')
1565
self.failUnlessExists('c/d')
1568
class Bogus(Exception):
1572
class TestTransformRollback(tests.TestCaseWithTransport):
1574
class ExceptionFileMover(_FileMover):
1576
def __init__(self, bad_source=None, bad_target=None):
1577
_FileMover.__init__(self)
1578
self.bad_source = bad_source
1579
self.bad_target = bad_target
1581
def rename(self, source, target):
1582
if (self.bad_source is not None and
1583
source.endswith(self.bad_source)):
1585
elif (self.bad_target is not None and
1586
target.endswith(self.bad_target)):
1589
_FileMover.rename(self, source, target)
1591
def test_rollback_rename(self):
1592
tree = self.make_branch_and_tree('.')
1593
self.build_tree(['a/', 'a/b'])
1594
tt = TreeTransform(tree)
1595
self.addCleanup(tt.finalize)
1596
a_id = tt.trans_id_tree_path('a')
1597
tt.adjust_path('c', tt.root, a_id)
1598
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1599
self.assertRaises(Bogus, tt.apply,
1600
_mover=self.ExceptionFileMover(bad_source='a'))
1601
self.failUnlessExists('a')
1602
self.failUnlessExists('a/b')
1604
self.failUnlessExists('c')
1605
self.failUnlessExists('c/d')
1607
def test_rollback_rename_into_place(self):
1608
tree = self.make_branch_and_tree('.')
1609
self.build_tree(['a/', 'a/b'])
1610
tt = TreeTransform(tree)
1611
self.addCleanup(tt.finalize)
1612
a_id = tt.trans_id_tree_path('a')
1613
tt.adjust_path('c', tt.root, a_id)
1614
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1615
self.assertRaises(Bogus, tt.apply,
1616
_mover=self.ExceptionFileMover(bad_target='c/d'))
1617
self.failUnlessExists('a')
1618
self.failUnlessExists('a/b')
1620
self.failUnlessExists('c')
1621
self.failUnlessExists('c/d')
1623
def test_rollback_deletion(self):
1624
tree = self.make_branch_and_tree('.')
1625
self.build_tree(['a/', 'a/b'])
1626
tt = TreeTransform(tree)
1627
self.addCleanup(tt.finalize)
1628
a_id = tt.trans_id_tree_path('a')
1629
tt.delete_contents(a_id)
1630
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1631
self.assertRaises(Bogus, tt.apply,
1632
_mover=self.ExceptionFileMover(bad_target='d'))
1633
self.failUnlessExists('a')
1634
self.failUnlessExists('a/b')
1636
def test_resolve_no_parent(self):
1637
wt = self.make_branch_and_tree('.')
1638
tt = TreeTransform(wt)
1639
self.addCleanup(tt.finalize)
1640
parent = tt.trans_id_file_id('parent-id')
1641
tt.new_file('file', parent, 'Contents')
1642
resolve_conflicts(tt)