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.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
21
ReusingTransform, CantMoveRoot, NotVersionedError,
22
ExistingLimbo, ImmortalLimbo, LockError)
23
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
24
from bzrlib.merge import Merge3Merger
25
from bzrlib.tests import TestCaseInTempDir, TestSkipped
26
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
27
resolve_conflicts, cook_conflicts,
28
conflicts_strings, find_interesting, build_tree)
30
class TestTreeTransform(TestCaseInTempDir):
32
super(TestTreeTransform, self).setUp()
33
self.wt = BzrDir.create_standalone_workingtree('.')
36
def get_transform(self):
37
transform = TreeTransform(self.wt)
38
#self.addCleanup(transform.finalize)
39
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
41
def test_existing_limbo(self):
42
limbo_name = self.wt._control_files.controlfilename('limbo')
43
transform, root = self.get_transform()
44
os.mkdir(pathjoin(limbo_name, 'hehe'))
45
self.assertRaises(ImmortalLimbo, transform.apply)
46
self.assertRaises(LockError, self.wt.unlock)
47
self.assertRaises(ExistingLimbo, self.get_transform)
48
self.assertRaises(LockError, self.wt.unlock)
49
os.rmdir(pathjoin(limbo_name, 'hehe'))
51
transform, root = self.get_transform()
55
transform, root = self.get_transform()
56
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
57
imaginary_id = transform.trans_id_tree_path('imaginary')
58
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
59
self.assertEqual(transform.final_kind(root), 'directory')
60
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
61
trans_id = transform.create_path('name', root)
62
self.assertIs(transform.final_file_id(trans_id), None)
63
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
64
transform.create_file('contents', trans_id)
65
transform.set_executability(True, trans_id)
66
transform.version_file('my_pretties', trans_id)
67
self.assertRaises(DuplicateKey, transform.version_file,
68
'my_pretties', trans_id)
69
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
70
self.assertEqual(transform.final_parent(trans_id), root)
71
self.assertIs(transform.final_parent(root), ROOT_PARENT)
72
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
73
oz_id = transform.create_path('oz', root)
74
transform.create_directory(oz_id)
75
transform.version_file('ozzie', oz_id)
76
trans_id2 = transform.create_path('name2', root)
77
transform.create_file('contents', trans_id2)
78
transform.set_executability(False, trans_id2)
79
transform.version_file('my_pretties2', trans_id2)
80
modified_paths = transform.apply().modified_paths
81
self.assertEqual('contents', self.wt.get_file_byname('name').read())
82
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
83
self.assertIs(self.wt.is_executable('my_pretties'), True)
84
self.assertIs(self.wt.is_executable('my_pretties2'), False)
85
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
86
self.assertEqual(len(modified_paths), 3)
87
tree_mod_paths = [self.wt.id2abspath(f) for f in
88
('ozzie', 'my_pretties', 'my_pretties2')]
89
self.assertSubset(tree_mod_paths, modified_paths)
90
# is it safe to finalize repeatedly?
94
def test_convenience(self):
95
transform, root = self.get_transform()
96
trans_id = transform.new_file('name', root, 'contents',
98
oz = transform.new_directory('oz', root, 'oz-id')
99
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
100
toto = transform.new_file('toto', dorothy, 'toto-contents',
103
self.assertEqual(len(transform.find_conflicts()), 0)
105
self.assertRaises(ReusingTransform, transform.find_conflicts)
106
self.assertEqual('contents', file(self.wt.abspath('name')).read())
107
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
108
self.assertIs(self.wt.is_executable('my_pretties'), True)
109
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
110
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
111
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
113
self.assertEqual('toto-contents',
114
self.wt.get_file_byname('oz/dorothy/toto').read())
115
self.assertIs(self.wt.is_executable('toto-id'), False)
117
def test_conflicts(self):
118
transform, root = self.get_transform()
119
trans_id = transform.new_file('name', root, 'contents',
121
self.assertEqual(len(transform.find_conflicts()), 0)
122
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
123
self.assertEqual(transform.find_conflicts(),
124
[('duplicate', trans_id, trans_id2, 'name')])
125
self.assertRaises(MalformedTransform, transform.apply)
126
transform.adjust_path('name', trans_id, trans_id2)
127
self.assertEqual(transform.find_conflicts(),
128
[('non-directory parent', trans_id)])
129
tinman_id = transform.trans_id_tree_path('tinman')
130
transform.adjust_path('name', tinman_id, trans_id2)
131
self.assertEqual(transform.find_conflicts(),
132
[('unversioned parent', tinman_id),
133
('missing parent', tinman_id)])
134
lion_id = transform.create_path('lion', root)
135
self.assertEqual(transform.find_conflicts(),
136
[('unversioned parent', tinman_id),
137
('missing parent', tinman_id)])
138
transform.adjust_path('name', lion_id, trans_id2)
139
self.assertEqual(transform.find_conflicts(),
140
[('unversioned parent', lion_id),
141
('missing parent', lion_id)])
142
transform.version_file("Courage", lion_id)
143
self.assertEqual(transform.find_conflicts(),
144
[('missing parent', lion_id),
145
('versioning no contents', lion_id)])
146
transform.adjust_path('name2', root, trans_id2)
147
self.assertEqual(transform.find_conflicts(),
148
[('versioning no contents', lion_id)])
149
transform.create_file('Contents, okay?', lion_id)
150
transform.adjust_path('name2', trans_id2, trans_id2)
151
self.assertEqual(transform.find_conflicts(),
152
[('parent loop', trans_id2),
153
('non-directory parent', trans_id2)])
154
transform.adjust_path('name2', root, trans_id2)
155
oz_id = transform.new_directory('oz', root)
156
transform.set_executability(True, oz_id)
157
self.assertEqual(transform.find_conflicts(),
158
[('unversioned executability', oz_id)])
159
transform.version_file('oz-id', oz_id)
160
self.assertEqual(transform.find_conflicts(),
161
[('non-file executability', oz_id)])
162
transform.set_executability(None, oz_id)
163
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
165
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
166
self.assertEqual('contents', file(self.wt.abspath('name')).read())
167
transform2, root = self.get_transform()
168
oz_id = transform2.trans_id_tree_file_id('oz-id')
169
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
170
result = transform2.find_conflicts()
171
fp = FinalPaths(transform2)
172
self.assert_('oz/tip' in transform2._tree_path_ids)
173
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
174
self.assertEqual(len(result), 2)
175
self.assertEqual((result[0][0], result[0][1]),
176
('duplicate', newtip))
177
self.assertEqual((result[1][0], result[1][2]),
178
('duplicate id', newtip))
179
transform2.finalize()
180
transform3 = TreeTransform(self.wt)
181
self.addCleanup(transform3.finalize)
182
oz_id = transform3.trans_id_tree_file_id('oz-id')
183
transform3.delete_contents(oz_id)
184
self.assertEqual(transform3.find_conflicts(),
185
[('missing parent', oz_id)])
186
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
187
tip_id = transform3.trans_id_tree_file_id('tip-id')
188
transform3.adjust_path('tip', root_id, tip_id)
191
def test_unversioning(self):
192
create_tree, root = self.get_transform()
193
parent_id = create_tree.new_directory('parent', root, 'parent-id')
194
create_tree.new_file('child', parent_id, 'child', 'child-id')
196
unversion = TreeTransform(self.wt)
197
self.addCleanup(unversion.finalize)
198
parent = unversion.trans_id_tree_path('parent')
199
unversion.unversion_file(parent)
200
self.assertEqual(unversion.find_conflicts(),
201
[('unversioned parent', parent_id)])
202
file_id = unversion.trans_id_tree_file_id('child-id')
203
unversion.unversion_file(file_id)
206
def test_name_invariants(self):
207
create_tree, root = self.get_transform()
209
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
210
create_tree.new_file('name1', root, 'hello1', 'name1')
211
create_tree.new_file('name2', root, 'hello2', 'name2')
212
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
213
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
214
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
215
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
218
mangle_tree,root = self.get_transform()
219
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
221
name1 = mangle_tree.trans_id_tree_file_id('name1')
222
name2 = mangle_tree.trans_id_tree_file_id('name2')
223
mangle_tree.adjust_path('name2', root, name1)
224
mangle_tree.adjust_path('name1', root, name2)
226
#tests for deleting parent directories
227
ddir = mangle_tree.trans_id_tree_file_id('ddir')
228
mangle_tree.delete_contents(ddir)
229
dfile = mangle_tree.trans_id_tree_file_id('dfile')
230
mangle_tree.delete_versioned(dfile)
231
mangle_tree.unversion_file(dfile)
232
mfile = mangle_tree.trans_id_tree_file_id('mfile')
233
mangle_tree.adjust_path('mfile', root, mfile)
235
#tests for adding parent directories
236
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
237
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
238
mangle_tree.adjust_path('mfile2', newdir, mfile2)
239
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
240
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
241
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
242
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
244
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
245
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
246
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
247
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
248
self.assertEqual(file(mfile2_path).read(), 'later2')
249
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
250
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
251
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
252
self.assertEqual(file(newfile_path).read(), 'hello3')
253
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
254
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
255
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
257
def test_both_rename(self):
258
create_tree,root = self.get_transform()
259
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
260
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
262
mangle_tree,root = self.get_transform()
263
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
264
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
265
mangle_tree.adjust_path('test', root, selftest)
266
mangle_tree.adjust_path('test_too_much', root, selftest)
267
mangle_tree.set_executability(True, blackbox)
270
def test_both_rename2(self):
271
create_tree,root = self.get_transform()
272
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
273
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
274
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
275
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
278
mangle_tree,root = self.get_transform()
279
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
280
tests = mangle_tree.trans_id_tree_file_id('tests-id')
281
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
282
mangle_tree.adjust_path('selftest', bzrlib, tests)
283
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
284
mangle_tree.set_executability(True, test_too_much)
287
def test_both_rename3(self):
288
create_tree,root = self.get_transform()
289
tests = create_tree.new_directory('tests', root, 'tests-id')
290
create_tree.new_file('test_too_much.py', tests, 'hello1',
293
mangle_tree,root = self.get_transform()
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', root, tests)
297
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
298
mangle_tree.set_executability(True, test_too_much)
301
def test_move_dangling_ie(self):
302
create_tree, root = self.get_transform()
304
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
305
create_tree.new_file('name1', root, 'hello1', 'name1')
307
delete_contents, root = self.get_transform()
308
file = delete_contents.trans_id_tree_file_id('name1')
309
delete_contents.delete_contents(file)
310
delete_contents.apply()
311
move_id, root = self.get_transform()
312
name1 = move_id.trans_id_tree_file_id('name1')
313
newdir = move_id.new_directory('dir', root, 'newdir')
314
move_id.adjust_path('name2', newdir, name1)
317
def test_replace_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 = TreeTransform(self.wt)
324
self.addCleanup(delete_contents.finalize)
325
file = delete_contents.trans_id_tree_file_id('name1')
326
delete_contents.delete_contents(file)
327
delete_contents.apply()
328
delete_contents.finalize()
329
replace = TreeTransform(self.wt)
330
self.addCleanup(replace.finalize)
331
name2 = replace.new_file('name2', root, 'hello2', 'name1')
332
conflicts = replace.find_conflicts()
333
name1 = replace.trans_id_tree_file_id('name1')
334
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
335
resolve_conflicts(replace)
338
def test_symlinks(self):
339
if not has_symlinks():
340
raise TestSkipped('Symlinks are not supported on this platform')
341
transform,root = self.get_transform()
342
oz_id = transform.new_directory('oz', root, 'oz-id')
343
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
345
wiz_id = transform.create_path('wizard2', oz_id)
346
transform.create_symlink('behind_curtain', wiz_id)
347
transform.version_file('wiz-id2', wiz_id)
348
transform.set_executability(True, wiz_id)
349
self.assertEqual(transform.find_conflicts(),
350
[('non-file executability', wiz_id)])
351
transform.set_executability(None, wiz_id)
353
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
354
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
355
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
357
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
361
def get_conflicted(self):
362
create,root = self.get_transform()
363
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
364
oz = create.new_directory('oz', root, 'oz-id')
365
create.new_directory('emeraldcity', oz, 'emerald-id')
367
conflicts,root = self.get_transform()
368
# set up duplicate entry, duplicate id
369
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
371
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
372
oz = conflicts.trans_id_tree_file_id('oz-id')
373
# set up missing, unversioned parent
374
conflicts.delete_versioned(oz)
375
emerald = conflicts.trans_id_tree_file_id('emerald-id')
377
conflicts.adjust_path('emeraldcity', emerald, emerald)
378
return conflicts, emerald, oz, old_dorothy, new_dorothy
380
def test_conflict_resolution(self):
381
conflicts, emerald, oz, old_dorothy, new_dorothy =\
382
self.get_conflicted()
383
resolve_conflicts(conflicts)
384
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
385
self.assertIs(conflicts.final_file_id(old_dorothy), None)
386
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
387
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
388
self.assertEqual(conflicts.final_parent(emerald), oz)
391
def test_cook_conflicts(self):
392
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
393
raw_conflicts = resolve_conflicts(tt)
394
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
395
duplicate = ('duplicate', 'Moved existing file to', 'dorothy.moved',
396
None, 'dorothy', 'dorothy-id')
397
self.assertEqual(cooked_conflicts[0], duplicate)
398
duplicate_id = ('duplicate id', 'Unversioned existing file',
399
'dorothy.moved', None, 'dorothy', 'dorothy-id')
400
self.assertEqual(cooked_conflicts[1], duplicate_id)
401
missing_parent = ('missing parent', 'Not deleting', 'oz', 'oz-id')
402
self.assertEqual(cooked_conflicts[2], missing_parent)
403
unversioned_parent = ('unversioned parent',
404
'Versioned directory', 'oz', 'oz-id')
405
self.assertEqual(cooked_conflicts[3], unversioned_parent)
406
parent_loop = ('parent loop', 'Cancelled move', 'oz/emeraldcity',
407
'emerald-id', 'oz/emeraldcity', 'emerald-id')
408
self.assertEqual(cooked_conflicts[4], parent_loop)
409
self.assertEqual(len(cooked_conflicts), 5)
412
def test_string_conflicts(self):
413
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
414
raw_conflicts = resolve_conflicts(tt)
415
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
417
conflicts_s = list(conflicts_strings(cooked_conflicts))
418
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
419
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
420
'Moved existing file to '
422
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
423
'Unversioned existing file '
425
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
427
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
428
'oz. Versioned directory.')
429
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
430
' oz/emeraldcity. Cancelled move.')
432
def test_moving_versioned_directories(self):
433
create, root = self.get_transform()
434
kansas = create.new_directory('kansas', root, 'kansas-id')
435
create.new_directory('house', kansas, 'house-id')
436
create.new_directory('oz', root, 'oz-id')
438
cyclone, root = self.get_transform()
439
oz = cyclone.trans_id_tree_file_id('oz-id')
440
house = cyclone.trans_id_tree_file_id('house-id')
441
cyclone.adjust_path('house', oz, house)
444
def test_moving_root(self):
445
create, root = self.get_transform()
446
fun = create.new_directory('fun', root, 'fun-id')
447
create.new_directory('sun', root, 'sun-id')
448
create.new_directory('moon', root, 'moon')
450
transform, root = self.get_transform()
451
transform.adjust_root_path('oldroot', fun)
452
new_root=transform.trans_id_tree_path('')
453
transform.version_file('new-root', new_root)
456
def test_renames(self):
457
create, root = self.get_transform()
458
old = create.new_directory('old-parent', root, 'old-id')
459
intermediate = create.new_directory('intermediate', old, 'im-id')
460
myfile = create.new_file('myfile', intermediate, 'myfile-text',
463
rename, root = self.get_transform()
464
old = rename.trans_id_file_id('old-id')
465
rename.adjust_path('new', root, old)
466
myfile = rename.trans_id_file_id('myfile-id')
467
rename.set_executability(True, myfile)
470
def test_find_interesting(self):
471
create, root = self.get_transform()
473
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
474
create.new_file('uvfile', root, 'othertext')
476
self.assertEqual(find_interesting(wt, wt, ['vfile']),
478
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
482
class TransformGroup(object):
483
def __init__(self, dirname):
486
self.wt = BzrDir.create_standalone_workingtree(dirname)
487
self.b = self.wt.branch
488
self.tt = TreeTransform(self.wt)
489
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
491
def conflict_text(tree, merge):
492
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
493
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
496
class TestTransformMerge(TestCaseInTempDir):
497
def test_text_merge(self):
498
base = TransformGroup("base")
499
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
500
base.tt.new_file('b', base.root, 'b1', 'b')
501
base.tt.new_file('c', base.root, 'c', 'c')
502
base.tt.new_file('d', base.root, 'd', 'd')
503
base.tt.new_file('e', base.root, 'e', 'e')
504
base.tt.new_file('f', base.root, 'f', 'f')
505
base.tt.new_directory('g', base.root, 'g')
506
base.tt.new_directory('h', base.root, 'h')
508
other = TransformGroup("other")
509
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
510
other.tt.new_file('b', other.root, 'b2', 'b')
511
other.tt.new_file('c', other.root, 'c2', 'c')
512
other.tt.new_file('d', other.root, 'd', 'd')
513
other.tt.new_file('e', other.root, 'e2', 'e')
514
other.tt.new_file('f', other.root, 'f', 'f')
515
other.tt.new_file('g', other.root, 'g', 'g')
516
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
517
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
519
this = TransformGroup("this")
520
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
521
this.tt.new_file('b', this.root, 'b', 'b')
522
this.tt.new_file('c', this.root, 'c', 'c')
523
this.tt.new_file('d', this.root, 'd2', 'd')
524
this.tt.new_file('e', this.root, 'e2', 'e')
525
this.tt.new_file('f', this.root, 'f', 'f')
526
this.tt.new_file('g', this.root, 'g', 'g')
527
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
528
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
530
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
532
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
533
# three-way text conflict
534
self.assertEqual(this.wt.get_file('b').read(),
535
conflict_text('b', 'b2'))
537
self.assertEqual(this.wt.get_file('c').read(), 'c2')
539
self.assertEqual(this.wt.get_file('d').read(), 'd2')
540
# Ambigious clean merge
541
self.assertEqual(this.wt.get_file('e').read(), 'e2')
543
self.assertEqual(this.wt.get_file('f').read(), 'f')
544
# Correct correct results when THIS == OTHER
545
self.assertEqual(this.wt.get_file('g').read(), 'g')
546
# Text conflict when THIS & OTHER are text and BASE is dir
547
self.assertEqual(this.wt.get_file('h').read(),
548
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
549
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
551
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
553
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
554
self.assertEqual(this.wt.get_file('i').read(),
555
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
556
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
558
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
560
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
561
modified = ['a', 'b', 'c', 'h', 'i']
562
merge_modified = this.wt.merge_modified()
563
self.assertSubset(merge_modified, modified)
564
self.assertEqual(len(merge_modified), len(modified))
565
file(this.wt.id2abspath('a'), 'wb').write('booga')
567
merge_modified = this.wt.merge_modified()
568
self.assertSubset(merge_modified, modified)
569
self.assertEqual(len(merge_modified), len(modified))
571
def test_file_merge(self):
572
if not has_symlinks():
573
raise TestSkipped('Symlinks are not supported on this platform')
574
base = TransformGroup("BASE")
575
this = TransformGroup("THIS")
576
other = TransformGroup("OTHER")
577
for tg in this, base, other:
578
tg.tt.new_directory('a', tg.root, 'a')
579
tg.tt.new_symlink('b', tg.root, 'b', 'b')
580
tg.tt.new_file('c', tg.root, 'c', 'c')
581
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
582
targets = ((base, 'base-e', 'base-f', None, None),
583
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
584
(other, 'other-e', None, 'other-g', 'other-h'))
585
for tg, e_target, f_target, g_target, h_target in targets:
586
for link, target in (('e', e_target), ('f', f_target),
587
('g', g_target), ('h', h_target)):
588
if target is not None:
589
tg.tt.new_symlink(link, tg.root, target, link)
591
for tg in this, base, other:
593
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
594
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
595
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
596
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
597
for suffix in ('THIS', 'BASE', 'OTHER'):
598
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
599
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
600
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
601
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
602
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
603
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
604
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
605
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
606
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
607
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
608
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
609
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
610
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
611
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
613
def test_filename_merge(self):
614
base = TransformGroup("BASE")
615
this = TransformGroup("THIS")
616
other = TransformGroup("OTHER")
617
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
618
for t in [base, this, other]]
619
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
620
for t in [base, this, other]]
621
base.tt.new_directory('c', base_a, 'c')
622
this.tt.new_directory('c1', this_a, 'c')
623
other.tt.new_directory('c', other_b, 'c')
625
base.tt.new_directory('d', base_a, 'd')
626
this.tt.new_directory('d1', this_b, 'd')
627
other.tt.new_directory('d', other_a, 'd')
629
base.tt.new_directory('e', base_a, 'e')
630
this.tt.new_directory('e', this_a, 'e')
631
other.tt.new_directory('e1', other_b, 'e')
633
base.tt.new_directory('f', base_a, 'f')
634
this.tt.new_directory('f1', this_b, 'f')
635
other.tt.new_directory('f1', other_b, 'f')
637
for tg in [this, base, other]:
639
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
640
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
641
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
642
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
643
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
645
def test_filename_merge_conflicts(self):
646
base = TransformGroup("BASE")
647
this = TransformGroup("THIS")
648
other = TransformGroup("OTHER")
649
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
650
for t in [base, this, other]]
651
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
652
for t in [base, this, other]]
654
base.tt.new_file('g', base_a, 'g', 'g')
655
other.tt.new_file('g1', other_b, 'g1', 'g')
657
base.tt.new_file('h', base_a, 'h', 'h')
658
this.tt.new_file('h1', this_b, 'h1', 'h')
660
base.tt.new_file('i', base.root, 'i', 'i')
661
other.tt.new_directory('i1', this_b, 'i')
663
for tg in [this, base, other]:
665
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
667
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
668
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
669
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
670
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
671
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
672
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
673
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
675
class TestBuildTree(TestCaseInTempDir):
676
def test_build_tree(self):
677
if not has_symlinks():
678
raise TestSkipped('Test requires symlink support')
680
a = BzrDir.create_standalone_workingtree('a')
682
file('a/foo/bar', 'wb').write('contents')
683
os.symlink('a/foo/bar', 'a/foo/baz')
684
a.add(['foo', 'foo/bar', 'foo/baz'])
685
a.commit('initial commit')
686
b = BzrDir.create_standalone_workingtree('b')
687
build_tree(a.basis_tree(), b)
688
self.assertIs(os.path.isdir('b/foo'), True)
689
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
690
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')