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.bzrdir import BzrDir
20
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
UnversionedParent, ParentLoop)
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
ReusingTransform, CantMoveRoot, NotVersionedError,
24
ExistingLimbo, ImmortalLimbo, LockError)
25
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
26
from bzrlib.merge import Merge3Merger
27
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
28
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
29
resolve_conflicts, cook_conflicts,
30
find_interesting, build_tree, get_backup_name)
31
import bzrlib.urlutils as urlutils
33
class TestTreeTransform(TestCaseInTempDir):
36
super(TestTreeTransform, self).setUp()
37
self.wt = BzrDir.create_standalone_workingtree('.')
40
def get_transform(self):
41
transform = TreeTransform(self.wt)
42
#self.addCleanup(transform.finalize)
43
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
45
def test_existing_limbo(self):
46
limbo_name = urlutils.local_path_from_url(
47
self.wt._control_files.controlfilename('limbo'))
48
transform, root = self.get_transform()
49
os.mkdir(pathjoin(limbo_name, 'hehe'))
50
self.assertRaises(ImmortalLimbo, transform.apply)
51
self.assertRaises(LockError, self.wt.unlock)
52
self.assertRaises(ExistingLimbo, self.get_transform)
53
self.assertRaises(LockError, self.wt.unlock)
54
os.rmdir(pathjoin(limbo_name, 'hehe'))
56
transform, root = self.get_transform()
60
transform, root = self.get_transform()
61
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
62
imaginary_id = transform.trans_id_tree_path('imaginary')
63
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
64
self.assertEqual(imaginary_id, imaginary_id2)
65
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
66
self.assertEqual(transform.final_kind(root), 'directory')
67
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
68
trans_id = transform.create_path('name', root)
69
self.assertIs(transform.final_file_id(trans_id), None)
70
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
71
transform.create_file('contents', trans_id)
72
transform.set_executability(True, trans_id)
73
transform.version_file('my_pretties', trans_id)
74
self.assertRaises(DuplicateKey, transform.version_file,
75
'my_pretties', trans_id)
76
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
77
self.assertEqual(transform.final_parent(trans_id), root)
78
self.assertIs(transform.final_parent(root), ROOT_PARENT)
79
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
80
oz_id = transform.create_path('oz', root)
81
transform.create_directory(oz_id)
82
transform.version_file('ozzie', oz_id)
83
trans_id2 = transform.create_path('name2', root)
84
transform.create_file('contents', trans_id2)
85
transform.set_executability(False, trans_id2)
86
transform.version_file('my_pretties2', trans_id2)
87
modified_paths = transform.apply().modified_paths
88
self.assertEqual('contents', self.wt.get_file_byname('name').read())
89
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
90
self.assertIs(self.wt.is_executable('my_pretties'), True)
91
self.assertIs(self.wt.is_executable('my_pretties2'), False)
92
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
93
self.assertEqual(len(modified_paths), 3)
94
tree_mod_paths = [self.wt.id2abspath(f) for f in
95
('ozzie', 'my_pretties', 'my_pretties2')]
96
self.assertSubset(tree_mod_paths, modified_paths)
97
# is it safe to finalize repeatedly?
101
def test_convenience(self):
102
transform, root = self.get_transform()
103
trans_id = transform.new_file('name', root, 'contents',
105
oz = transform.new_directory('oz', root, 'oz-id')
106
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
107
toto = transform.new_file('toto', dorothy, 'toto-contents',
110
self.assertEqual(len(transform.find_conflicts()), 0)
112
self.assertRaises(ReusingTransform, transform.find_conflicts)
113
self.assertEqual('contents', file(self.wt.abspath('name')).read())
114
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
115
self.assertIs(self.wt.is_executable('my_pretties'), True)
116
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
117
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
118
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
120
self.assertEqual('toto-contents',
121
self.wt.get_file_byname('oz/dorothy/toto').read())
122
self.assertIs(self.wt.is_executable('toto-id'), False)
124
def test_conflicts(self):
125
transform, root = self.get_transform()
126
trans_id = transform.new_file('name', root, 'contents',
128
self.assertEqual(len(transform.find_conflicts()), 0)
129
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
130
self.assertEqual(transform.find_conflicts(),
131
[('duplicate', trans_id, trans_id2, 'name')])
132
self.assertRaises(MalformedTransform, transform.apply)
133
transform.adjust_path('name', trans_id, trans_id2)
134
self.assertEqual(transform.find_conflicts(),
135
[('non-directory parent', trans_id)])
136
tinman_id = transform.trans_id_tree_path('tinman')
137
transform.adjust_path('name', tinman_id, trans_id2)
138
self.assertEqual(transform.find_conflicts(),
139
[('unversioned parent', tinman_id),
140
('missing parent', tinman_id)])
141
lion_id = transform.create_path('lion', root)
142
self.assertEqual(transform.find_conflicts(),
143
[('unversioned parent', tinman_id),
144
('missing parent', tinman_id)])
145
transform.adjust_path('name', lion_id, trans_id2)
146
self.assertEqual(transform.find_conflicts(),
147
[('unversioned parent', lion_id),
148
('missing parent', lion_id)])
149
transform.version_file("Courage", lion_id)
150
self.assertEqual(transform.find_conflicts(),
151
[('missing parent', lion_id),
152
('versioning no contents', lion_id)])
153
transform.adjust_path('name2', root, trans_id2)
154
self.assertEqual(transform.find_conflicts(),
155
[('versioning no contents', lion_id)])
156
transform.create_file('Contents, okay?', lion_id)
157
transform.adjust_path('name2', trans_id2, trans_id2)
158
self.assertEqual(transform.find_conflicts(),
159
[('parent loop', trans_id2),
160
('non-directory parent', trans_id2)])
161
transform.adjust_path('name2', root, trans_id2)
162
oz_id = transform.new_directory('oz', root)
163
transform.set_executability(True, oz_id)
164
self.assertEqual(transform.find_conflicts(),
165
[('unversioned executability', oz_id)])
166
transform.version_file('oz-id', oz_id)
167
self.assertEqual(transform.find_conflicts(),
168
[('non-file executability', oz_id)])
169
transform.set_executability(None, oz_id)
170
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
172
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
173
self.assertEqual('contents', file(self.wt.abspath('name')).read())
174
transform2, root = self.get_transform()
175
oz_id = transform2.trans_id_tree_file_id('oz-id')
176
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
177
result = transform2.find_conflicts()
178
fp = FinalPaths(transform2)
179
self.assert_('oz/tip' in transform2._tree_path_ids)
180
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
181
self.assertEqual(len(result), 2)
182
self.assertEqual((result[0][0], result[0][1]),
183
('duplicate', newtip))
184
self.assertEqual((result[1][0], result[1][2]),
185
('duplicate id', newtip))
186
transform2.finalize()
187
transform3 = TreeTransform(self.wt)
188
self.addCleanup(transform3.finalize)
189
oz_id = transform3.trans_id_tree_file_id('oz-id')
190
transform3.delete_contents(oz_id)
191
self.assertEqual(transform3.find_conflicts(),
192
[('missing parent', oz_id)])
193
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
194
tip_id = transform3.trans_id_tree_file_id('tip-id')
195
transform3.adjust_path('tip', root_id, tip_id)
198
def test_add_del(self):
199
start, root = self.get_transform()
200
start.new_directory('a', root, 'a')
202
transform, root = self.get_transform()
203
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
204
transform.new_directory('a', root, 'a')
207
def test_unversioning(self):
208
create_tree, root = self.get_transform()
209
parent_id = create_tree.new_directory('parent', root, 'parent-id')
210
create_tree.new_file('child', parent_id, 'child', 'child-id')
212
unversion = TreeTransform(self.wt)
213
self.addCleanup(unversion.finalize)
214
parent = unversion.trans_id_tree_path('parent')
215
unversion.unversion_file(parent)
216
self.assertEqual(unversion.find_conflicts(),
217
[('unversioned parent', parent_id)])
218
file_id = unversion.trans_id_tree_file_id('child-id')
219
unversion.unversion_file(file_id)
222
def test_name_invariants(self):
223
create_tree, root = self.get_transform()
225
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
226
create_tree.new_file('name1', root, 'hello1', 'name1')
227
create_tree.new_file('name2', root, 'hello2', 'name2')
228
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
229
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
230
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
231
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
234
mangle_tree,root = self.get_transform()
235
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
237
name1 = mangle_tree.trans_id_tree_file_id('name1')
238
name2 = mangle_tree.trans_id_tree_file_id('name2')
239
mangle_tree.adjust_path('name2', root, name1)
240
mangle_tree.adjust_path('name1', root, name2)
242
#tests for deleting parent directories
243
ddir = mangle_tree.trans_id_tree_file_id('ddir')
244
mangle_tree.delete_contents(ddir)
245
dfile = mangle_tree.trans_id_tree_file_id('dfile')
246
mangle_tree.delete_versioned(dfile)
247
mangle_tree.unversion_file(dfile)
248
mfile = mangle_tree.trans_id_tree_file_id('mfile')
249
mangle_tree.adjust_path('mfile', root, mfile)
251
#tests for adding parent directories
252
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
253
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
254
mangle_tree.adjust_path('mfile2', newdir, mfile2)
255
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
256
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
257
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
258
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
260
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
261
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
262
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
263
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
264
self.assertEqual(file(mfile2_path).read(), 'later2')
265
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
266
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
267
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
268
self.assertEqual(file(newfile_path).read(), 'hello3')
269
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
270
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
271
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
273
def test_both_rename(self):
274
create_tree,root = self.get_transform()
275
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
276
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
278
mangle_tree,root = self.get_transform()
279
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
280
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
281
mangle_tree.adjust_path('test', root, selftest)
282
mangle_tree.adjust_path('test_too_much', root, selftest)
283
mangle_tree.set_executability(True, blackbox)
286
def test_both_rename2(self):
287
create_tree,root = self.get_transform()
288
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
289
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
290
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
291
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
294
mangle_tree,root = self.get_transform()
295
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
296
tests = mangle_tree.trans_id_tree_file_id('tests-id')
297
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
298
mangle_tree.adjust_path('selftest', bzrlib, tests)
299
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
300
mangle_tree.set_executability(True, test_too_much)
303
def test_both_rename3(self):
304
create_tree,root = self.get_transform()
305
tests = create_tree.new_directory('tests', root, 'tests-id')
306
create_tree.new_file('test_too_much.py', tests, 'hello1',
309
mangle_tree,root = self.get_transform()
310
tests = mangle_tree.trans_id_tree_file_id('tests-id')
311
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
312
mangle_tree.adjust_path('selftest', root, tests)
313
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
314
mangle_tree.set_executability(True, test_too_much)
317
def test_move_dangling_ie(self):
318
create_tree, root = self.get_transform()
320
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
321
create_tree.new_file('name1', root, 'hello1', 'name1')
323
delete_contents, root = self.get_transform()
324
file = delete_contents.trans_id_tree_file_id('name1')
325
delete_contents.delete_contents(file)
326
delete_contents.apply()
327
move_id, root = self.get_transform()
328
name1 = move_id.trans_id_tree_file_id('name1')
329
newdir = move_id.new_directory('dir', root, 'newdir')
330
move_id.adjust_path('name2', newdir, name1)
333
def test_replace_dangling_ie(self):
334
create_tree, root = self.get_transform()
336
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
337
create_tree.new_file('name1', root, 'hello1', 'name1')
339
delete_contents = TreeTransform(self.wt)
340
self.addCleanup(delete_contents.finalize)
341
file = delete_contents.trans_id_tree_file_id('name1')
342
delete_contents.delete_contents(file)
343
delete_contents.apply()
344
delete_contents.finalize()
345
replace = TreeTransform(self.wt)
346
self.addCleanup(replace.finalize)
347
name2 = replace.new_file('name2', root, 'hello2', 'name1')
348
conflicts = replace.find_conflicts()
349
name1 = replace.trans_id_tree_file_id('name1')
350
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
351
resolve_conflicts(replace)
354
def test_symlinks(self):
355
if not has_symlinks():
356
raise TestSkipped('Symlinks are not supported on this platform')
357
transform,root = self.get_transform()
358
oz_id = transform.new_directory('oz', root, 'oz-id')
359
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
361
wiz_id = transform.create_path('wizard2', oz_id)
362
transform.create_symlink('behind_curtain', wiz_id)
363
transform.version_file('wiz-id2', wiz_id)
364
transform.set_executability(True, wiz_id)
365
self.assertEqual(transform.find_conflicts(),
366
[('non-file executability', wiz_id)])
367
transform.set_executability(None, wiz_id)
369
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
370
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
371
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
373
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
377
def get_conflicted(self):
378
create,root = self.get_transform()
379
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
380
oz = create.new_directory('oz', root, 'oz-id')
381
create.new_directory('emeraldcity', oz, 'emerald-id')
383
conflicts,root = self.get_transform()
384
# set up duplicate entry, duplicate id
385
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
387
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
388
oz = conflicts.trans_id_tree_file_id('oz-id')
389
# set up missing, unversioned parent
390
conflicts.delete_versioned(oz)
391
emerald = conflicts.trans_id_tree_file_id('emerald-id')
393
conflicts.adjust_path('emeraldcity', emerald, emerald)
394
return conflicts, emerald, oz, old_dorothy, new_dorothy
396
def test_conflict_resolution(self):
397
conflicts, emerald, oz, old_dorothy, new_dorothy =\
398
self.get_conflicted()
399
resolve_conflicts(conflicts)
400
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
401
self.assertIs(conflicts.final_file_id(old_dorothy), None)
402
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
403
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
404
self.assertEqual(conflicts.final_parent(emerald), oz)
407
def test_cook_conflicts(self):
408
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
409
raw_conflicts = resolve_conflicts(tt)
410
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
411
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
412
'dorothy', None, 'dorothy-id')
413
self.assertEqual(cooked_conflicts[0], duplicate)
414
duplicate_id = DuplicateID('Unversioned existing file',
415
'dorothy.moved', 'dorothy', None,
417
self.assertEqual(cooked_conflicts[1], duplicate_id)
418
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
419
self.assertEqual(cooked_conflicts[2], missing_parent)
420
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
422
self.assertEqual(cooked_conflicts[3], unversioned_parent)
423
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
424
'oz/emeraldcity', 'emerald-id', 'emerald-id')
425
self.assertEqual(cooked_conflicts[4], parent_loop)
426
self.assertEqual(len(cooked_conflicts), 5)
429
def test_string_conflicts(self):
430
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
431
raw_conflicts = resolve_conflicts(tt)
432
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
434
conflicts_s = [str(c) for c in cooked_conflicts]
435
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
436
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
437
'Moved existing file to '
439
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
440
'Unversioned existing file '
442
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
444
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
445
'oz. Versioned directory.')
446
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
447
' oz/emeraldcity. Cancelled move.')
449
def test_moving_versioned_directories(self):
450
create, root = self.get_transform()
451
kansas = create.new_directory('kansas', root, 'kansas-id')
452
create.new_directory('house', kansas, 'house-id')
453
create.new_directory('oz', root, 'oz-id')
455
cyclone, root = self.get_transform()
456
oz = cyclone.trans_id_tree_file_id('oz-id')
457
house = cyclone.trans_id_tree_file_id('house-id')
458
cyclone.adjust_path('house', oz, house)
461
def test_moving_root(self):
462
create, root = self.get_transform()
463
fun = create.new_directory('fun', root, 'fun-id')
464
create.new_directory('sun', root, 'sun-id')
465
create.new_directory('moon', root, 'moon')
467
transform, root = self.get_transform()
468
transform.adjust_root_path('oldroot', fun)
469
new_root=transform.trans_id_tree_path('')
470
transform.version_file('new-root', new_root)
473
def test_renames(self):
474
create, root = self.get_transform()
475
old = create.new_directory('old-parent', root, 'old-id')
476
intermediate = create.new_directory('intermediate', old, 'im-id')
477
myfile = create.new_file('myfile', intermediate, 'myfile-text',
480
rename, root = self.get_transform()
481
old = rename.trans_id_file_id('old-id')
482
rename.adjust_path('new', root, old)
483
myfile = rename.trans_id_file_id('myfile-id')
484
rename.set_executability(True, myfile)
487
def test_find_interesting(self):
488
create, root = self.get_transform()
490
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
491
create.new_file('uvfile', root, 'othertext')
493
self.assertEqual(find_interesting(wt, wt, ['vfile']),
495
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
498
def test_set_executability_order(self):
499
"""Ensure that executability behaves the same, no matter what order.
501
- create file and set executability simultaneously
502
- create file and set executability afterward
503
- unsetting the executability of a file whose executability has not been
504
declared should throw an exception (this may happen when a
505
merge attempts to create a file with a duplicate ID)
507
transform, root = self.get_transform()
509
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
511
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
512
transform.set_executability(True, sac)
513
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
514
self.assertRaises(KeyError, transform.set_executability, None, uws)
516
self.assertTrue(wt.is_executable('soc'))
517
self.assertTrue(wt.is_executable('sac'))
520
class TransformGroup(object):
521
def __init__(self, dirname):
524
self.wt = BzrDir.create_standalone_workingtree(dirname)
525
self.b = self.wt.branch
526
self.tt = TreeTransform(self.wt)
527
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
529
def conflict_text(tree, merge):
530
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
531
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
534
class TestTransformMerge(TestCaseInTempDir):
535
def test_text_merge(self):
536
base = TransformGroup("base")
537
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
538
base.tt.new_file('b', base.root, 'b1', 'b')
539
base.tt.new_file('c', base.root, 'c', 'c')
540
base.tt.new_file('d', base.root, 'd', 'd')
541
base.tt.new_file('e', base.root, 'e', 'e')
542
base.tt.new_file('f', base.root, 'f', 'f')
543
base.tt.new_directory('g', base.root, 'g')
544
base.tt.new_directory('h', base.root, 'h')
546
other = TransformGroup("other")
547
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
548
other.tt.new_file('b', other.root, 'b2', 'b')
549
other.tt.new_file('c', other.root, 'c2', 'c')
550
other.tt.new_file('d', other.root, 'd', 'd')
551
other.tt.new_file('e', other.root, 'e2', 'e')
552
other.tt.new_file('f', other.root, 'f', 'f')
553
other.tt.new_file('g', other.root, 'g', 'g')
554
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
555
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
557
this = TransformGroup("this")
558
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
559
this.tt.new_file('b', this.root, 'b', 'b')
560
this.tt.new_file('c', this.root, 'c', 'c')
561
this.tt.new_file('d', this.root, 'd2', 'd')
562
this.tt.new_file('e', this.root, 'e2', 'e')
563
this.tt.new_file('f', this.root, 'f', 'f')
564
this.tt.new_file('g', this.root, 'g', 'g')
565
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
566
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
568
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
570
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
571
# three-way text conflict
572
self.assertEqual(this.wt.get_file('b').read(),
573
conflict_text('b', 'b2'))
575
self.assertEqual(this.wt.get_file('c').read(), 'c2')
577
self.assertEqual(this.wt.get_file('d').read(), 'd2')
578
# Ambigious clean merge
579
self.assertEqual(this.wt.get_file('e').read(), 'e2')
581
self.assertEqual(this.wt.get_file('f').read(), 'f')
582
# Correct correct results when THIS == OTHER
583
self.assertEqual(this.wt.get_file('g').read(), 'g')
584
# Text conflict when THIS & OTHER are text and BASE is dir
585
self.assertEqual(this.wt.get_file('h').read(),
586
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
587
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
589
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
591
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
592
self.assertEqual(this.wt.get_file('i').read(),
593
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
594
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
596
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
598
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
599
modified = ['a', 'b', 'c', 'h', 'i']
600
merge_modified = this.wt.merge_modified()
601
self.assertSubset(merge_modified, modified)
602
self.assertEqual(len(merge_modified), len(modified))
603
file(this.wt.id2abspath('a'), 'wb').write('booga')
605
merge_modified = this.wt.merge_modified()
606
self.assertSubset(merge_modified, modified)
607
self.assertEqual(len(merge_modified), len(modified))
611
def test_file_merge(self):
612
if not has_symlinks():
613
raise TestSkipped('Symlinks are not supported on this platform')
614
base = TransformGroup("BASE")
615
this = TransformGroup("THIS")
616
other = TransformGroup("OTHER")
617
for tg in this, base, other:
618
tg.tt.new_directory('a', tg.root, 'a')
619
tg.tt.new_symlink('b', tg.root, 'b', 'b')
620
tg.tt.new_file('c', tg.root, 'c', 'c')
621
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
622
targets = ((base, 'base-e', 'base-f', None, None),
623
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
624
(other, 'other-e', None, 'other-g', 'other-h'))
625
for tg, e_target, f_target, g_target, h_target in targets:
626
for link, target in (('e', e_target), ('f', f_target),
627
('g', g_target), ('h', h_target)):
628
if target is not None:
629
tg.tt.new_symlink(link, tg.root, target, link)
631
for tg in this, base, other:
633
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
634
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
635
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
636
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
637
for suffix in ('THIS', 'BASE', 'OTHER'):
638
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
639
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
640
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
641
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
642
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
643
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
644
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
645
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
646
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
647
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
648
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
649
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
650
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
651
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
653
def test_filename_merge(self):
654
base = TransformGroup("BASE")
655
this = TransformGroup("THIS")
656
other = TransformGroup("OTHER")
657
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
658
for t in [base, this, other]]
659
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
660
for t in [base, this, other]]
661
base.tt.new_directory('c', base_a, 'c')
662
this.tt.new_directory('c1', this_a, 'c')
663
other.tt.new_directory('c', other_b, 'c')
665
base.tt.new_directory('d', base_a, 'd')
666
this.tt.new_directory('d1', this_b, 'd')
667
other.tt.new_directory('d', other_a, 'd')
669
base.tt.new_directory('e', base_a, 'e')
670
this.tt.new_directory('e', this_a, 'e')
671
other.tt.new_directory('e1', other_b, 'e')
673
base.tt.new_directory('f', base_a, 'f')
674
this.tt.new_directory('f1', this_b, 'f')
675
other.tt.new_directory('f1', other_b, 'f')
677
for tg in [this, base, other]:
679
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
680
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
681
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
682
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
683
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
685
def test_filename_merge_conflicts(self):
686
base = TransformGroup("BASE")
687
this = TransformGroup("THIS")
688
other = TransformGroup("OTHER")
689
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
690
for t in [base, this, other]]
691
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
692
for t in [base, this, other]]
694
base.tt.new_file('g', base_a, 'g', 'g')
695
other.tt.new_file('g1', other_b, 'g1', 'g')
697
base.tt.new_file('h', base_a, 'h', 'h')
698
this.tt.new_file('h1', this_b, 'h1', 'h')
700
base.tt.new_file('i', base.root, 'i', 'i')
701
other.tt.new_directory('i1', this_b, 'i')
703
for tg in [this, base, other]:
705
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
707
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
708
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
709
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
710
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
711
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
712
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
713
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
715
class TestBuildTree(TestCaseInTempDir):
716
def test_build_tree(self):
717
if not has_symlinks():
718
raise TestSkipped('Test requires symlink support')
720
a = BzrDir.create_standalone_workingtree('a')
722
file('a/foo/bar', 'wb').write('contents')
723
os.symlink('a/foo/bar', 'a/foo/baz')
724
a.add(['foo', 'foo/bar', 'foo/baz'])
725
a.commit('initial commit')
726
b = BzrDir.create_standalone_workingtree('b')
727
build_tree(a.basis_tree(), b)
728
self.assertIs(os.path.isdir('b/foo'), True)
729
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
730
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
732
class MockTransform(object):
734
def has_named_child(self, by_parent, parent_id, name):
735
for child_id in by_parent[parent_id]:
739
elif name == "name.~%s~" % child_id:
743
class MockEntry(object):
745
object.__init__(self)
748
class TestGetBackupName(TestCase):
749
def test_get_backup_name(self):
751
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
752
self.assertEqual(name, 'name.~1~')
753
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
754
self.assertEqual(name, 'name.~2~')
755
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
756
self.assertEqual(name, 'name.~1~')
757
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
758
self.assertEqual(name, 'name.~1~')
759
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
760
self.assertEqual(name, 'name.~4~')