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)
32
class TestTreeTransform(TestCaseInTempDir):
35
super(TestTreeTransform, self).setUp()
36
self.wt = BzrDir.create_standalone_workingtree('.')
39
def get_transform(self):
40
transform = TreeTransform(self.wt)
41
#self.addCleanup(transform.finalize)
42
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
44
def test_existing_limbo(self):
45
limbo_name = self.wt._control_files.controlfilename('limbo')
46
transform, root = self.get_transform()
47
os.mkdir(pathjoin(limbo_name, 'hehe'))
48
self.assertRaises(ImmortalLimbo, transform.apply)
49
self.assertRaises(LockError, self.wt.unlock)
50
self.assertRaises(ExistingLimbo, self.get_transform)
51
self.assertRaises(LockError, self.wt.unlock)
52
os.rmdir(pathjoin(limbo_name, 'hehe'))
54
transform, root = self.get_transform()
58
transform, root = self.get_transform()
59
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
60
imaginary_id = transform.trans_id_tree_path('imaginary')
61
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
62
self.assertEqual(imaginary_id, imaginary_id2)
63
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
64
self.assertEqual(transform.final_kind(root), 'directory')
65
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
66
trans_id = transform.create_path('name', root)
67
self.assertIs(transform.final_file_id(trans_id), None)
68
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
69
transform.create_file('contents', trans_id)
70
transform.set_executability(True, trans_id)
71
transform.version_file('my_pretties', trans_id)
72
self.assertRaises(DuplicateKey, transform.version_file,
73
'my_pretties', trans_id)
74
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
75
self.assertEqual(transform.final_parent(trans_id), root)
76
self.assertIs(transform.final_parent(root), ROOT_PARENT)
77
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
78
oz_id = transform.create_path('oz', root)
79
transform.create_directory(oz_id)
80
transform.version_file('ozzie', oz_id)
81
trans_id2 = transform.create_path('name2', root)
82
transform.create_file('contents', trans_id2)
83
transform.set_executability(False, trans_id2)
84
transform.version_file('my_pretties2', trans_id2)
85
modified_paths = transform.apply().modified_paths
86
self.assertEqual('contents', self.wt.get_file_byname('name').read())
87
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
88
self.assertIs(self.wt.is_executable('my_pretties'), True)
89
self.assertIs(self.wt.is_executable('my_pretties2'), False)
90
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
91
self.assertEqual(len(modified_paths), 3)
92
tree_mod_paths = [self.wt.id2abspath(f) for f in
93
('ozzie', 'my_pretties', 'my_pretties2')]
94
self.assertSubset(tree_mod_paths, modified_paths)
95
# is it safe to finalize repeatedly?
99
def test_convenience(self):
100
transform, root = self.get_transform()
101
trans_id = transform.new_file('name', root, 'contents',
103
oz = transform.new_directory('oz', root, 'oz-id')
104
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
105
toto = transform.new_file('toto', dorothy, 'toto-contents',
108
self.assertEqual(len(transform.find_conflicts()), 0)
110
self.assertRaises(ReusingTransform, transform.find_conflicts)
111
self.assertEqual('contents', file(self.wt.abspath('name')).read())
112
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
113
self.assertIs(self.wt.is_executable('my_pretties'), True)
114
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
115
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
116
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
118
self.assertEqual('toto-contents',
119
self.wt.get_file_byname('oz/dorothy/toto').read())
120
self.assertIs(self.wt.is_executable('toto-id'), False)
122
def test_conflicts(self):
123
transform, root = self.get_transform()
124
trans_id = transform.new_file('name', root, 'contents',
126
self.assertEqual(len(transform.find_conflicts()), 0)
127
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
128
self.assertEqual(transform.find_conflicts(),
129
[('duplicate', trans_id, trans_id2, 'name')])
130
self.assertRaises(MalformedTransform, transform.apply)
131
transform.adjust_path('name', trans_id, trans_id2)
132
self.assertEqual(transform.find_conflicts(),
133
[('non-directory parent', trans_id)])
134
tinman_id = transform.trans_id_tree_path('tinman')
135
transform.adjust_path('name', tinman_id, trans_id2)
136
self.assertEqual(transform.find_conflicts(),
137
[('unversioned parent', tinman_id),
138
('missing parent', tinman_id)])
139
lion_id = transform.create_path('lion', root)
140
self.assertEqual(transform.find_conflicts(),
141
[('unversioned parent', tinman_id),
142
('missing parent', tinman_id)])
143
transform.adjust_path('name', lion_id, trans_id2)
144
self.assertEqual(transform.find_conflicts(),
145
[('unversioned parent', lion_id),
146
('missing parent', lion_id)])
147
transform.version_file("Courage", lion_id)
148
self.assertEqual(transform.find_conflicts(),
149
[('missing parent', lion_id),
150
('versioning no contents', lion_id)])
151
transform.adjust_path('name2', root, trans_id2)
152
self.assertEqual(transform.find_conflicts(),
153
[('versioning no contents', lion_id)])
154
transform.create_file('Contents, okay?', lion_id)
155
transform.adjust_path('name2', trans_id2, trans_id2)
156
self.assertEqual(transform.find_conflicts(),
157
[('parent loop', trans_id2),
158
('non-directory parent', trans_id2)])
159
transform.adjust_path('name2', root, trans_id2)
160
oz_id = transform.new_directory('oz', root)
161
transform.set_executability(True, oz_id)
162
self.assertEqual(transform.find_conflicts(),
163
[('unversioned executability', oz_id)])
164
transform.version_file('oz-id', oz_id)
165
self.assertEqual(transform.find_conflicts(),
166
[('non-file executability', oz_id)])
167
transform.set_executability(None, oz_id)
168
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
170
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
171
self.assertEqual('contents', file(self.wt.abspath('name')).read())
172
transform2, root = self.get_transform()
173
oz_id = transform2.trans_id_tree_file_id('oz-id')
174
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
175
result = transform2.find_conflicts()
176
fp = FinalPaths(transform2)
177
self.assert_('oz/tip' in transform2._tree_path_ids)
178
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
179
self.assertEqual(len(result), 2)
180
self.assertEqual((result[0][0], result[0][1]),
181
('duplicate', newtip))
182
self.assertEqual((result[1][0], result[1][2]),
183
('duplicate id', newtip))
184
transform2.finalize()
185
transform3 = TreeTransform(self.wt)
186
self.addCleanup(transform3.finalize)
187
oz_id = transform3.trans_id_tree_file_id('oz-id')
188
transform3.delete_contents(oz_id)
189
self.assertEqual(transform3.find_conflicts(),
190
[('missing parent', oz_id)])
191
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
192
tip_id = transform3.trans_id_tree_file_id('tip-id')
193
transform3.adjust_path('tip', root_id, tip_id)
196
def test_add_del(self):
197
start, root = self.get_transform()
198
start.new_directory('a', root, 'a')
200
transform, root = self.get_transform()
201
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
202
transform.new_directory('a', root, 'a')
205
def test_unversioning(self):
206
create_tree, root = self.get_transform()
207
parent_id = create_tree.new_directory('parent', root, 'parent-id')
208
create_tree.new_file('child', parent_id, 'child', 'child-id')
210
unversion = TreeTransform(self.wt)
211
self.addCleanup(unversion.finalize)
212
parent = unversion.trans_id_tree_path('parent')
213
unversion.unversion_file(parent)
214
self.assertEqual(unversion.find_conflicts(),
215
[('unversioned parent', parent_id)])
216
file_id = unversion.trans_id_tree_file_id('child-id')
217
unversion.unversion_file(file_id)
220
def test_name_invariants(self):
221
create_tree, root = self.get_transform()
223
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
224
create_tree.new_file('name1', root, 'hello1', 'name1')
225
create_tree.new_file('name2', root, 'hello2', 'name2')
226
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
227
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
228
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
229
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
232
mangle_tree,root = self.get_transform()
233
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
235
name1 = mangle_tree.trans_id_tree_file_id('name1')
236
name2 = mangle_tree.trans_id_tree_file_id('name2')
237
mangle_tree.adjust_path('name2', root, name1)
238
mangle_tree.adjust_path('name1', root, name2)
240
#tests for deleting parent directories
241
ddir = mangle_tree.trans_id_tree_file_id('ddir')
242
mangle_tree.delete_contents(ddir)
243
dfile = mangle_tree.trans_id_tree_file_id('dfile')
244
mangle_tree.delete_versioned(dfile)
245
mangle_tree.unversion_file(dfile)
246
mfile = mangle_tree.trans_id_tree_file_id('mfile')
247
mangle_tree.adjust_path('mfile', root, mfile)
249
#tests for adding parent directories
250
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
251
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
252
mangle_tree.adjust_path('mfile2', newdir, mfile2)
253
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
254
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
255
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
256
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
258
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
259
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
260
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
261
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
262
self.assertEqual(file(mfile2_path).read(), 'later2')
263
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
264
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
265
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
266
self.assertEqual(file(newfile_path).read(), 'hello3')
267
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
268
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
269
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
271
def test_both_rename(self):
272
create_tree,root = self.get_transform()
273
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
274
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
276
mangle_tree,root = self.get_transform()
277
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
278
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
279
mangle_tree.adjust_path('test', root, selftest)
280
mangle_tree.adjust_path('test_too_much', root, selftest)
281
mangle_tree.set_executability(True, blackbox)
284
def test_both_rename2(self):
285
create_tree,root = self.get_transform()
286
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
287
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
288
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
289
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
292
mangle_tree,root = self.get_transform()
293
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
294
tests = mangle_tree.trans_id_tree_file_id('tests-id')
295
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
296
mangle_tree.adjust_path('selftest', bzrlib, tests)
297
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
298
mangle_tree.set_executability(True, test_too_much)
301
def test_both_rename3(self):
302
create_tree,root = self.get_transform()
303
tests = create_tree.new_directory('tests', root, 'tests-id')
304
create_tree.new_file('test_too_much.py', tests, 'hello1',
307
mangle_tree,root = self.get_transform()
308
tests = mangle_tree.trans_id_tree_file_id('tests-id')
309
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
310
mangle_tree.adjust_path('selftest', root, tests)
311
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
312
mangle_tree.set_executability(True, test_too_much)
315
def test_move_dangling_ie(self):
316
create_tree, root = self.get_transform()
318
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
319
create_tree.new_file('name1', root, 'hello1', 'name1')
321
delete_contents, root = self.get_transform()
322
file = delete_contents.trans_id_tree_file_id('name1')
323
delete_contents.delete_contents(file)
324
delete_contents.apply()
325
move_id, root = self.get_transform()
326
name1 = move_id.trans_id_tree_file_id('name1')
327
newdir = move_id.new_directory('dir', root, 'newdir')
328
move_id.adjust_path('name2', newdir, name1)
331
def test_replace_dangling_ie(self):
332
create_tree, root = self.get_transform()
334
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
335
create_tree.new_file('name1', root, 'hello1', 'name1')
337
delete_contents = TreeTransform(self.wt)
338
self.addCleanup(delete_contents.finalize)
339
file = delete_contents.trans_id_tree_file_id('name1')
340
delete_contents.delete_contents(file)
341
delete_contents.apply()
342
delete_contents.finalize()
343
replace = TreeTransform(self.wt)
344
self.addCleanup(replace.finalize)
345
name2 = replace.new_file('name2', root, 'hello2', 'name1')
346
conflicts = replace.find_conflicts()
347
name1 = replace.trans_id_tree_file_id('name1')
348
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
349
resolve_conflicts(replace)
352
def test_symlinks(self):
353
if not has_symlinks():
354
raise TestSkipped('Symlinks are not supported on this platform')
355
transform,root = self.get_transform()
356
oz_id = transform.new_directory('oz', root, 'oz-id')
357
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
359
wiz_id = transform.create_path('wizard2', oz_id)
360
transform.create_symlink('behind_curtain', wiz_id)
361
transform.version_file('wiz-id2', wiz_id)
362
transform.set_executability(True, wiz_id)
363
self.assertEqual(transform.find_conflicts(),
364
[('non-file executability', wiz_id)])
365
transform.set_executability(None, wiz_id)
367
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
368
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
369
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
371
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
375
def get_conflicted(self):
376
create,root = self.get_transform()
377
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
378
oz = create.new_directory('oz', root, 'oz-id')
379
create.new_directory('emeraldcity', oz, 'emerald-id')
381
conflicts,root = self.get_transform()
382
# set up duplicate entry, duplicate id
383
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
385
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
386
oz = conflicts.trans_id_tree_file_id('oz-id')
387
# set up missing, unversioned parent
388
conflicts.delete_versioned(oz)
389
emerald = conflicts.trans_id_tree_file_id('emerald-id')
391
conflicts.adjust_path('emeraldcity', emerald, emerald)
392
return conflicts, emerald, oz, old_dorothy, new_dorothy
394
def test_conflict_resolution(self):
395
conflicts, emerald, oz, old_dorothy, new_dorothy =\
396
self.get_conflicted()
397
resolve_conflicts(conflicts)
398
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
399
self.assertIs(conflicts.final_file_id(old_dorothy), None)
400
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
401
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
402
self.assertEqual(conflicts.final_parent(emerald), oz)
405
def test_cook_conflicts(self):
406
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
407
raw_conflicts = resolve_conflicts(tt)
408
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
409
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
410
'dorothy', None, 'dorothy-id')
411
self.assertEqual(cooked_conflicts[0], duplicate)
412
duplicate_id = DuplicateID('Unversioned existing file',
413
'dorothy.moved', 'dorothy', None,
415
self.assertEqual(cooked_conflicts[1], duplicate_id)
416
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
417
self.assertEqual(cooked_conflicts[2], missing_parent)
418
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
420
self.assertEqual(cooked_conflicts[3], unversioned_parent)
421
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
422
'oz/emeraldcity', 'emerald-id', 'emerald-id')
423
self.assertEqual(cooked_conflicts[4], parent_loop)
424
self.assertEqual(len(cooked_conflicts), 5)
427
def test_string_conflicts(self):
428
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
429
raw_conflicts = resolve_conflicts(tt)
430
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
432
conflicts_s = [str(c) for c in cooked_conflicts]
433
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
434
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
435
'Moved existing file to '
437
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
438
'Unversioned existing file '
440
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
442
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
443
'oz. Versioned directory.')
444
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
445
' oz/emeraldcity. Cancelled move.')
447
def test_moving_versioned_directories(self):
448
create, root = self.get_transform()
449
kansas = create.new_directory('kansas', root, 'kansas-id')
450
create.new_directory('house', kansas, 'house-id')
451
create.new_directory('oz', root, 'oz-id')
453
cyclone, root = self.get_transform()
454
oz = cyclone.trans_id_tree_file_id('oz-id')
455
house = cyclone.trans_id_tree_file_id('house-id')
456
cyclone.adjust_path('house', oz, house)
459
def test_moving_root(self):
460
create, root = self.get_transform()
461
fun = create.new_directory('fun', root, 'fun-id')
462
create.new_directory('sun', root, 'sun-id')
463
create.new_directory('moon', root, 'moon')
465
transform, root = self.get_transform()
466
transform.adjust_root_path('oldroot', fun)
467
new_root=transform.trans_id_tree_path('')
468
transform.version_file('new-root', new_root)
471
def test_renames(self):
472
create, root = self.get_transform()
473
old = create.new_directory('old-parent', root, 'old-id')
474
intermediate = create.new_directory('intermediate', old, 'im-id')
475
myfile = create.new_file('myfile', intermediate, 'myfile-text',
478
rename, root = self.get_transform()
479
old = rename.trans_id_file_id('old-id')
480
rename.adjust_path('new', root, old)
481
myfile = rename.trans_id_file_id('myfile-id')
482
rename.set_executability(True, myfile)
485
def test_find_interesting(self):
486
create, root = self.get_transform()
488
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
489
create.new_file('uvfile', root, 'othertext')
491
self.assertEqual(find_interesting(wt, wt, ['vfile']),
493
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
496
def test_set_executability_order(self):
497
"""Ensure that executability behaves the same, no matter what order.
499
- create file and set executability simultaneously
500
- create file and set executability afterward
501
- unsetting the executability of a file whose executability has not been
502
declared should throw an exception (this may happen when a
503
merge attempts to create a file with a duplicate ID)
505
transform, root = self.get_transform()
507
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
509
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
510
transform.set_executability(True, sac)
511
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
512
self.assertRaises(KeyError, transform.set_executability, None, uws)
514
self.assertTrue(wt.is_executable('soc'))
515
self.assertTrue(wt.is_executable('sac'))
518
class TransformGroup(object):
519
def __init__(self, dirname):
522
self.wt = BzrDir.create_standalone_workingtree(dirname)
523
self.b = self.wt.branch
524
self.tt = TreeTransform(self.wt)
525
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
527
def conflict_text(tree, merge):
528
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
529
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
532
class TestTransformMerge(TestCaseInTempDir):
533
def test_text_merge(self):
534
base = TransformGroup("base")
535
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
536
base.tt.new_file('b', base.root, 'b1', 'b')
537
base.tt.new_file('c', base.root, 'c', 'c')
538
base.tt.new_file('d', base.root, 'd', 'd')
539
base.tt.new_file('e', base.root, 'e', 'e')
540
base.tt.new_file('f', base.root, 'f', 'f')
541
base.tt.new_directory('g', base.root, 'g')
542
base.tt.new_directory('h', base.root, 'h')
544
other = TransformGroup("other")
545
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
546
other.tt.new_file('b', other.root, 'b2', 'b')
547
other.tt.new_file('c', other.root, 'c2', 'c')
548
other.tt.new_file('d', other.root, 'd', 'd')
549
other.tt.new_file('e', other.root, 'e2', 'e')
550
other.tt.new_file('f', other.root, 'f', 'f')
551
other.tt.new_file('g', other.root, 'g', 'g')
552
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
553
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
555
this = TransformGroup("this")
556
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
557
this.tt.new_file('b', this.root, 'b', 'b')
558
this.tt.new_file('c', this.root, 'c', 'c')
559
this.tt.new_file('d', this.root, 'd2', 'd')
560
this.tt.new_file('e', this.root, 'e2', 'e')
561
this.tt.new_file('f', this.root, 'f', 'f')
562
this.tt.new_file('g', this.root, 'g', 'g')
563
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
564
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
566
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
568
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
569
# three-way text conflict
570
self.assertEqual(this.wt.get_file('b').read(),
571
conflict_text('b', 'b2'))
573
self.assertEqual(this.wt.get_file('c').read(), 'c2')
575
self.assertEqual(this.wt.get_file('d').read(), 'd2')
576
# Ambigious clean merge
577
self.assertEqual(this.wt.get_file('e').read(), 'e2')
579
self.assertEqual(this.wt.get_file('f').read(), 'f')
580
# Correct correct results when THIS == OTHER
581
self.assertEqual(this.wt.get_file('g').read(), 'g')
582
# Text conflict when THIS & OTHER are text and BASE is dir
583
self.assertEqual(this.wt.get_file('h').read(),
584
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
585
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
587
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
589
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
590
self.assertEqual(this.wt.get_file('i').read(),
591
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
592
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
594
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
596
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
597
modified = ['a', 'b', 'c', 'h', 'i']
598
merge_modified = this.wt.merge_modified()
599
self.assertSubset(merge_modified, modified)
600
self.assertEqual(len(merge_modified), len(modified))
601
file(this.wt.id2abspath('a'), 'wb').write('booga')
603
merge_modified = this.wt.merge_modified()
604
self.assertSubset(merge_modified, modified)
605
self.assertEqual(len(merge_modified), len(modified))
609
def test_file_merge(self):
610
if not has_symlinks():
611
raise TestSkipped('Symlinks are not supported on this platform')
612
base = TransformGroup("BASE")
613
this = TransformGroup("THIS")
614
other = TransformGroup("OTHER")
615
for tg in this, base, other:
616
tg.tt.new_directory('a', tg.root, 'a')
617
tg.tt.new_symlink('b', tg.root, 'b', 'b')
618
tg.tt.new_file('c', tg.root, 'c', 'c')
619
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
620
targets = ((base, 'base-e', 'base-f', None, None),
621
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
622
(other, 'other-e', None, 'other-g', 'other-h'))
623
for tg, e_target, f_target, g_target, h_target in targets:
624
for link, target in (('e', e_target), ('f', f_target),
625
('g', g_target), ('h', h_target)):
626
if target is not None:
627
tg.tt.new_symlink(link, tg.root, target, link)
629
for tg in this, base, other:
631
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
632
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
633
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
634
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
635
for suffix in ('THIS', 'BASE', 'OTHER'):
636
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
637
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
638
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
639
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
640
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
641
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
642
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
643
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
644
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
645
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
646
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
647
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
648
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
649
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
651
def test_filename_merge(self):
652
base = TransformGroup("BASE")
653
this = TransformGroup("THIS")
654
other = TransformGroup("OTHER")
655
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
656
for t in [base, this, other]]
657
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
658
for t in [base, this, other]]
659
base.tt.new_directory('c', base_a, 'c')
660
this.tt.new_directory('c1', this_a, 'c')
661
other.tt.new_directory('c', other_b, 'c')
663
base.tt.new_directory('d', base_a, 'd')
664
this.tt.new_directory('d1', this_b, 'd')
665
other.tt.new_directory('d', other_a, 'd')
667
base.tt.new_directory('e', base_a, 'e')
668
this.tt.new_directory('e', this_a, 'e')
669
other.tt.new_directory('e1', other_b, 'e')
671
base.tt.new_directory('f', base_a, 'f')
672
this.tt.new_directory('f1', this_b, 'f')
673
other.tt.new_directory('f1', other_b, 'f')
675
for tg in [this, base, other]:
677
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
678
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
679
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
680
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
681
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
683
def test_filename_merge_conflicts(self):
684
base = TransformGroup("BASE")
685
this = TransformGroup("THIS")
686
other = TransformGroup("OTHER")
687
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
688
for t in [base, this, other]]
689
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
690
for t in [base, this, other]]
692
base.tt.new_file('g', base_a, 'g', 'g')
693
other.tt.new_file('g1', other_b, 'g1', 'g')
695
base.tt.new_file('h', base_a, 'h', 'h')
696
this.tt.new_file('h1', this_b, 'h1', 'h')
698
base.tt.new_file('i', base.root, 'i', 'i')
699
other.tt.new_directory('i1', this_b, 'i')
701
for tg in [this, base, other]:
703
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
705
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
706
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
707
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
708
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
709
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
710
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
711
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
713
class TestBuildTree(TestCaseInTempDir):
714
def test_build_tree(self):
715
if not has_symlinks():
716
raise TestSkipped('Test requires symlink support')
718
a = BzrDir.create_standalone_workingtree('a')
720
file('a/foo/bar', 'wb').write('contents')
721
os.symlink('a/foo/bar', 'a/foo/baz')
722
a.add(['foo', 'foo/bar', 'foo/baz'])
723
a.commit('initial commit')
724
b = BzrDir.create_standalone_workingtree('b')
725
build_tree(a.basis_tree(), b)
726
self.assertIs(os.path.isdir('b/foo'), True)
727
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
728
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
730
class MockTransform(object):
732
def has_named_child(self, by_parent, parent_id, name):
733
for child_id in by_parent[parent_id]:
737
elif name == "name.~%s~" % child_id:
741
class MockEntry(object):
743
object.__init__(self)
746
class TestGetBackupName(TestCase):
747
def test_get_backup_name(self):
749
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
750
self.assertEqual(name, 'name.~1~')
751
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
752
self.assertEqual(name, 'name.~2~')
753
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
754
self.assertEqual(name, 'name.~1~')
755
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
756
self.assertEqual(name, 'name.~1~')
757
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
758
self.assertEqual(name, 'name.~4~')