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):
34
super(TestTreeTransform, self).setUp()
35
self.wt = BzrDir.create_standalone_workingtree('.')
38
def get_transform(self):
39
transform = TreeTransform(self.wt)
40
#self.addCleanup(transform.finalize)
41
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
43
def test_existing_limbo(self):
44
limbo_name = self.wt._control_files.controlfilename('limbo')
45
transform, root = self.get_transform()
46
os.mkdir(pathjoin(limbo_name, 'hehe'))
47
self.assertRaises(ImmortalLimbo, transform.apply)
48
self.assertRaises(LockError, self.wt.unlock)
49
self.assertRaises(ExistingLimbo, self.get_transform)
50
self.assertRaises(LockError, self.wt.unlock)
51
os.rmdir(pathjoin(limbo_name, 'hehe'))
53
transform, root = self.get_transform()
57
transform, root = self.get_transform()
58
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
imaginary_id = transform.trans_id_tree_path('imaginary')
60
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
61
self.assertEqual(transform.final_kind(root), 'directory')
62
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
63
trans_id = transform.create_path('name', root)
64
self.assertIs(transform.final_file_id(trans_id), None)
65
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
66
transform.create_file('contents', trans_id)
67
transform.set_executability(True, trans_id)
68
transform.version_file('my_pretties', trans_id)
69
self.assertRaises(DuplicateKey, transform.version_file,
70
'my_pretties', trans_id)
71
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
72
self.assertEqual(transform.final_parent(trans_id), root)
73
self.assertIs(transform.final_parent(root), ROOT_PARENT)
74
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
75
oz_id = transform.create_path('oz', root)
76
transform.create_directory(oz_id)
77
transform.version_file('ozzie', oz_id)
78
trans_id2 = transform.create_path('name2', root)
79
transform.create_file('contents', trans_id2)
80
transform.set_executability(False, trans_id2)
81
transform.version_file('my_pretties2', trans_id2)
82
modified_paths = transform.apply().modified_paths
83
self.assertEqual('contents', self.wt.get_file_byname('name').read())
84
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
85
self.assertIs(self.wt.is_executable('my_pretties'), True)
86
self.assertIs(self.wt.is_executable('my_pretties2'), False)
87
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
88
self.assertEqual(len(modified_paths), 3)
89
tree_mod_paths = [self.wt.id2abspath(f) for f in
90
('ozzie', 'my_pretties', 'my_pretties2')]
91
self.assertSubset(tree_mod_paths, modified_paths)
92
# is it safe to finalize repeatedly?
96
def test_convenience(self):
97
transform, root = self.get_transform()
98
trans_id = transform.new_file('name', root, 'contents',
100
oz = transform.new_directory('oz', root, 'oz-id')
101
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
102
toto = transform.new_file('toto', dorothy, 'toto-contents',
105
self.assertEqual(len(transform.find_conflicts()), 0)
107
self.assertRaises(ReusingTransform, transform.find_conflicts)
108
self.assertEqual('contents', file(self.wt.abspath('name')).read())
109
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
110
self.assertIs(self.wt.is_executable('my_pretties'), True)
111
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
112
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
113
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
115
self.assertEqual('toto-contents',
116
self.wt.get_file_byname('oz/dorothy/toto').read())
117
self.assertIs(self.wt.is_executable('toto-id'), False)
119
def test_conflicts(self):
120
transform, root = self.get_transform()
121
trans_id = transform.new_file('name', root, 'contents',
123
self.assertEqual(len(transform.find_conflicts()), 0)
124
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
125
self.assertEqual(transform.find_conflicts(),
126
[('duplicate', trans_id, trans_id2, 'name')])
127
self.assertRaises(MalformedTransform, transform.apply)
128
transform.adjust_path('name', trans_id, trans_id2)
129
self.assertEqual(transform.find_conflicts(),
130
[('non-directory parent', trans_id)])
131
tinman_id = transform.trans_id_tree_path('tinman')
132
transform.adjust_path('name', tinman_id, trans_id2)
133
self.assertEqual(transform.find_conflicts(),
134
[('unversioned parent', tinman_id),
135
('missing parent', tinman_id)])
136
lion_id = transform.create_path('lion', root)
137
self.assertEqual(transform.find_conflicts(),
138
[('unversioned parent', tinman_id),
139
('missing parent', tinman_id)])
140
transform.adjust_path('name', lion_id, trans_id2)
141
self.assertEqual(transform.find_conflicts(),
142
[('unversioned parent', lion_id),
143
('missing parent', lion_id)])
144
transform.version_file("Courage", lion_id)
145
self.assertEqual(transform.find_conflicts(),
146
[('missing parent', lion_id),
147
('versioning no contents', lion_id)])
148
transform.adjust_path('name2', root, trans_id2)
149
self.assertEqual(transform.find_conflicts(),
150
[('versioning no contents', lion_id)])
151
transform.create_file('Contents, okay?', lion_id)
152
transform.adjust_path('name2', trans_id2, trans_id2)
153
self.assertEqual(transform.find_conflicts(),
154
[('parent loop', trans_id2),
155
('non-directory parent', trans_id2)])
156
transform.adjust_path('name2', root, trans_id2)
157
oz_id = transform.new_directory('oz', root)
158
transform.set_executability(True, oz_id)
159
self.assertEqual(transform.find_conflicts(),
160
[('unversioned executability', oz_id)])
161
transform.version_file('oz-id', oz_id)
162
self.assertEqual(transform.find_conflicts(),
163
[('non-file executability', oz_id)])
164
transform.set_executability(None, oz_id)
165
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
167
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
169
transform2, root = self.get_transform()
170
oz_id = transform2.trans_id_tree_file_id('oz-id')
171
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
172
result = transform2.find_conflicts()
173
fp = FinalPaths(transform2)
174
self.assert_('oz/tip' in transform2._tree_path_ids)
175
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
176
self.assertEqual(len(result), 2)
177
self.assertEqual((result[0][0], result[0][1]),
178
('duplicate', newtip))
179
self.assertEqual((result[1][0], result[1][2]),
180
('duplicate id', newtip))
181
transform2.finalize()
182
transform3 = TreeTransform(self.wt)
183
self.addCleanup(transform3.finalize)
184
oz_id = transform3.trans_id_tree_file_id('oz-id')
185
transform3.delete_contents(oz_id)
186
self.assertEqual(transform3.find_conflicts(),
187
[('missing parent', oz_id)])
188
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
189
tip_id = transform3.trans_id_tree_file_id('tip-id')
190
transform3.adjust_path('tip', root_id, tip_id)
193
def test_add_del(self):
194
start, root = self.get_transform()
195
start.new_directory('a', root, 'a')
197
transform, root = self.get_transform()
198
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
199
transform.new_directory('a', root, 'a')
202
def test_unversioning(self):
203
create_tree, root = self.get_transform()
204
parent_id = create_tree.new_directory('parent', root, 'parent-id')
205
create_tree.new_file('child', parent_id, 'child', 'child-id')
207
unversion = TreeTransform(self.wt)
208
self.addCleanup(unversion.finalize)
209
parent = unversion.trans_id_tree_path('parent')
210
unversion.unversion_file(parent)
211
self.assertEqual(unversion.find_conflicts(),
212
[('unversioned parent', parent_id)])
213
file_id = unversion.trans_id_tree_file_id('child-id')
214
unversion.unversion_file(file_id)
217
def test_name_invariants(self):
218
create_tree, root = self.get_transform()
220
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
221
create_tree.new_file('name1', root, 'hello1', 'name1')
222
create_tree.new_file('name2', root, 'hello2', 'name2')
223
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
224
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
225
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
226
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
229
mangle_tree,root = self.get_transform()
230
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
232
name1 = mangle_tree.trans_id_tree_file_id('name1')
233
name2 = mangle_tree.trans_id_tree_file_id('name2')
234
mangle_tree.adjust_path('name2', root, name1)
235
mangle_tree.adjust_path('name1', root, name2)
237
#tests for deleting parent directories
238
ddir = mangle_tree.trans_id_tree_file_id('ddir')
239
mangle_tree.delete_contents(ddir)
240
dfile = mangle_tree.trans_id_tree_file_id('dfile')
241
mangle_tree.delete_versioned(dfile)
242
mangle_tree.unversion_file(dfile)
243
mfile = mangle_tree.trans_id_tree_file_id('mfile')
244
mangle_tree.adjust_path('mfile', root, mfile)
246
#tests for adding parent directories
247
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
248
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
249
mangle_tree.adjust_path('mfile2', newdir, mfile2)
250
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
251
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
252
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
253
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
255
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
256
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
257
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
258
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
259
self.assertEqual(file(mfile2_path).read(), 'later2')
260
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
261
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
262
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
263
self.assertEqual(file(newfile_path).read(), 'hello3')
264
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
265
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
266
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
268
def test_both_rename(self):
269
create_tree,root = self.get_transform()
270
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
271
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
273
mangle_tree,root = self.get_transform()
274
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
275
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
276
mangle_tree.adjust_path('test', root, selftest)
277
mangle_tree.adjust_path('test_too_much', root, selftest)
278
mangle_tree.set_executability(True, blackbox)
281
def test_both_rename2(self):
282
create_tree,root = self.get_transform()
283
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
284
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
285
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
286
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
289
mangle_tree,root = self.get_transform()
290
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
291
tests = mangle_tree.trans_id_tree_file_id('tests-id')
292
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
293
mangle_tree.adjust_path('selftest', bzrlib, tests)
294
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
295
mangle_tree.set_executability(True, test_too_much)
298
def test_both_rename3(self):
299
create_tree,root = self.get_transform()
300
tests = create_tree.new_directory('tests', root, 'tests-id')
301
create_tree.new_file('test_too_much.py', tests, 'hello1',
304
mangle_tree,root = self.get_transform()
305
tests = mangle_tree.trans_id_tree_file_id('tests-id')
306
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
307
mangle_tree.adjust_path('selftest', root, tests)
308
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
309
mangle_tree.set_executability(True, test_too_much)
312
def test_move_dangling_ie(self):
313
create_tree, root = self.get_transform()
315
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
316
create_tree.new_file('name1', root, 'hello1', 'name1')
318
delete_contents, root = self.get_transform()
319
file = delete_contents.trans_id_tree_file_id('name1')
320
delete_contents.delete_contents(file)
321
delete_contents.apply()
322
move_id, root = self.get_transform()
323
name1 = move_id.trans_id_tree_file_id('name1')
324
newdir = move_id.new_directory('dir', root, 'newdir')
325
move_id.adjust_path('name2', newdir, name1)
328
def test_replace_dangling_ie(self):
329
create_tree, root = self.get_transform()
331
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
332
create_tree.new_file('name1', root, 'hello1', 'name1')
334
delete_contents = TreeTransform(self.wt)
335
self.addCleanup(delete_contents.finalize)
336
file = delete_contents.trans_id_tree_file_id('name1')
337
delete_contents.delete_contents(file)
338
delete_contents.apply()
339
delete_contents.finalize()
340
replace = TreeTransform(self.wt)
341
self.addCleanup(replace.finalize)
342
name2 = replace.new_file('name2', root, 'hello2', 'name1')
343
conflicts = replace.find_conflicts()
344
name1 = replace.trans_id_tree_file_id('name1')
345
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
346
resolve_conflicts(replace)
349
def test_symlinks(self):
350
if not has_symlinks():
351
raise TestSkipped('Symlinks are not supported on this platform')
352
transform,root = self.get_transform()
353
oz_id = transform.new_directory('oz', root, 'oz-id')
354
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
356
wiz_id = transform.create_path('wizard2', oz_id)
357
transform.create_symlink('behind_curtain', wiz_id)
358
transform.version_file('wiz-id2', wiz_id)
359
transform.set_executability(True, wiz_id)
360
self.assertEqual(transform.find_conflicts(),
361
[('non-file executability', wiz_id)])
362
transform.set_executability(None, wiz_id)
364
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
365
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
366
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
368
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
372
def get_conflicted(self):
373
create,root = self.get_transform()
374
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
375
oz = create.new_directory('oz', root, 'oz-id')
376
create.new_directory('emeraldcity', oz, 'emerald-id')
378
conflicts,root = self.get_transform()
379
# set up duplicate entry, duplicate id
380
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
382
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
383
oz = conflicts.trans_id_tree_file_id('oz-id')
384
# set up missing, unversioned parent
385
conflicts.delete_versioned(oz)
386
emerald = conflicts.trans_id_tree_file_id('emerald-id')
388
conflicts.adjust_path('emeraldcity', emerald, emerald)
389
return conflicts, emerald, oz, old_dorothy, new_dorothy
391
def test_conflict_resolution(self):
392
conflicts, emerald, oz, old_dorothy, new_dorothy =\
393
self.get_conflicted()
394
resolve_conflicts(conflicts)
395
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
396
self.assertIs(conflicts.final_file_id(old_dorothy), None)
397
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
398
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
399
self.assertEqual(conflicts.final_parent(emerald), oz)
402
def test_cook_conflicts(self):
403
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
404
raw_conflicts = resolve_conflicts(tt)
405
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
406
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
407
'dorothy', None, 'dorothy-id')
408
self.assertEqual(cooked_conflicts[0], duplicate)
409
duplicate_id = DuplicateID('Unversioned existing file',
410
'dorothy.moved', 'dorothy', None,
412
self.assertEqual(cooked_conflicts[1], duplicate_id)
413
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
414
self.assertEqual(cooked_conflicts[2], missing_parent)
415
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
417
self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
419
'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
self.assertEqual(cooked_conflicts[4], parent_loop)
421
self.assertEqual(len(cooked_conflicts), 5)
424
def test_string_conflicts(self):
425
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
426
raw_conflicts = resolve_conflicts(tt)
427
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
429
conflicts_s = [str(c) for c in cooked_conflicts]
430
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
431
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
432
'Moved existing file to '
434
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
'Unversioned existing file '
437
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
439
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
'oz. Versioned directory.')
441
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
442
' oz/emeraldcity. Cancelled move.')
444
def test_moving_versioned_directories(self):
445
create, root = self.get_transform()
446
kansas = create.new_directory('kansas', root, 'kansas-id')
447
create.new_directory('house', kansas, 'house-id')
448
create.new_directory('oz', root, 'oz-id')
450
cyclone, root = self.get_transform()
451
oz = cyclone.trans_id_tree_file_id('oz-id')
452
house = cyclone.trans_id_tree_file_id('house-id')
453
cyclone.adjust_path('house', oz, house)
456
def test_moving_root(self):
457
create, root = self.get_transform()
458
fun = create.new_directory('fun', root, 'fun-id')
459
create.new_directory('sun', root, 'sun-id')
460
create.new_directory('moon', root, 'moon')
462
transform, root = self.get_transform()
463
transform.adjust_root_path('oldroot', fun)
464
new_root=transform.trans_id_tree_path('')
465
transform.version_file('new-root', new_root)
468
def test_renames(self):
469
create, root = self.get_transform()
470
old = create.new_directory('old-parent', root, 'old-id')
471
intermediate = create.new_directory('intermediate', old, 'im-id')
472
myfile = create.new_file('myfile', intermediate, 'myfile-text',
475
rename, root = self.get_transform()
476
old = rename.trans_id_file_id('old-id')
477
rename.adjust_path('new', root, old)
478
myfile = rename.trans_id_file_id('myfile-id')
479
rename.set_executability(True, myfile)
482
def test_find_interesting(self):
483
create, root = self.get_transform()
485
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
create.new_file('uvfile', root, 'othertext')
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
494
class TransformGroup(object):
495
def __init__(self, dirname):
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
499
self.b = self.wt.branch
500
self.tt = TreeTransform(self.wt)
501
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
def conflict_text(tree, merge):
504
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
508
class TestTransformMerge(TestCaseInTempDir):
509
def test_text_merge(self):
510
base = TransformGroup("base")
511
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
base.tt.new_file('b', base.root, 'b1', 'b')
513
base.tt.new_file('c', base.root, 'c', 'c')
514
base.tt.new_file('d', base.root, 'd', 'd')
515
base.tt.new_file('e', base.root, 'e', 'e')
516
base.tt.new_file('f', base.root, 'f', 'f')
517
base.tt.new_directory('g', base.root, 'g')
518
base.tt.new_directory('h', base.root, 'h')
520
other = TransformGroup("other")
521
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
522
other.tt.new_file('b', other.root, 'b2', 'b')
523
other.tt.new_file('c', other.root, 'c2', 'c')
524
other.tt.new_file('d', other.root, 'd', 'd')
525
other.tt.new_file('e', other.root, 'e2', 'e')
526
other.tt.new_file('f', other.root, 'f', 'f')
527
other.tt.new_file('g', other.root, 'g', 'g')
528
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
529
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
531
this = TransformGroup("this")
532
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
533
this.tt.new_file('b', this.root, 'b', 'b')
534
this.tt.new_file('c', this.root, 'c', 'c')
535
this.tt.new_file('d', this.root, 'd2', 'd')
536
this.tt.new_file('e', this.root, 'e2', 'e')
537
this.tt.new_file('f', this.root, 'f', 'f')
538
this.tt.new_file('g', this.root, 'g', 'g')
539
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
540
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
542
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
544
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
545
# three-way text conflict
546
self.assertEqual(this.wt.get_file('b').read(),
547
conflict_text('b', 'b2'))
549
self.assertEqual(this.wt.get_file('c').read(), 'c2')
551
self.assertEqual(this.wt.get_file('d').read(), 'd2')
552
# Ambigious clean merge
553
self.assertEqual(this.wt.get_file('e').read(), 'e2')
555
self.assertEqual(this.wt.get_file('f').read(), 'f')
556
# Correct correct results when THIS == OTHER
557
self.assertEqual(this.wt.get_file('g').read(), 'g')
558
# Text conflict when THIS & OTHER are text and BASE is dir
559
self.assertEqual(this.wt.get_file('h').read(),
560
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
561
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
563
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
565
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
566
self.assertEqual(this.wt.get_file('i').read(),
567
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
568
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
570
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
572
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
573
modified = ['a', 'b', 'c', 'h', 'i']
574
merge_modified = this.wt.merge_modified()
575
self.assertSubset(merge_modified, modified)
576
self.assertEqual(len(merge_modified), len(modified))
577
file(this.wt.id2abspath('a'), 'wb').write('booga')
579
merge_modified = this.wt.merge_modified()
580
self.assertSubset(merge_modified, modified)
581
self.assertEqual(len(merge_modified), len(modified))
585
def test_file_merge(self):
586
if not has_symlinks():
587
raise TestSkipped('Symlinks are not supported on this platform')
588
base = TransformGroup("BASE")
589
this = TransformGroup("THIS")
590
other = TransformGroup("OTHER")
591
for tg in this, base, other:
592
tg.tt.new_directory('a', tg.root, 'a')
593
tg.tt.new_symlink('b', tg.root, 'b', 'b')
594
tg.tt.new_file('c', tg.root, 'c', 'c')
595
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
596
targets = ((base, 'base-e', 'base-f', None, None),
597
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
598
(other, 'other-e', None, 'other-g', 'other-h'))
599
for tg, e_target, f_target, g_target, h_target in targets:
600
for link, target in (('e', e_target), ('f', f_target),
601
('g', g_target), ('h', h_target)):
602
if target is not None:
603
tg.tt.new_symlink(link, tg.root, target, link)
605
for tg in this, base, other:
607
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
608
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
609
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
610
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
611
for suffix in ('THIS', 'BASE', 'OTHER'):
612
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
613
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
614
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
615
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
616
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
617
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
618
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
619
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
620
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
621
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
622
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
623
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
624
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
625
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
627
def test_filename_merge(self):
628
base = TransformGroup("BASE")
629
this = TransformGroup("THIS")
630
other = TransformGroup("OTHER")
631
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
632
for t in [base, this, other]]
633
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
634
for t in [base, this, other]]
635
base.tt.new_directory('c', base_a, 'c')
636
this.tt.new_directory('c1', this_a, 'c')
637
other.tt.new_directory('c', other_b, 'c')
639
base.tt.new_directory('d', base_a, 'd')
640
this.tt.new_directory('d1', this_b, 'd')
641
other.tt.new_directory('d', other_a, 'd')
643
base.tt.new_directory('e', base_a, 'e')
644
this.tt.new_directory('e', this_a, 'e')
645
other.tt.new_directory('e1', other_b, 'e')
647
base.tt.new_directory('f', base_a, 'f')
648
this.tt.new_directory('f1', this_b, 'f')
649
other.tt.new_directory('f1', other_b, 'f')
651
for tg in [this, base, other]:
653
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
654
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
655
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
656
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
657
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
659
def test_filename_merge_conflicts(self):
660
base = TransformGroup("BASE")
661
this = TransformGroup("THIS")
662
other = TransformGroup("OTHER")
663
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
664
for t in [base, this, other]]
665
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
666
for t in [base, this, other]]
668
base.tt.new_file('g', base_a, 'g', 'g')
669
other.tt.new_file('g1', other_b, 'g1', 'g')
671
base.tt.new_file('h', base_a, 'h', 'h')
672
this.tt.new_file('h1', this_b, 'h1', 'h')
674
base.tt.new_file('i', base.root, 'i', 'i')
675
other.tt.new_directory('i1', this_b, 'i')
677
for tg in [this, base, other]:
679
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
681
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
682
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
683
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
684
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
685
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
686
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
689
class TestBuildTree(TestCaseInTempDir):
690
def test_build_tree(self):
691
if not has_symlinks():
692
raise TestSkipped('Test requires symlink support')
694
a = BzrDir.create_standalone_workingtree('a')
696
file('a/foo/bar', 'wb').write('contents')
697
os.symlink('a/foo/bar', 'a/foo/baz')
698
a.add(['foo', 'foo/bar', 'foo/baz'])
699
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
702
self.assertIs(os.path.isdir('b/foo'), True)
703
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
706
class MockTransform(object):
708
def has_named_child(self, by_parent, parent_id, name):
709
for child_id in by_parent[parent_id]:
713
elif name == "name.~%s~" % child_id:
717
class MockEntry(object):
719
object.__init__(self)
722
class TestGetBackupName(TestCase):
723
def test_get_backup_name(self):
725
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
self.assertEqual(name, 'name.~1~')
727
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
self.assertEqual(name, 'name.~2~')
729
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
self.assertEqual(name, 'name.~1~')
731
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
self.assertEqual(name, 'name.~4~')