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