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
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
61
self.assertEqual(imaginary_id, imaginary_id2)
62
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
63
self.assertEqual(transform.final_kind(root), 'directory')
64
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
65
trans_id = transform.create_path('name', root)
66
self.assertIs(transform.final_file_id(trans_id), None)
67
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
68
transform.create_file('contents', trans_id)
69
transform.set_executability(True, trans_id)
70
transform.version_file('my_pretties', trans_id)
71
self.assertRaises(DuplicateKey, transform.version_file,
72
'my_pretties', trans_id)
73
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
74
self.assertEqual(transform.final_parent(trans_id), root)
75
self.assertIs(transform.final_parent(root), ROOT_PARENT)
76
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
77
oz_id = transform.create_path('oz', root)
78
transform.create_directory(oz_id)
79
transform.version_file('ozzie', oz_id)
80
trans_id2 = transform.create_path('name2', root)
81
transform.create_file('contents', trans_id2)
82
transform.set_executability(False, trans_id2)
83
transform.version_file('my_pretties2', trans_id2)
84
modified_paths = transform.apply().modified_paths
85
self.assertEqual('contents', self.wt.get_file_byname('name').read())
86
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
87
self.assertIs(self.wt.is_executable('my_pretties'), True)
88
self.assertIs(self.wt.is_executable('my_pretties2'), False)
89
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
90
self.assertEqual(len(modified_paths), 3)
91
tree_mod_paths = [self.wt.id2abspath(f) for f in
92
('ozzie', 'my_pretties', 'my_pretties2')]
93
self.assertSubset(tree_mod_paths, modified_paths)
94
# is it safe to finalize repeatedly?
98
def test_convenience(self):
99
transform, root = self.get_transform()
100
trans_id = transform.new_file('name', root, 'contents',
102
oz = transform.new_directory('oz', root, 'oz-id')
103
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
104
toto = transform.new_file('toto', dorothy, 'toto-contents',
107
self.assertEqual(len(transform.find_conflicts()), 0)
109
self.assertRaises(ReusingTransform, transform.find_conflicts)
110
self.assertEqual('contents', file(self.wt.abspath('name')).read())
111
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
112
self.assertIs(self.wt.is_executable('my_pretties'), True)
113
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
114
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
115
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
117
self.assertEqual('toto-contents',
118
self.wt.get_file_byname('oz/dorothy/toto').read())
119
self.assertIs(self.wt.is_executable('toto-id'), False)
121
def test_conflicts(self):
122
transform, root = self.get_transform()
123
trans_id = transform.new_file('name', root, 'contents',
125
self.assertEqual(len(transform.find_conflicts()), 0)
126
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
127
self.assertEqual(transform.find_conflicts(),
128
[('duplicate', trans_id, trans_id2, 'name')])
129
self.assertRaises(MalformedTransform, transform.apply)
130
transform.adjust_path('name', trans_id, trans_id2)
131
self.assertEqual(transform.find_conflicts(),
132
[('non-directory parent', trans_id)])
133
tinman_id = transform.trans_id_tree_path('tinman')
134
transform.adjust_path('name', tinman_id, trans_id2)
135
self.assertEqual(transform.find_conflicts(),
136
[('unversioned parent', tinman_id),
137
('missing parent', tinman_id)])
138
lion_id = transform.create_path('lion', root)
139
self.assertEqual(transform.find_conflicts(),
140
[('unversioned parent', tinman_id),
141
('missing parent', tinman_id)])
142
transform.adjust_path('name', lion_id, trans_id2)
143
self.assertEqual(transform.find_conflicts(),
144
[('unversioned parent', lion_id),
145
('missing parent', lion_id)])
146
transform.version_file("Courage", lion_id)
147
self.assertEqual(transform.find_conflicts(),
148
[('missing parent', lion_id),
149
('versioning no contents', lion_id)])
150
transform.adjust_path('name2', root, trans_id2)
151
self.assertEqual(transform.find_conflicts(),
152
[('versioning no contents', lion_id)])
153
transform.create_file('Contents, okay?', lion_id)
154
transform.adjust_path('name2', trans_id2, trans_id2)
155
self.assertEqual(transform.find_conflicts(),
156
[('parent loop', trans_id2),
157
('non-directory parent', trans_id2)])
158
transform.adjust_path('name2', root, trans_id2)
159
oz_id = transform.new_directory('oz', root)
160
transform.set_executability(True, oz_id)
161
self.assertEqual(transform.find_conflicts(),
162
[('unversioned executability', oz_id)])
163
transform.version_file('oz-id', oz_id)
164
self.assertEqual(transform.find_conflicts(),
165
[('non-file executability', oz_id)])
166
transform.set_executability(None, oz_id)
167
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
169
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
170
self.assertEqual('contents', file(self.wt.abspath('name')).read())
171
transform2, root = self.get_transform()
172
oz_id = transform2.trans_id_tree_file_id('oz-id')
173
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
174
result = transform2.find_conflicts()
175
fp = FinalPaths(transform2)
176
self.assert_('oz/tip' in transform2._tree_path_ids)
177
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
178
self.assertEqual(len(result), 2)
179
self.assertEqual((result[0][0], result[0][1]),
180
('duplicate', newtip))
181
self.assertEqual((result[1][0], result[1][2]),
182
('duplicate id', newtip))
183
transform2.finalize()
184
transform3 = TreeTransform(self.wt)
185
self.addCleanup(transform3.finalize)
186
oz_id = transform3.trans_id_tree_file_id('oz-id')
187
transform3.delete_contents(oz_id)
188
self.assertEqual(transform3.find_conflicts(),
189
[('missing parent', oz_id)])
190
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
191
tip_id = transform3.trans_id_tree_file_id('tip-id')
192
transform3.adjust_path('tip', root_id, tip_id)
195
def test_add_del(self):
196
start, root = self.get_transform()
197
start.new_directory('a', root, 'a')
199
transform, root = self.get_transform()
200
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
201
transform.new_directory('a', root, 'a')
204
def test_unversioning(self):
205
create_tree, root = self.get_transform()
206
parent_id = create_tree.new_directory('parent', root, 'parent-id')
207
create_tree.new_file('child', parent_id, 'child', 'child-id')
209
unversion = TreeTransform(self.wt)
210
self.addCleanup(unversion.finalize)
211
parent = unversion.trans_id_tree_path('parent')
212
unversion.unversion_file(parent)
213
self.assertEqual(unversion.find_conflicts(),
214
[('unversioned parent', parent_id)])
215
file_id = unversion.trans_id_tree_file_id('child-id')
216
unversion.unversion_file(file_id)
219
def test_name_invariants(self):
220
create_tree, root = self.get_transform()
222
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
223
create_tree.new_file('name1', root, 'hello1', 'name1')
224
create_tree.new_file('name2', root, 'hello2', 'name2')
225
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
226
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
227
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
228
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
231
mangle_tree,root = self.get_transform()
232
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
234
name1 = mangle_tree.trans_id_tree_file_id('name1')
235
name2 = mangle_tree.trans_id_tree_file_id('name2')
236
mangle_tree.adjust_path('name2', root, name1)
237
mangle_tree.adjust_path('name1', root, name2)
239
#tests for deleting parent directories
240
ddir = mangle_tree.trans_id_tree_file_id('ddir')
241
mangle_tree.delete_contents(ddir)
242
dfile = mangle_tree.trans_id_tree_file_id('dfile')
243
mangle_tree.delete_versioned(dfile)
244
mangle_tree.unversion_file(dfile)
245
mfile = mangle_tree.trans_id_tree_file_id('mfile')
246
mangle_tree.adjust_path('mfile', root, mfile)
248
#tests for adding parent directories
249
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
250
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
251
mangle_tree.adjust_path('mfile2', newdir, mfile2)
252
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
253
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
254
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
255
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
257
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
258
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
259
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
260
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
261
self.assertEqual(file(mfile2_path).read(), 'later2')
262
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
263
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
264
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
265
self.assertEqual(file(newfile_path).read(), 'hello3')
266
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
267
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
268
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
270
def test_both_rename(self):
271
create_tree,root = self.get_transform()
272
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
273
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
275
mangle_tree,root = self.get_transform()
276
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
277
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
278
mangle_tree.adjust_path('test', root, selftest)
279
mangle_tree.adjust_path('test_too_much', root, selftest)
280
mangle_tree.set_executability(True, blackbox)
283
def test_both_rename2(self):
284
create_tree,root = self.get_transform()
285
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
286
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
287
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
288
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
291
mangle_tree,root = self.get_transform()
292
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
293
tests = mangle_tree.trans_id_tree_file_id('tests-id')
294
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
295
mangle_tree.adjust_path('selftest', bzrlib, tests)
296
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
297
mangle_tree.set_executability(True, test_too_much)
300
def test_both_rename3(self):
301
create_tree,root = self.get_transform()
302
tests = create_tree.new_directory('tests', root, 'tests-id')
303
create_tree.new_file('test_too_much.py', tests, 'hello1',
306
mangle_tree,root = self.get_transform()
307
tests = mangle_tree.trans_id_tree_file_id('tests-id')
308
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
309
mangle_tree.adjust_path('selftest', root, tests)
310
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
311
mangle_tree.set_executability(True, test_too_much)
314
def test_move_dangling_ie(self):
315
create_tree, root = self.get_transform()
317
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
318
create_tree.new_file('name1', root, 'hello1', 'name1')
320
delete_contents, root = self.get_transform()
321
file = delete_contents.trans_id_tree_file_id('name1')
322
delete_contents.delete_contents(file)
323
delete_contents.apply()
324
move_id, root = self.get_transform()
325
name1 = move_id.trans_id_tree_file_id('name1')
326
newdir = move_id.new_directory('dir', root, 'newdir')
327
move_id.adjust_path('name2', newdir, name1)
330
def test_replace_dangling_ie(self):
331
create_tree, root = self.get_transform()
333
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
334
create_tree.new_file('name1', root, 'hello1', 'name1')
336
delete_contents = TreeTransform(self.wt)
337
self.addCleanup(delete_contents.finalize)
338
file = delete_contents.trans_id_tree_file_id('name1')
339
delete_contents.delete_contents(file)
340
delete_contents.apply()
341
delete_contents.finalize()
342
replace = TreeTransform(self.wt)
343
self.addCleanup(replace.finalize)
344
name2 = replace.new_file('name2', root, 'hello2', 'name1')
345
conflicts = replace.find_conflicts()
346
name1 = replace.trans_id_tree_file_id('name1')
347
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
348
resolve_conflicts(replace)
351
def test_symlinks(self):
352
if not has_symlinks():
353
raise TestSkipped('Symlinks are not supported on this platform')
354
transform,root = self.get_transform()
355
oz_id = transform.new_directory('oz', root, 'oz-id')
356
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
358
wiz_id = transform.create_path('wizard2', oz_id)
359
transform.create_symlink('behind_curtain', wiz_id)
360
transform.version_file('wiz-id2', wiz_id)
361
transform.set_executability(True, wiz_id)
362
self.assertEqual(transform.find_conflicts(),
363
[('non-file executability', wiz_id)])
364
transform.set_executability(None, wiz_id)
366
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
367
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
368
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
370
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
374
def get_conflicted(self):
375
create,root = self.get_transform()
376
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
377
oz = create.new_directory('oz', root, 'oz-id')
378
create.new_directory('emeraldcity', oz, 'emerald-id')
380
conflicts,root = self.get_transform()
381
# set up duplicate entry, duplicate id
382
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
384
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
385
oz = conflicts.trans_id_tree_file_id('oz-id')
386
# set up missing, unversioned parent
387
conflicts.delete_versioned(oz)
388
emerald = conflicts.trans_id_tree_file_id('emerald-id')
390
conflicts.adjust_path('emeraldcity', emerald, emerald)
391
return conflicts, emerald, oz, old_dorothy, new_dorothy
393
def test_conflict_resolution(self):
394
conflicts, emerald, oz, old_dorothy, new_dorothy =\
395
self.get_conflicted()
396
resolve_conflicts(conflicts)
397
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
398
self.assertIs(conflicts.final_file_id(old_dorothy), None)
399
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
400
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
401
self.assertEqual(conflicts.final_parent(emerald), oz)
404
def test_cook_conflicts(self):
405
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
406
raw_conflicts = resolve_conflicts(tt)
407
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
408
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
409
'dorothy', None, 'dorothy-id')
410
self.assertEqual(cooked_conflicts[0], duplicate)
411
duplicate_id = DuplicateID('Unversioned existing file',
412
'dorothy.moved', 'dorothy', None,
414
self.assertEqual(cooked_conflicts[1], duplicate_id)
415
missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
416
self.assertEqual(cooked_conflicts[2], missing_parent)
417
unversioned_parent = UnversionedParent('Versioned directory', 'oz',
419
self.assertEqual(cooked_conflicts[3], unversioned_parent)
420
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
421
'oz/emeraldcity', 'emerald-id', 'emerald-id')
422
self.assertEqual(cooked_conflicts[4], parent_loop)
423
self.assertEqual(len(cooked_conflicts), 5)
426
def test_string_conflicts(self):
427
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
428
raw_conflicts = resolve_conflicts(tt)
429
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
431
conflicts_s = [str(c) for c in cooked_conflicts]
432
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
433
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
434
'Moved existing file to '
436
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
437
'Unversioned existing file '
439
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
441
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
442
'oz. Versioned directory.')
443
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
444
' oz/emeraldcity. Cancelled move.')
446
def test_moving_versioned_directories(self):
447
create, root = self.get_transform()
448
kansas = create.new_directory('kansas', root, 'kansas-id')
449
create.new_directory('house', kansas, 'house-id')
450
create.new_directory('oz', root, 'oz-id')
452
cyclone, root = self.get_transform()
453
oz = cyclone.trans_id_tree_file_id('oz-id')
454
house = cyclone.trans_id_tree_file_id('house-id')
455
cyclone.adjust_path('house', oz, house)
458
def test_moving_root(self):
459
create, root = self.get_transform()
460
fun = create.new_directory('fun', root, 'fun-id')
461
create.new_directory('sun', root, 'sun-id')
462
create.new_directory('moon', root, 'moon')
464
transform, root = self.get_transform()
465
transform.adjust_root_path('oldroot', fun)
466
new_root=transform.trans_id_tree_path('')
467
transform.version_file('new-root', new_root)
470
def test_renames(self):
471
create, root = self.get_transform()
472
old = create.new_directory('old-parent', root, 'old-id')
473
intermediate = create.new_directory('intermediate', old, 'im-id')
474
myfile = create.new_file('myfile', intermediate, 'myfile-text',
477
rename, root = self.get_transform()
478
old = rename.trans_id_file_id('old-id')
479
rename.adjust_path('new', root, old)
480
myfile = rename.trans_id_file_id('myfile-id')
481
rename.set_executability(True, myfile)
484
def test_find_interesting(self):
485
create, root = self.get_transform()
487
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
488
create.new_file('uvfile', root, 'othertext')
490
self.assertEqual(find_interesting(wt, wt, ['vfile']),
492
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
496
class TransformGroup(object):
497
def __init__(self, dirname):
500
self.wt = BzrDir.create_standalone_workingtree(dirname)
501
self.b = self.wt.branch
502
self.tt = TreeTransform(self.wt)
503
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
505
def conflict_text(tree, merge):
506
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
507
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
510
class TestTransformMerge(TestCaseInTempDir):
511
def test_text_merge(self):
512
base = TransformGroup("base")
513
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
514
base.tt.new_file('b', base.root, 'b1', 'b')
515
base.tt.new_file('c', base.root, 'c', 'c')
516
base.tt.new_file('d', base.root, 'd', 'd')
517
base.tt.new_file('e', base.root, 'e', 'e')
518
base.tt.new_file('f', base.root, 'f', 'f')
519
base.tt.new_directory('g', base.root, 'g')
520
base.tt.new_directory('h', base.root, 'h')
522
other = TransformGroup("other")
523
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
524
other.tt.new_file('b', other.root, 'b2', 'b')
525
other.tt.new_file('c', other.root, 'c2', 'c')
526
other.tt.new_file('d', other.root, 'd', 'd')
527
other.tt.new_file('e', other.root, 'e2', 'e')
528
other.tt.new_file('f', other.root, 'f', 'f')
529
other.tt.new_file('g', other.root, 'g', 'g')
530
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
531
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
533
this = TransformGroup("this")
534
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
535
this.tt.new_file('b', this.root, 'b', 'b')
536
this.tt.new_file('c', this.root, 'c', 'c')
537
this.tt.new_file('d', this.root, 'd2', 'd')
538
this.tt.new_file('e', this.root, 'e2', 'e')
539
this.tt.new_file('f', this.root, 'f', 'f')
540
this.tt.new_file('g', this.root, 'g', 'g')
541
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
542
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
544
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
546
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
547
# three-way text conflict
548
self.assertEqual(this.wt.get_file('b').read(),
549
conflict_text('b', 'b2'))
551
self.assertEqual(this.wt.get_file('c').read(), 'c2')
553
self.assertEqual(this.wt.get_file('d').read(), 'd2')
554
# Ambigious clean merge
555
self.assertEqual(this.wt.get_file('e').read(), 'e2')
557
self.assertEqual(this.wt.get_file('f').read(), 'f')
558
# Correct correct results when THIS == OTHER
559
self.assertEqual(this.wt.get_file('g').read(), 'g')
560
# Text conflict when THIS & OTHER are text and BASE is dir
561
self.assertEqual(this.wt.get_file('h').read(),
562
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
563
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
565
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
567
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
568
self.assertEqual(this.wt.get_file('i').read(),
569
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
570
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
572
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
574
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
575
modified = ['a', 'b', 'c', 'h', 'i']
576
merge_modified = this.wt.merge_modified()
577
self.assertSubset(merge_modified, modified)
578
self.assertEqual(len(merge_modified), len(modified))
579
file(this.wt.id2abspath('a'), 'wb').write('booga')
581
merge_modified = this.wt.merge_modified()
582
self.assertSubset(merge_modified, modified)
583
self.assertEqual(len(merge_modified), len(modified))
587
def test_file_merge(self):
588
if not has_symlinks():
589
raise TestSkipped('Symlinks are not supported on this platform')
590
base = TransformGroup("BASE")
591
this = TransformGroup("THIS")
592
other = TransformGroup("OTHER")
593
for tg in this, base, other:
594
tg.tt.new_directory('a', tg.root, 'a')
595
tg.tt.new_symlink('b', tg.root, 'b', 'b')
596
tg.tt.new_file('c', tg.root, 'c', 'c')
597
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
598
targets = ((base, 'base-e', 'base-f', None, None),
599
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
600
(other, 'other-e', None, 'other-g', 'other-h'))
601
for tg, e_target, f_target, g_target, h_target in targets:
602
for link, target in (('e', e_target), ('f', f_target),
603
('g', g_target), ('h', h_target)):
604
if target is not None:
605
tg.tt.new_symlink(link, tg.root, target, link)
607
for tg in this, base, other:
609
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
610
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
611
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
612
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
613
for suffix in ('THIS', 'BASE', 'OTHER'):
614
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
615
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
616
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
617
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
618
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
619
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
620
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
621
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
622
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
623
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
624
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
625
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
626
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
627
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
629
def test_filename_merge(self):
630
base = TransformGroup("BASE")
631
this = TransformGroup("THIS")
632
other = TransformGroup("OTHER")
633
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
634
for t in [base, this, other]]
635
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
636
for t in [base, this, other]]
637
base.tt.new_directory('c', base_a, 'c')
638
this.tt.new_directory('c1', this_a, 'c')
639
other.tt.new_directory('c', other_b, 'c')
641
base.tt.new_directory('d', base_a, 'd')
642
this.tt.new_directory('d1', this_b, 'd')
643
other.tt.new_directory('d', other_a, 'd')
645
base.tt.new_directory('e', base_a, 'e')
646
this.tt.new_directory('e', this_a, 'e')
647
other.tt.new_directory('e1', other_b, 'e')
649
base.tt.new_directory('f', base_a, 'f')
650
this.tt.new_directory('f1', this_b, 'f')
651
other.tt.new_directory('f1', other_b, 'f')
653
for tg in [this, base, other]:
655
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
656
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
657
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
658
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
659
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
661
def test_filename_merge_conflicts(self):
662
base = TransformGroup("BASE")
663
this = TransformGroup("THIS")
664
other = TransformGroup("OTHER")
665
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
666
for t in [base, this, other]]
667
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
668
for t in [base, this, other]]
670
base.tt.new_file('g', base_a, 'g', 'g')
671
other.tt.new_file('g1', other_b, 'g1', 'g')
673
base.tt.new_file('h', base_a, 'h', 'h')
674
this.tt.new_file('h1', this_b, 'h1', 'h')
676
base.tt.new_file('i', base.root, 'i', 'i')
677
other.tt.new_directory('i1', this_b, 'i')
679
for tg in [this, base, other]:
681
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
683
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
684
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
685
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
686
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
687
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
688
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
689
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
691
class TestBuildTree(TestCaseInTempDir):
692
def test_build_tree(self):
693
if not has_symlinks():
694
raise TestSkipped('Test requires symlink support')
696
a = BzrDir.create_standalone_workingtree('a')
698
file('a/foo/bar', 'wb').write('contents')
699
os.symlink('a/foo/bar', 'a/foo/baz')
700
a.add(['foo', 'foo/bar', 'foo/baz'])
701
a.commit('initial commit')
702
b = BzrDir.create_standalone_workingtree('b')
703
build_tree(a.basis_tree(), b)
704
self.assertIs(os.path.isdir('b/foo'), True)
705
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
706
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
708
class MockTransform(object):
710
def has_named_child(self, by_parent, parent_id, name):
711
for child_id in by_parent[parent_id]:
715
elif name == "name.~%s~" % child_id:
719
class MockEntry(object):
721
object.__init__(self)
724
class TestGetBackupName(TestCase):
725
def test_get_backup_name(self):
727
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
728
self.assertEqual(name, 'name.~1~')
729
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
730
self.assertEqual(name, 'name.~2~')
731
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
734
self.assertEqual(name, 'name.~1~')
735
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
736
self.assertEqual(name, 'name.~4~')