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 bzrlib import tests
20
from bzrlib.bzrdir import BzrDir
21
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
22
UnversionedParent, ParentLoop)
23
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
24
ReusingTransform, CantMoveRoot,
25
PathsNotVersionedError, ExistingLimbo,
26
ImmortalLimbo, LockError)
27
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
28
from bzrlib.merge import Merge3Merger
29
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
30
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
31
resolve_conflicts, cook_conflicts,
32
find_interesting, build_tree, get_backup_name)
33
import bzrlib.urlutils as urlutils
35
class TestTreeTransform(TestCaseInTempDir):
38
super(TestTreeTransform, self).setUp()
39
self.wt = BzrDir.create_standalone_workingtree('.')
42
def get_transform(self):
43
transform = TreeTransform(self.wt)
44
#self.addCleanup(transform.finalize)
45
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
47
def test_existing_limbo(self):
48
limbo_name = urlutils.local_path_from_url(
49
self.wt._control_files.controlfilename('limbo'))
50
transform, root = self.get_transform()
51
os.mkdir(pathjoin(limbo_name, 'hehe'))
52
self.assertRaises(ImmortalLimbo, transform.apply)
53
self.assertRaises(LockError, self.wt.unlock)
54
self.assertRaises(ExistingLimbo, self.get_transform)
55
self.assertRaises(LockError, self.wt.unlock)
56
os.rmdir(pathjoin(limbo_name, 'hehe'))
58
transform, root = self.get_transform()
62
transform, root = self.get_transform()
63
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
64
imaginary_id = transform.trans_id_tree_path('imaginary')
65
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
66
self.assertEqual(imaginary_id, imaginary_id2)
67
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
68
self.assertEqual(transform.final_kind(root), 'directory')
69
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
70
trans_id = transform.create_path('name', root)
71
self.assertIs(transform.final_file_id(trans_id), None)
72
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
73
transform.create_file('contents', trans_id)
74
transform.set_executability(True, trans_id)
75
transform.version_file('my_pretties', trans_id)
76
self.assertRaises(DuplicateKey, transform.version_file,
77
'my_pretties', trans_id)
78
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
79
self.assertEqual(transform.final_parent(trans_id), root)
80
self.assertIs(transform.final_parent(root), ROOT_PARENT)
81
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
82
oz_id = transform.create_path('oz', root)
83
transform.create_directory(oz_id)
84
transform.version_file('ozzie', oz_id)
85
trans_id2 = transform.create_path('name2', root)
86
transform.create_file('contents', trans_id2)
87
transform.set_executability(False, trans_id2)
88
transform.version_file('my_pretties2', trans_id2)
89
modified_paths = transform.apply().modified_paths
90
self.assertEqual('contents', self.wt.get_file_byname('name').read())
91
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
92
self.assertIs(self.wt.is_executable('my_pretties'), True)
93
self.assertIs(self.wt.is_executable('my_pretties2'), False)
94
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
95
self.assertEqual(len(modified_paths), 3)
96
tree_mod_paths = [self.wt.id2abspath(f) for f in
97
('ozzie', 'my_pretties', 'my_pretties2')]
98
self.assertSubset(tree_mod_paths, modified_paths)
99
# is it safe to finalize repeatedly?
103
def test_convenience(self):
104
transform, root = self.get_transform()
105
trans_id = transform.new_file('name', root, 'contents',
107
oz = transform.new_directory('oz', root, 'oz-id')
108
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
109
toto = transform.new_file('toto', dorothy, 'toto-contents',
112
self.assertEqual(len(transform.find_conflicts()), 0)
114
self.assertRaises(ReusingTransform, transform.find_conflicts)
115
self.assertEqual('contents', file(self.wt.abspath('name')).read())
116
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
117
self.assertIs(self.wt.is_executable('my_pretties'), True)
118
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
119
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
120
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
122
self.assertEqual('toto-contents',
123
self.wt.get_file_byname('oz/dorothy/toto').read())
124
self.assertIs(self.wt.is_executable('toto-id'), False)
126
def test_conflicts(self):
127
transform, root = self.get_transform()
128
trans_id = transform.new_file('name', root, 'contents',
130
self.assertEqual(len(transform.find_conflicts()), 0)
131
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
132
self.assertEqual(transform.find_conflicts(),
133
[('duplicate', trans_id, trans_id2, 'name')])
134
self.assertRaises(MalformedTransform, transform.apply)
135
transform.adjust_path('name', trans_id, trans_id2)
136
self.assertEqual(transform.find_conflicts(),
137
[('non-directory parent', trans_id)])
138
tinman_id = transform.trans_id_tree_path('tinman')
139
transform.adjust_path('name', tinman_id, trans_id2)
140
self.assertEqual(transform.find_conflicts(),
141
[('unversioned parent', tinman_id),
142
('missing parent', tinman_id)])
143
lion_id = transform.create_path('lion', root)
144
self.assertEqual(transform.find_conflicts(),
145
[('unversioned parent', tinman_id),
146
('missing parent', tinman_id)])
147
transform.adjust_path('name', lion_id, trans_id2)
148
self.assertEqual(transform.find_conflicts(),
149
[('unversioned parent', lion_id),
150
('missing parent', lion_id)])
151
transform.version_file("Courage", lion_id)
152
self.assertEqual(transform.find_conflicts(),
153
[('missing parent', lion_id),
154
('versioning no contents', lion_id)])
155
transform.adjust_path('name2', root, trans_id2)
156
self.assertEqual(transform.find_conflicts(),
157
[('versioning no contents', lion_id)])
158
transform.create_file('Contents, okay?', lion_id)
159
transform.adjust_path('name2', trans_id2, trans_id2)
160
self.assertEqual(transform.find_conflicts(),
161
[('parent loop', trans_id2),
162
('non-directory parent', trans_id2)])
163
transform.adjust_path('name2', root, trans_id2)
164
oz_id = transform.new_directory('oz', root)
165
transform.set_executability(True, oz_id)
166
self.assertEqual(transform.find_conflicts(),
167
[('unversioned executability', oz_id)])
168
transform.version_file('oz-id', oz_id)
169
self.assertEqual(transform.find_conflicts(),
170
[('non-file executability', oz_id)])
171
transform.set_executability(None, oz_id)
172
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
174
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
175
self.assertEqual('contents', file(self.wt.abspath('name')).read())
176
transform2, root = self.get_transform()
177
oz_id = transform2.trans_id_tree_file_id('oz-id')
178
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
179
result = transform2.find_conflicts()
180
fp = FinalPaths(transform2)
181
self.assert_('oz/tip' in transform2._tree_path_ids)
182
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
183
self.assertEqual(len(result), 2)
184
self.assertEqual((result[0][0], result[0][1]),
185
('duplicate', newtip))
186
self.assertEqual((result[1][0], result[1][2]),
187
('duplicate id', newtip))
188
transform2.finalize()
189
transform3 = TreeTransform(self.wt)
190
self.addCleanup(transform3.finalize)
191
oz_id = transform3.trans_id_tree_file_id('oz-id')
192
transform3.delete_contents(oz_id)
193
self.assertEqual(transform3.find_conflicts(),
194
[('missing parent', oz_id)])
195
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
196
tip_id = transform3.trans_id_tree_file_id('tip-id')
197
transform3.adjust_path('tip', root_id, tip_id)
200
def test_add_del(self):
201
start, root = self.get_transform()
202
start.new_directory('a', root, 'a')
204
transform, root = self.get_transform()
205
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
206
transform.new_directory('a', root, 'a')
209
def test_unversioning(self):
210
create_tree, root = self.get_transform()
211
parent_id = create_tree.new_directory('parent', root, 'parent-id')
212
create_tree.new_file('child', parent_id, 'child', 'child-id')
214
unversion = TreeTransform(self.wt)
215
self.addCleanup(unversion.finalize)
216
parent = unversion.trans_id_tree_path('parent')
217
unversion.unversion_file(parent)
218
self.assertEqual(unversion.find_conflicts(),
219
[('unversioned parent', parent_id)])
220
file_id = unversion.trans_id_tree_file_id('child-id')
221
unversion.unversion_file(file_id)
224
def test_name_invariants(self):
225
create_tree, root = self.get_transform()
227
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
228
create_tree.new_file('name1', root, 'hello1', 'name1')
229
create_tree.new_file('name2', root, 'hello2', 'name2')
230
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
231
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
232
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
233
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
236
mangle_tree,root = self.get_transform()
237
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
239
name1 = mangle_tree.trans_id_tree_file_id('name1')
240
name2 = mangle_tree.trans_id_tree_file_id('name2')
241
mangle_tree.adjust_path('name2', root, name1)
242
mangle_tree.adjust_path('name1', root, name2)
244
#tests for deleting parent directories
245
ddir = mangle_tree.trans_id_tree_file_id('ddir')
246
mangle_tree.delete_contents(ddir)
247
dfile = mangle_tree.trans_id_tree_file_id('dfile')
248
mangle_tree.delete_versioned(dfile)
249
mangle_tree.unversion_file(dfile)
250
mfile = mangle_tree.trans_id_tree_file_id('mfile')
251
mangle_tree.adjust_path('mfile', root, mfile)
253
#tests for adding parent directories
254
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
255
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
256
mangle_tree.adjust_path('mfile2', newdir, mfile2)
257
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
258
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
259
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
260
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
262
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
263
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
264
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
265
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
266
self.assertEqual(file(mfile2_path).read(), 'later2')
267
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
268
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
269
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
270
self.assertEqual(file(newfile_path).read(), 'hello3')
271
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
272
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
273
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
275
def test_both_rename(self):
276
create_tree,root = self.get_transform()
277
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
278
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
280
mangle_tree,root = self.get_transform()
281
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
282
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
283
mangle_tree.adjust_path('test', root, selftest)
284
mangle_tree.adjust_path('test_too_much', root, selftest)
285
mangle_tree.set_executability(True, blackbox)
288
def test_both_rename2(self):
289
create_tree,root = self.get_transform()
290
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
291
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
292
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
293
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
296
mangle_tree,root = self.get_transform()
297
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
298
tests = mangle_tree.trans_id_tree_file_id('tests-id')
299
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
300
mangle_tree.adjust_path('selftest', bzrlib, tests)
301
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
302
mangle_tree.set_executability(True, test_too_much)
305
def test_both_rename3(self):
306
create_tree,root = self.get_transform()
307
tests = create_tree.new_directory('tests', root, 'tests-id')
308
create_tree.new_file('test_too_much.py', tests, 'hello1',
311
mangle_tree,root = self.get_transform()
312
tests = mangle_tree.trans_id_tree_file_id('tests-id')
313
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
314
mangle_tree.adjust_path('selftest', root, tests)
315
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
316
mangle_tree.set_executability(True, test_too_much)
319
def test_move_dangling_ie(self):
320
create_tree, root = self.get_transform()
322
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
323
create_tree.new_file('name1', root, 'hello1', 'name1')
325
delete_contents, root = self.get_transform()
326
file = delete_contents.trans_id_tree_file_id('name1')
327
delete_contents.delete_contents(file)
328
delete_contents.apply()
329
move_id, root = self.get_transform()
330
name1 = move_id.trans_id_tree_file_id('name1')
331
newdir = move_id.new_directory('dir', root, 'newdir')
332
move_id.adjust_path('name2', newdir, name1)
335
def test_replace_dangling_ie(self):
336
create_tree, root = self.get_transform()
338
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
339
create_tree.new_file('name1', root, 'hello1', 'name1')
341
delete_contents = TreeTransform(self.wt)
342
self.addCleanup(delete_contents.finalize)
343
file = delete_contents.trans_id_tree_file_id('name1')
344
delete_contents.delete_contents(file)
345
delete_contents.apply()
346
delete_contents.finalize()
347
replace = TreeTransform(self.wt)
348
self.addCleanup(replace.finalize)
349
name2 = replace.new_file('name2', root, 'hello2', 'name1')
350
conflicts = replace.find_conflicts()
351
name1 = replace.trans_id_tree_file_id('name1')
352
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
353
resolve_conflicts(replace)
356
def test_symlinks(self):
357
if not has_symlinks():
358
raise TestSkipped('Symlinks are not supported on this platform')
359
transform,root = self.get_transform()
360
oz_id = transform.new_directory('oz', root, 'oz-id')
361
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
363
wiz_id = transform.create_path('wizard2', oz_id)
364
transform.create_symlink('behind_curtain', wiz_id)
365
transform.version_file('wiz-id2', wiz_id)
366
transform.set_executability(True, wiz_id)
367
self.assertEqual(transform.find_conflicts(),
368
[('non-file executability', wiz_id)])
369
transform.set_executability(None, wiz_id)
371
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
372
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
373
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
375
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
379
def get_conflicted(self):
380
create,root = self.get_transform()
381
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
382
oz = create.new_directory('oz', root, 'oz-id')
383
create.new_directory('emeraldcity', oz, 'emerald-id')
385
conflicts,root = self.get_transform()
386
# set up duplicate entry, duplicate id
387
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
389
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
390
oz = conflicts.trans_id_tree_file_id('oz-id')
391
# set up missing, unversioned parent
392
conflicts.delete_versioned(oz)
393
emerald = conflicts.trans_id_tree_file_id('emerald-id')
395
conflicts.adjust_path('emeraldcity', emerald, emerald)
396
return conflicts, emerald, oz, old_dorothy, new_dorothy
398
def test_conflict_resolution(self):
399
conflicts, emerald, oz, old_dorothy, new_dorothy =\
400
self.get_conflicted()
401
resolve_conflicts(conflicts)
402
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
403
self.assertIs(conflicts.final_file_id(old_dorothy), None)
404
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
405
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
406
self.assertEqual(conflicts.final_parent(emerald), oz)
409
def test_cook_conflicts(self):
410
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
411
raw_conflicts = resolve_conflicts(tt)
412
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
413
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
414
'dorothy', None, 'dorothy-id')
415
self.assertEqual(cooked_conflicts[0], duplicate)
416
duplicate_id = DuplicateID('Unversioned existing file',
417
'dorothy.moved', 'dorothy', None,
419
self.assertEqual(cooked_conflicts[1], duplicate_id)
420
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
421
self.assertEqual(cooked_conflicts[2], missing_parent)
422
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
424
self.assertEqual(cooked_conflicts[3], unversioned_parent)
425
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
426
'oz/emeraldcity', 'emerald-id', 'emerald-id')
427
self.assertEqual(cooked_conflicts[4], parent_loop)
428
self.assertEqual(len(cooked_conflicts), 5)
431
def test_string_conflicts(self):
432
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
433
raw_conflicts = resolve_conflicts(tt)
434
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
436
conflicts_s = [str(c) for c in cooked_conflicts]
437
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
438
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
439
'Moved existing file to '
441
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
442
'Unversioned existing file '
444
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
446
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
447
'oz. Versioned directory.')
448
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
449
' oz/emeraldcity. Cancelled move.')
451
def test_moving_versioned_directories(self):
452
create, root = self.get_transform()
453
kansas = create.new_directory('kansas', root, 'kansas-id')
454
create.new_directory('house', kansas, 'house-id')
455
create.new_directory('oz', root, 'oz-id')
457
cyclone, root = self.get_transform()
458
oz = cyclone.trans_id_tree_file_id('oz-id')
459
house = cyclone.trans_id_tree_file_id('house-id')
460
cyclone.adjust_path('house', oz, house)
463
def test_moving_root(self):
464
create, root = self.get_transform()
465
fun = create.new_directory('fun', root, 'fun-id')
466
create.new_directory('sun', root, 'sun-id')
467
create.new_directory('moon', root, 'moon')
469
transform, root = self.get_transform()
470
transform.adjust_root_path('oldroot', fun)
471
new_root=transform.trans_id_tree_path('')
472
transform.version_file('new-root', new_root)
475
def test_renames(self):
476
create, root = self.get_transform()
477
old = create.new_directory('old-parent', root, 'old-id')
478
intermediate = create.new_directory('intermediate', old, 'im-id')
479
myfile = create.new_file('myfile', intermediate, 'myfile-text',
482
rename, root = self.get_transform()
483
old = rename.trans_id_file_id('old-id')
484
rename.adjust_path('new', root, old)
485
myfile = rename.trans_id_file_id('myfile-id')
486
rename.set_executability(True, myfile)
489
def test_find_interesting(self):
490
create, root = self.get_transform()
492
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
493
create.new_file('uvfile', root, 'othertext')
495
self.assertEqual(find_interesting(wt, wt, ['vfile']),
497
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
500
def test_set_executability_order(self):
501
"""Ensure that executability behaves the same, no matter what order.
503
- create file and set executability simultaneously
504
- create file and set executability afterward
505
- unsetting the executability of a file whose executability has not been
506
declared should throw an exception (this may happen when a
507
merge attempts to create a file with a duplicate ID)
509
transform, root = self.get_transform()
511
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
513
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
514
transform.set_executability(True, sac)
515
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
516
self.assertRaises(KeyError, transform.set_executability, None, uws)
518
self.assertTrue(wt.is_executable('soc'))
519
self.assertTrue(wt.is_executable('sac'))
522
class TransformGroup(object):
523
def __init__(self, dirname):
526
self.wt = BzrDir.create_standalone_workingtree(dirname)
527
self.b = self.wt.branch
528
self.tt = TreeTransform(self.wt)
529
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
531
def conflict_text(tree, merge):
532
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
533
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
536
class TestTransformMerge(TestCaseInTempDir):
537
def test_text_merge(self):
538
base = TransformGroup("base")
539
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
540
base.tt.new_file('b', base.root, 'b1', 'b')
541
base.tt.new_file('c', base.root, 'c', 'c')
542
base.tt.new_file('d', base.root, 'd', 'd')
543
base.tt.new_file('e', base.root, 'e', 'e')
544
base.tt.new_file('f', base.root, 'f', 'f')
545
base.tt.new_directory('g', base.root, 'g')
546
base.tt.new_directory('h', base.root, 'h')
548
other = TransformGroup("other")
549
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
550
other.tt.new_file('b', other.root, 'b2', 'b')
551
other.tt.new_file('c', other.root, 'c2', 'c')
552
other.tt.new_file('d', other.root, 'd', 'd')
553
other.tt.new_file('e', other.root, 'e2', 'e')
554
other.tt.new_file('f', other.root, 'f', 'f')
555
other.tt.new_file('g', other.root, 'g', 'g')
556
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
557
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
559
this = TransformGroup("this")
560
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
561
this.tt.new_file('b', this.root, 'b', 'b')
562
this.tt.new_file('c', this.root, 'c', 'c')
563
this.tt.new_file('d', this.root, 'd2', 'd')
564
this.tt.new_file('e', this.root, 'e2', 'e')
565
this.tt.new_file('f', this.root, 'f', 'f')
566
this.tt.new_file('g', this.root, 'g', 'g')
567
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
568
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
570
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
572
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
573
# three-way text conflict
574
self.assertEqual(this.wt.get_file('b').read(),
575
conflict_text('b', 'b2'))
577
self.assertEqual(this.wt.get_file('c').read(), 'c2')
579
self.assertEqual(this.wt.get_file('d').read(), 'd2')
580
# Ambigious clean merge
581
self.assertEqual(this.wt.get_file('e').read(), 'e2')
583
self.assertEqual(this.wt.get_file('f').read(), 'f')
584
# Correct correct results when THIS == OTHER
585
self.assertEqual(this.wt.get_file('g').read(), 'g')
586
# Text conflict when THIS & OTHER are text and BASE is dir
587
self.assertEqual(this.wt.get_file('h').read(),
588
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
589
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
591
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
593
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
594
self.assertEqual(this.wt.get_file('i').read(),
595
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
596
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
598
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
600
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
601
modified = ['a', 'b', 'c', 'h', 'i']
602
merge_modified = this.wt.merge_modified()
603
self.assertSubset(merge_modified, modified)
604
self.assertEqual(len(merge_modified), len(modified))
605
file(this.wt.id2abspath('a'), 'wb').write('booga')
607
merge_modified = this.wt.merge_modified()
608
self.assertSubset(merge_modified, modified)
609
self.assertEqual(len(merge_modified), len(modified))
613
def test_file_merge(self):
614
if not has_symlinks():
615
raise TestSkipped('Symlinks are not supported on this platform')
616
base = TransformGroup("BASE")
617
this = TransformGroup("THIS")
618
other = TransformGroup("OTHER")
619
for tg in this, base, other:
620
tg.tt.new_directory('a', tg.root, 'a')
621
tg.tt.new_symlink('b', tg.root, 'b', 'b')
622
tg.tt.new_file('c', tg.root, 'c', 'c')
623
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
624
targets = ((base, 'base-e', 'base-f', None, None),
625
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
626
(other, 'other-e', None, 'other-g', 'other-h'))
627
for tg, e_target, f_target, g_target, h_target in targets:
628
for link, target in (('e', e_target), ('f', f_target),
629
('g', g_target), ('h', h_target)):
630
if target is not None:
631
tg.tt.new_symlink(link, tg.root, target, link)
633
for tg in this, base, other:
635
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
636
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
637
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
638
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
639
for suffix in ('THIS', 'BASE', 'OTHER'):
640
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
641
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
642
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
643
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
644
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
645
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
646
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
647
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
648
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
649
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
650
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
651
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
652
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
653
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
655
def test_filename_merge(self):
656
base = TransformGroup("BASE")
657
this = TransformGroup("THIS")
658
other = TransformGroup("OTHER")
659
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
660
for t in [base, this, other]]
661
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
662
for t in [base, this, other]]
663
base.tt.new_directory('c', base_a, 'c')
664
this.tt.new_directory('c1', this_a, 'c')
665
other.tt.new_directory('c', other_b, 'c')
667
base.tt.new_directory('d', base_a, 'd')
668
this.tt.new_directory('d1', this_b, 'd')
669
other.tt.new_directory('d', other_a, 'd')
671
base.tt.new_directory('e', base_a, 'e')
672
this.tt.new_directory('e', this_a, 'e')
673
other.tt.new_directory('e1', other_b, 'e')
675
base.tt.new_directory('f', base_a, 'f')
676
this.tt.new_directory('f1', this_b, 'f')
677
other.tt.new_directory('f1', other_b, 'f')
679
for tg in [this, base, other]:
681
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
682
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
683
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
684
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
685
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
687
def test_filename_merge_conflicts(self):
688
base = TransformGroup("BASE")
689
this = TransformGroup("THIS")
690
other = TransformGroup("OTHER")
691
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
692
for t in [base, this, other]]
693
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
694
for t in [base, this, other]]
696
base.tt.new_file('g', base_a, 'g', 'g')
697
other.tt.new_file('g1', other_b, 'g1', 'g')
699
base.tt.new_file('h', base_a, 'h', 'h')
700
this.tt.new_file('h1', this_b, 'h1', 'h')
702
base.tt.new_file('i', base.root, 'i', 'i')
703
other.tt.new_directory('i1', this_b, 'i')
705
for tg in [this, base, other]:
707
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
709
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
710
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
711
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
712
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
713
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
714
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
715
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
717
class TestBuildTree(tests.TestCaseWithTransport):
719
def test_build_tree(self):
720
if not has_symlinks():
721
raise TestSkipped('Test requires symlink support')
723
a = BzrDir.create_standalone_workingtree('a')
725
file('a/foo/bar', 'wb').write('contents')
726
os.symlink('a/foo/bar', 'a/foo/baz')
727
a.add(['foo', 'foo/bar', 'foo/baz'])
728
a.commit('initial commit')
729
b = BzrDir.create_standalone_workingtree('b')
730
build_tree(a.basis_tree(), b)
731
self.assertIs(os.path.isdir('b/foo'), True)
732
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
733
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
735
def test_file_conflict_handling(self):
736
"""Ensure that when building trees, conflict handling is done"""
737
source = self.make_branch_and_tree('source')
738
target = self.make_branch_and_tree('target')
739
self.build_tree(['source/file', 'target/file'])
740
source.add('file', 'new-file')
741
source.commit('added file')
742
build_tree(source.basis_tree(), target)
743
self.assertEqual([DuplicateEntry('Moved existing file to',
744
'file.moved', 'file', None, 'new-file')],
746
target2 = self.make_branch_and_tree('target2')
747
target_file = file('target2/file', 'wb')
749
source_file = file('source/file', 'rb')
751
target_file.write(source_file.read())
756
build_tree(source.basis_tree(), target2)
757
self.assertEqual([], target2.conflicts())
759
def test_symlink_conflict_handling(self):
760
"""Ensure that when building trees, conflict handling is done"""
761
if not has_symlinks():
762
raise TestSkipped('Test requires symlink support')
763
source = self.make_branch_and_tree('source')
764
os.symlink('foo', 'source/symlink')
765
source.add('symlink', 'new-symlink')
766
source.commit('added file')
767
target = self.make_branch_and_tree('target')
768
os.symlink('bar', 'target/symlink')
769
build_tree(source.basis_tree(), target)
770
self.assertEqual([DuplicateEntry('Moved existing file to',
771
'symlink.moved', 'symlink', None, 'new-symlink')],
773
target = self.make_branch_and_tree('target2')
774
os.symlink('foo', 'target2/symlink')
775
build_tree(source.basis_tree(), target)
776
self.assertEqual([], target.conflicts())
778
def test_directory_conflict_handling(self):
779
"""Ensure that when building trees, conflict handling is done"""
780
source = self.make_branch_and_tree('source')
781
target = self.make_branch_and_tree('target')
782
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
783
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
784
source.commit('added file')
785
build_tree(source.basis_tree(), target)
786
self.assertEqual([], target.conflicts())
787
self.failUnlessExists('target/dir1/file')
789
# Ensure contents are merged
790
target = self.make_branch_and_tree('target2')
791
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
792
build_tree(source.basis_tree(), target)
793
self.assertEqual([], target.conflicts())
794
self.failUnlessExists('target2/dir1/file2')
795
self.failUnlessExists('target2/dir1/file')
797
# Ensure new contents are suppressed for existing branches
798
target = self.make_branch_and_tree('target3')
799
self.make_branch('target3/dir1')
800
self.build_tree(['target3/dir1/file2'])
801
build_tree(source.basis_tree(), target)
802
self.failIfExists('target3/dir1/file')
803
self.failUnlessExists('target3/dir1/file2')
804
self.failUnlessExists('target3/dir1.diverted/file')
805
self.assertEqual([DuplicateEntry('Diverted to',
806
'dir1.diverted', 'dir1', 'new-dir1', None)],
809
target = self.make_branch_and_tree('target4')
810
self.build_tree(['target4/dir1/'])
811
self.make_branch('target4/dir1/file')
812
build_tree(source.basis_tree(), target)
813
self.failUnlessExists('target4/dir1/file')
814
self.assertEqual('directory', file_kind('target4/dir1/file'))
815
self.failUnlessExists('target4/dir1/file.diverted')
816
self.assertEqual([DuplicateEntry('Diverted to',
817
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
820
def test_mixed_conflict_handling(self):
821
"""Ensure that when building trees, conflict handling is done"""
822
source = self.make_branch_and_tree('source')
823
target = self.make_branch_and_tree('target')
824
self.build_tree(['source/name', 'target/name/'])
825
source.add('name', 'new-name')
826
source.commit('added file')
827
build_tree(source.basis_tree(), target)
828
self.assertEqual([DuplicateEntry('Moved existing file to',
829
'name.moved', 'name', None, 'new-name')], target.conflicts())
831
def test_raises_in_populated(self):
832
source = self.make_branch_and_tree('source')
833
self.build_tree(['source/name'])
835
source.commit('added name')
836
target = self.make_branch_and_tree('target')
837
self.build_tree(['target/name'])
839
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
843
class MockTransform(object):
845
def has_named_child(self, by_parent, parent_id, name):
846
for child_id in by_parent[parent_id]:
850
elif name == "name.~%s~" % child_id:
854
class MockEntry(object):
856
object.__init__(self)
859
class TestGetBackupName(TestCase):
860
def test_get_backup_name(self):
862
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
863
self.assertEqual(name, 'name.~1~')
864
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
865
self.assertEqual(name, 'name.~2~')
866
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
867
self.assertEqual(name, 'name.~1~')
868
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
869
self.assertEqual(name, 'name.~1~')
870
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
871
self.assertEqual(name, 'name.~4~')