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 import tests
20
from bzrlib.bzrdir import BzrDir
21
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
22
UnversionedParent, ParentLoop, DeletingParent,)
23
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
24
ReusingTransform, CantMoveRoot,
25
PathsNotVersionedError, ExistingLimbo,
26
ImmortalLimbo, LockError)
27
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
28
from bzrlib.merge import Merge3Merger
29
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
30
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
31
resolve_conflicts, cook_conflicts,
32
find_interesting, build_tree, get_backup_name)
33
import bzrlib.urlutils as urlutils
34
from bzrlib.workingtree import gen_root_id
37
class TestTreeTransform(TestCaseInTempDir):
40
super(TestTreeTransform, self).setUp()
41
self.wt = BzrDir.create_standalone_workingtree('.')
44
def get_transform(self):
45
transform = TreeTransform(self.wt)
46
#self.addCleanup(transform.finalize)
47
return transform, transform.root
49
def test_existing_limbo(self):
50
limbo_name = urlutils.local_path_from_url(
51
self.wt._control_files.controlfilename('limbo'))
52
transform, root = self.get_transform()
53
os.mkdir(pathjoin(limbo_name, 'hehe'))
54
self.assertRaises(ImmortalLimbo, transform.apply)
55
self.assertRaises(LockError, self.wt.unlock)
56
self.assertRaises(ExistingLimbo, self.get_transform)
57
self.assertRaises(LockError, self.wt.unlock)
58
os.rmdir(pathjoin(limbo_name, 'hehe'))
60
transform, root = self.get_transform()
64
transform, root = self.get_transform()
65
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
66
imaginary_id = transform.trans_id_tree_path('imaginary')
67
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
68
self.assertEqual(imaginary_id, imaginary_id2)
69
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
70
self.assertEqual(transform.final_kind(root), 'directory')
71
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
72
trans_id = transform.create_path('name', root)
73
self.assertIs(transform.final_file_id(trans_id), None)
74
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
75
transform.create_file('contents', trans_id)
76
transform.set_executability(True, trans_id)
77
transform.version_file('my_pretties', trans_id)
78
self.assertRaises(DuplicateKey, transform.version_file,
79
'my_pretties', trans_id)
80
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
81
self.assertEqual(transform.final_parent(trans_id), root)
82
self.assertIs(transform.final_parent(root), ROOT_PARENT)
83
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
84
oz_id = transform.create_path('oz', root)
85
transform.create_directory(oz_id)
86
transform.version_file('ozzie', oz_id)
87
trans_id2 = transform.create_path('name2', root)
88
transform.create_file('contents', trans_id2)
89
transform.set_executability(False, trans_id2)
90
transform.version_file('my_pretties2', trans_id2)
91
modified_paths = transform.apply().modified_paths
92
self.assertEqual('contents', self.wt.get_file_byname('name').read())
93
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
94
self.assertIs(self.wt.is_executable('my_pretties'), True)
95
self.assertIs(self.wt.is_executable('my_pretties2'), False)
96
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
97
self.assertEqual(len(modified_paths), 3)
98
tree_mod_paths = [self.wt.id2abspath(f) for f in
99
('ozzie', 'my_pretties', 'my_pretties2')]
100
self.assertSubset(tree_mod_paths, modified_paths)
101
# is it safe to finalize repeatedly?
105
def test_convenience(self):
106
transform, root = self.get_transform()
107
trans_id = transform.new_file('name', root, 'contents',
109
oz = transform.new_directory('oz', root, 'oz-id')
110
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
111
toto = transform.new_file('toto', dorothy, 'toto-contents',
114
self.assertEqual(len(transform.find_conflicts()), 0)
116
self.assertRaises(ReusingTransform, transform.find_conflicts)
117
self.assertEqual('contents', file(self.wt.abspath('name')).read())
118
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
119
self.assertIs(self.wt.is_executable('my_pretties'), True)
120
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
121
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
122
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
124
self.assertEqual('toto-contents',
125
self.wt.get_file_byname('oz/dorothy/toto').read())
126
self.assertIs(self.wt.is_executable('toto-id'), False)
128
def test_conflicts(self):
129
transform, root = self.get_transform()
130
trans_id = transform.new_file('name', root, 'contents',
132
self.assertEqual(len(transform.find_conflicts()), 0)
133
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
134
self.assertEqual(transform.find_conflicts(),
135
[('duplicate', trans_id, trans_id2, 'name')])
136
self.assertRaises(MalformedTransform, transform.apply)
137
transform.adjust_path('name', trans_id, trans_id2)
138
self.assertEqual(transform.find_conflicts(),
139
[('non-directory parent', trans_id)])
140
tinman_id = transform.trans_id_tree_path('tinman')
141
transform.adjust_path('name', tinman_id, trans_id2)
142
self.assertEqual(transform.find_conflicts(),
143
[('unversioned parent', tinman_id),
144
('missing parent', tinman_id)])
145
lion_id = transform.create_path('lion', root)
146
self.assertEqual(transform.find_conflicts(),
147
[('unversioned parent', tinman_id),
148
('missing parent', tinman_id)])
149
transform.adjust_path('name', lion_id, trans_id2)
150
self.assertEqual(transform.find_conflicts(),
151
[('unversioned parent', lion_id),
152
('missing parent', lion_id)])
153
transform.version_file("Courage", lion_id)
154
self.assertEqual(transform.find_conflicts(),
155
[('missing parent', lion_id),
156
('versioning no contents', lion_id)])
157
transform.adjust_path('name2', root, trans_id2)
158
self.assertEqual(transform.find_conflicts(),
159
[('versioning no contents', lion_id)])
160
transform.create_file('Contents, okay?', lion_id)
161
transform.adjust_path('name2', trans_id2, trans_id2)
162
self.assertEqual(transform.find_conflicts(),
163
[('parent loop', trans_id2),
164
('non-directory parent', trans_id2)])
165
transform.adjust_path('name2', root, trans_id2)
166
oz_id = transform.new_directory('oz', root)
167
transform.set_executability(True, oz_id)
168
self.assertEqual(transform.find_conflicts(),
169
[('unversioned executability', oz_id)])
170
transform.version_file('oz-id', oz_id)
171
self.assertEqual(transform.find_conflicts(),
172
[('non-file executability', oz_id)])
173
transform.set_executability(None, oz_id)
174
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
176
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
177
self.assertEqual('contents', file(self.wt.abspath('name')).read())
178
transform2, root = self.get_transform()
179
oz_id = transform2.trans_id_tree_file_id('oz-id')
180
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
181
result = transform2.find_conflicts()
182
fp = FinalPaths(transform2)
183
self.assert_('oz/tip' in transform2._tree_path_ids)
184
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
185
self.assertEqual(len(result), 2)
186
self.assertEqual((result[0][0], result[0][1]),
187
('duplicate', newtip))
188
self.assertEqual((result[1][0], result[1][2]),
189
('duplicate id', newtip))
190
transform2.finalize()
191
transform3 = TreeTransform(self.wt)
192
self.addCleanup(transform3.finalize)
193
oz_id = transform3.trans_id_tree_file_id('oz-id')
194
transform3.delete_contents(oz_id)
195
self.assertEqual(transform3.find_conflicts(),
196
[('missing parent', oz_id)])
197
root_id = transform3.root
198
tip_id = transform3.trans_id_tree_file_id('tip-id')
199
transform3.adjust_path('tip', root_id, tip_id)
202
def test_add_del(self):
203
start, root = self.get_transform()
204
start.new_directory('a', root, 'a')
206
transform, root = self.get_transform()
207
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
208
transform.new_directory('a', root, 'a')
211
def test_unversioning(self):
212
create_tree, root = self.get_transform()
213
parent_id = create_tree.new_directory('parent', root, 'parent-id')
214
create_tree.new_file('child', parent_id, 'child', 'child-id')
216
unversion = TreeTransform(self.wt)
217
self.addCleanup(unversion.finalize)
218
parent = unversion.trans_id_tree_path('parent')
219
unversion.unversion_file(parent)
220
self.assertEqual(unversion.find_conflicts(),
221
[('unversioned parent', parent_id)])
222
file_id = unversion.trans_id_tree_file_id('child-id')
223
unversion.unversion_file(file_id)
226
def test_name_invariants(self):
227
create_tree, root = self.get_transform()
229
root = create_tree.root
230
create_tree.new_file('name1', root, 'hello1', 'name1')
231
create_tree.new_file('name2', root, 'hello2', 'name2')
232
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
233
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
234
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
235
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
238
mangle_tree,root = self.get_transform()
239
root = mangle_tree.root
241
name1 = mangle_tree.trans_id_tree_file_id('name1')
242
name2 = mangle_tree.trans_id_tree_file_id('name2')
243
mangle_tree.adjust_path('name2', root, name1)
244
mangle_tree.adjust_path('name1', root, name2)
246
#tests for deleting parent directories
247
ddir = mangle_tree.trans_id_tree_file_id('ddir')
248
mangle_tree.delete_contents(ddir)
249
dfile = mangle_tree.trans_id_tree_file_id('dfile')
250
mangle_tree.delete_versioned(dfile)
251
mangle_tree.unversion_file(dfile)
252
mfile = mangle_tree.trans_id_tree_file_id('mfile')
253
mangle_tree.adjust_path('mfile', root, mfile)
255
#tests for adding parent directories
256
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
257
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
258
mangle_tree.adjust_path('mfile2', newdir, mfile2)
259
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
260
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
261
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
262
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
264
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
265
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
266
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
267
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
268
self.assertEqual(file(mfile2_path).read(), 'later2')
269
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
270
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
271
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
272
self.assertEqual(file(newfile_path).read(), 'hello3')
273
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
274
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
275
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
277
def test_both_rename(self):
278
create_tree,root = self.get_transform()
279
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
280
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
282
mangle_tree,root = self.get_transform()
283
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
284
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
285
mangle_tree.adjust_path('test', root, selftest)
286
mangle_tree.adjust_path('test_too_much', root, selftest)
287
mangle_tree.set_executability(True, blackbox)
290
def test_both_rename2(self):
291
create_tree,root = self.get_transform()
292
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
293
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
294
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
295
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
298
mangle_tree,root = self.get_transform()
299
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
300
tests = mangle_tree.trans_id_tree_file_id('tests-id')
301
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
302
mangle_tree.adjust_path('selftest', bzrlib, tests)
303
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
304
mangle_tree.set_executability(True, test_too_much)
307
def test_both_rename3(self):
308
create_tree,root = self.get_transform()
309
tests = create_tree.new_directory('tests', root, 'tests-id')
310
create_tree.new_file('test_too_much.py', tests, 'hello1',
313
mangle_tree,root = self.get_transform()
314
tests = mangle_tree.trans_id_tree_file_id('tests-id')
315
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
316
mangle_tree.adjust_path('selftest', root, tests)
317
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
318
mangle_tree.set_executability(True, test_too_much)
321
def test_move_dangling_ie(self):
322
create_tree, root = self.get_transform()
324
root = create_tree.root
325
create_tree.new_file('name1', root, 'hello1', 'name1')
327
delete_contents, root = self.get_transform()
328
file = delete_contents.trans_id_tree_file_id('name1')
329
delete_contents.delete_contents(file)
330
delete_contents.apply()
331
move_id, root = self.get_transform()
332
name1 = move_id.trans_id_tree_file_id('name1')
333
newdir = move_id.new_directory('dir', root, 'newdir')
334
move_id.adjust_path('name2', newdir, name1)
337
def test_replace_dangling_ie(self):
338
create_tree, root = self.get_transform()
340
root = create_tree.root
341
create_tree.new_file('name1', root, 'hello1', 'name1')
343
delete_contents = TreeTransform(self.wt)
344
self.addCleanup(delete_contents.finalize)
345
file = delete_contents.trans_id_tree_file_id('name1')
346
delete_contents.delete_contents(file)
347
delete_contents.apply()
348
delete_contents.finalize()
349
replace = TreeTransform(self.wt)
350
self.addCleanup(replace.finalize)
351
name2 = replace.new_file('name2', root, 'hello2', 'name1')
352
conflicts = replace.find_conflicts()
353
name1 = replace.trans_id_tree_file_id('name1')
354
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
355
resolve_conflicts(replace)
358
def test_symlinks(self):
359
if not has_symlinks():
360
raise TestSkipped('Symlinks are not supported on this platform')
361
transform,root = self.get_transform()
362
oz_id = transform.new_directory('oz', root, 'oz-id')
363
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
365
wiz_id = transform.create_path('wizard2', oz_id)
366
transform.create_symlink('behind_curtain', wiz_id)
367
transform.version_file('wiz-id2', wiz_id)
368
transform.set_executability(True, wiz_id)
369
self.assertEqual(transform.find_conflicts(),
370
[('non-file executability', wiz_id)])
371
transform.set_executability(None, wiz_id)
373
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
374
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
375
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
377
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
381
def get_conflicted(self):
382
create,root = self.get_transform()
383
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
384
oz = create.new_directory('oz', root, 'oz-id')
385
create.new_directory('emeraldcity', oz, 'emerald-id')
387
conflicts,root = self.get_transform()
388
# set up duplicate entry, duplicate id
389
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
391
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
392
oz = conflicts.trans_id_tree_file_id('oz-id')
393
# set up DeletedParent parent conflict
394
conflicts.delete_versioned(oz)
395
emerald = conflicts.trans_id_tree_file_id('emerald-id')
396
# set up MissingParent conflict
397
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
398
conflicts.adjust_path('munchkincity', root, munchkincity)
399
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
401
conflicts.adjust_path('emeraldcity', emerald, emerald)
402
return conflicts, emerald, oz, old_dorothy, new_dorothy
404
def test_conflict_resolution(self):
405
conflicts, emerald, oz, old_dorothy, new_dorothy =\
406
self.get_conflicted()
407
resolve_conflicts(conflicts)
408
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
409
self.assertIs(conflicts.final_file_id(old_dorothy), None)
410
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
411
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
412
self.assertEqual(conflicts.final_parent(emerald), oz)
415
def test_cook_conflicts(self):
416
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
417
raw_conflicts = resolve_conflicts(tt)
418
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
419
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
420
'dorothy', None, 'dorothy-id')
421
self.assertEqual(cooked_conflicts[0], duplicate)
422
duplicate_id = DuplicateID('Unversioned existing file',
423
'dorothy.moved', 'dorothy', None,
425
self.assertEqual(cooked_conflicts[1], duplicate_id)
426
missing_parent = MissingParent('Created directory', 'munchkincity',
428
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
429
self.assertEqual(cooked_conflicts[2], missing_parent)
430
unversioned_parent = UnversionedParent('Versioned directory',
433
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
435
self.assertEqual(cooked_conflicts[3], unversioned_parent)
436
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
437
'oz/emeraldcity', 'emerald-id', 'emerald-id')
438
self.assertEqual(cooked_conflicts[4], deleted_parent)
439
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
440
self.assertEqual(cooked_conflicts[6], parent_loop)
441
self.assertEqual(len(cooked_conflicts), 7)
444
def test_string_conflicts(self):
445
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
446
raw_conflicts = resolve_conflicts(tt)
447
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
449
conflicts_s = [str(c) for c in cooked_conflicts]
450
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
451
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
452
'Moved existing file to '
454
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
455
'Unversioned existing file '
457
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
458
' munchkincity. Created directory.')
459
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
460
' versioned, but has versioned'
461
' children. Versioned directory.')
462
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
463
" is not empty. Not deleting.")
464
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
465
' versioned, but has versioned'
466
' children. Versioned directory.')
467
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
468
' oz/emeraldcity. Cancelled move.')
470
def test_moving_versioned_directories(self):
471
create, root = self.get_transform()
472
kansas = create.new_directory('kansas', root, 'kansas-id')
473
create.new_directory('house', kansas, 'house-id')
474
create.new_directory('oz', root, 'oz-id')
476
cyclone, root = self.get_transform()
477
oz = cyclone.trans_id_tree_file_id('oz-id')
478
house = cyclone.trans_id_tree_file_id('house-id')
479
cyclone.adjust_path('house', oz, house)
482
def test_moving_root(self):
483
create, root = self.get_transform()
484
fun = create.new_directory('fun', root, 'fun-id')
485
create.new_directory('sun', root, 'sun-id')
486
create.new_directory('moon', root, 'moon')
488
transform, root = self.get_transform()
489
transform.adjust_root_path('oldroot', fun)
490
new_root=transform.trans_id_tree_path('')
491
transform.version_file('new-root', new_root)
494
def test_renames(self):
495
create, root = self.get_transform()
496
old = create.new_directory('old-parent', root, 'old-id')
497
intermediate = create.new_directory('intermediate', old, 'im-id')
498
myfile = create.new_file('myfile', intermediate, 'myfile-text',
501
rename, root = self.get_transform()
502
old = rename.trans_id_file_id('old-id')
503
rename.adjust_path('new', root, old)
504
myfile = rename.trans_id_file_id('myfile-id')
505
rename.set_executability(True, myfile)
508
def test_find_interesting(self):
509
create, root = self.get_transform()
511
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
512
create.new_file('uvfile', root, 'othertext')
514
self.assertEqual(find_interesting(wt, wt, ['vfile']),
516
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
519
def test_set_executability_order(self):
520
"""Ensure that executability behaves the same, no matter what order.
522
- create file and set executability simultaneously
523
- create file and set executability afterward
524
- unsetting the executability of a file whose executability has not been
525
declared should throw an exception (this may happen when a
526
merge attempts to create a file with a duplicate ID)
528
transform, root = self.get_transform()
530
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
532
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
533
transform.set_executability(True, sac)
534
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
535
self.assertRaises(KeyError, transform.set_executability, None, uws)
537
self.assertTrue(wt.is_executable('soc'))
538
self.assertTrue(wt.is_executable('sac'))
541
class TransformGroup(object):
542
def __init__(self, dirname, root_id):
545
self.wt = BzrDir.create_standalone_workingtree(dirname)
546
self.wt.set_root_id(root_id)
547
self.b = self.wt.branch
548
self.tt = TreeTransform(self.wt)
549
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
551
def conflict_text(tree, merge):
552
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
553
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
556
class TestTransformMerge(TestCaseInTempDir):
557
def test_text_merge(self):
558
root_id = gen_root_id()
559
base = TransformGroup("base", root_id)
560
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
561
base.tt.new_file('b', base.root, 'b1', 'b')
562
base.tt.new_file('c', base.root, 'c', 'c')
563
base.tt.new_file('d', base.root, 'd', 'd')
564
base.tt.new_file('e', base.root, 'e', 'e')
565
base.tt.new_file('f', base.root, 'f', 'f')
566
base.tt.new_directory('g', base.root, 'g')
567
base.tt.new_directory('h', base.root, 'h')
569
other = TransformGroup("other", root_id)
570
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
571
other.tt.new_file('b', other.root, 'b2', 'b')
572
other.tt.new_file('c', other.root, 'c2', 'c')
573
other.tt.new_file('d', other.root, 'd', 'd')
574
other.tt.new_file('e', other.root, 'e2', 'e')
575
other.tt.new_file('f', other.root, 'f', 'f')
576
other.tt.new_file('g', other.root, 'g', 'g')
577
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
578
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
580
this = TransformGroup("this", root_id)
581
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
582
this.tt.new_file('b', this.root, 'b', 'b')
583
this.tt.new_file('c', this.root, 'c', 'c')
584
this.tt.new_file('d', this.root, 'd2', 'd')
585
this.tt.new_file('e', this.root, 'e2', 'e')
586
this.tt.new_file('f', this.root, 'f', 'f')
587
this.tt.new_file('g', this.root, 'g', 'g')
588
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
589
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
591
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
593
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
594
# three-way text conflict
595
self.assertEqual(this.wt.get_file('b').read(),
596
conflict_text('b', 'b2'))
598
self.assertEqual(this.wt.get_file('c').read(), 'c2')
600
self.assertEqual(this.wt.get_file('d').read(), 'd2')
601
# Ambigious clean merge
602
self.assertEqual(this.wt.get_file('e').read(), 'e2')
604
self.assertEqual(this.wt.get_file('f').read(), 'f')
605
# Correct correct results when THIS == OTHER
606
self.assertEqual(this.wt.get_file('g').read(), 'g')
607
# Text conflict when THIS & OTHER are text and BASE is dir
608
self.assertEqual(this.wt.get_file('h').read(),
609
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
610
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
612
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
614
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
615
self.assertEqual(this.wt.get_file('i').read(),
616
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
617
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
619
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
621
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
622
modified = ['a', 'b', 'c', 'h', 'i']
623
merge_modified = this.wt.merge_modified()
624
self.assertSubset(merge_modified, modified)
625
self.assertEqual(len(merge_modified), len(modified))
626
file(this.wt.id2abspath('a'), 'wb').write('booga')
628
merge_modified = this.wt.merge_modified()
629
self.assertSubset(merge_modified, modified)
630
self.assertEqual(len(merge_modified), len(modified))
634
def test_file_merge(self):
635
if not has_symlinks():
636
raise TestSkipped('Symlinks are not supported on this platform')
637
root_id = gen_root_id()
638
base = TransformGroup("BASE", root_id)
639
this = TransformGroup("THIS", root_id)
640
other = TransformGroup("OTHER", root_id)
641
for tg in this, base, other:
642
tg.tt.new_directory('a', tg.root, 'a')
643
tg.tt.new_symlink('b', tg.root, 'b', 'b')
644
tg.tt.new_file('c', tg.root, 'c', 'c')
645
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
646
targets = ((base, 'base-e', 'base-f', None, None),
647
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
648
(other, 'other-e', None, 'other-g', 'other-h'))
649
for tg, e_target, f_target, g_target, h_target in targets:
650
for link, target in (('e', e_target), ('f', f_target),
651
('g', g_target), ('h', h_target)):
652
if target is not None:
653
tg.tt.new_symlink(link, tg.root, target, link)
655
for tg in this, base, other:
657
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
658
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
659
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
660
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
661
for suffix in ('THIS', 'BASE', 'OTHER'):
662
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
663
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
664
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
665
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
666
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
667
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
668
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
669
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
670
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
671
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
672
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
673
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
674
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
675
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
677
def test_filename_merge(self):
678
root_id = gen_root_id()
679
base = TransformGroup("BASE", root_id)
680
this = TransformGroup("THIS", root_id)
681
other = TransformGroup("OTHER", root_id)
682
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
683
for t in [base, this, other]]
684
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
685
for t in [base, this, other]]
686
base.tt.new_directory('c', base_a, 'c')
687
this.tt.new_directory('c1', this_a, 'c')
688
other.tt.new_directory('c', other_b, 'c')
690
base.tt.new_directory('d', base_a, 'd')
691
this.tt.new_directory('d1', this_b, 'd')
692
other.tt.new_directory('d', other_a, 'd')
694
base.tt.new_directory('e', base_a, 'e')
695
this.tt.new_directory('e', this_a, 'e')
696
other.tt.new_directory('e1', other_b, 'e')
698
base.tt.new_directory('f', base_a, 'f')
699
this.tt.new_directory('f1', this_b, 'f')
700
other.tt.new_directory('f1', other_b, 'f')
702
for tg in [this, base, other]:
704
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
705
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
706
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
707
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
708
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
710
def test_filename_merge_conflicts(self):
711
root_id = gen_root_id()
712
base = TransformGroup("BASE", root_id)
713
this = TransformGroup("THIS", root_id)
714
other = TransformGroup("OTHER", root_id)
715
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
716
for t in [base, this, other]]
717
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
718
for t in [base, this, other]]
720
base.tt.new_file('g', base_a, 'g', 'g')
721
other.tt.new_file('g1', other_b, 'g1', 'g')
723
base.tt.new_file('h', base_a, 'h', 'h')
724
this.tt.new_file('h1', this_b, 'h1', 'h')
726
base.tt.new_file('i', base.root, 'i', 'i')
727
other.tt.new_directory('i1', this_b, 'i')
729
for tg in [this, base, other]:
731
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
733
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
734
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
735
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
736
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
737
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
738
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
739
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
741
class TestBuildTree(tests.TestCaseWithTransport):
743
def test_build_tree(self):
744
if not has_symlinks():
745
raise TestSkipped('Test requires symlink support')
747
a = BzrDir.create_standalone_workingtree('a')
749
file('a/foo/bar', 'wb').write('contents')
750
os.symlink('a/foo/bar', 'a/foo/baz')
751
a.add(['foo', 'foo/bar', 'foo/baz'])
752
a.commit('initial commit')
753
b = BzrDir.create_standalone_workingtree('b')
754
build_tree(a.basis_tree(), b)
755
self.assertIs(os.path.isdir('b/foo'), True)
756
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
757
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
759
def test_file_conflict_handling(self):
760
"""Ensure that when building trees, conflict handling is done"""
761
source = self.make_branch_and_tree('source')
762
target = self.make_branch_and_tree('target')
763
self.build_tree(['source/file', 'target/file'])
764
source.add('file', 'new-file')
765
source.commit('added file')
766
build_tree(source.basis_tree(), target)
767
self.assertEqual([DuplicateEntry('Moved existing file to',
768
'file.moved', 'file', None, 'new-file')],
770
target2 = self.make_branch_and_tree('target2')
771
target_file = file('target2/file', 'wb')
773
source_file = file('source/file', 'rb')
775
target_file.write(source_file.read())
780
build_tree(source.basis_tree(), target2)
781
self.assertEqual([], target2.conflicts())
783
def test_symlink_conflict_handling(self):
784
"""Ensure that when building trees, conflict handling is done"""
785
if not has_symlinks():
786
raise TestSkipped('Test requires symlink support')
787
source = self.make_branch_and_tree('source')
788
os.symlink('foo', 'source/symlink')
789
source.add('symlink', 'new-symlink')
790
source.commit('added file')
791
target = self.make_branch_and_tree('target')
792
os.symlink('bar', 'target/symlink')
793
build_tree(source.basis_tree(), target)
794
self.assertEqual([DuplicateEntry('Moved existing file to',
795
'symlink.moved', 'symlink', None, 'new-symlink')],
797
target = self.make_branch_and_tree('target2')
798
os.symlink('foo', 'target2/symlink')
799
build_tree(source.basis_tree(), target)
800
self.assertEqual([], target.conflicts())
802
def test_directory_conflict_handling(self):
803
"""Ensure that when building trees, conflict handling is done"""
804
source = self.make_branch_and_tree('source')
805
target = self.make_branch_and_tree('target')
806
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
807
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
808
source.commit('added file')
809
build_tree(source.basis_tree(), target)
810
self.assertEqual([], target.conflicts())
811
self.failUnlessExists('target/dir1/file')
813
# Ensure contents are merged
814
target = self.make_branch_and_tree('target2')
815
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
816
build_tree(source.basis_tree(), target)
817
self.assertEqual([], target.conflicts())
818
self.failUnlessExists('target2/dir1/file2')
819
self.failUnlessExists('target2/dir1/file')
821
# Ensure new contents are suppressed for existing branches
822
target = self.make_branch_and_tree('target3')
823
self.make_branch('target3/dir1')
824
self.build_tree(['target3/dir1/file2'])
825
build_tree(source.basis_tree(), target)
826
self.failIfExists('target3/dir1/file')
827
self.failUnlessExists('target3/dir1/file2')
828
self.failUnlessExists('target3/dir1.diverted/file')
829
self.assertEqual([DuplicateEntry('Diverted to',
830
'dir1.diverted', 'dir1', 'new-dir1', None)],
833
target = self.make_branch_and_tree('target4')
834
self.build_tree(['target4/dir1/'])
835
self.make_branch('target4/dir1/file')
836
build_tree(source.basis_tree(), target)
837
self.failUnlessExists('target4/dir1/file')
838
self.assertEqual('directory', file_kind('target4/dir1/file'))
839
self.failUnlessExists('target4/dir1/file.diverted')
840
self.assertEqual([DuplicateEntry('Diverted to',
841
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
844
def test_mixed_conflict_handling(self):
845
"""Ensure that when building trees, conflict handling is done"""
846
source = self.make_branch_and_tree('source')
847
target = self.make_branch_and_tree('target')
848
self.build_tree(['source/name', 'target/name/'])
849
source.add('name', 'new-name')
850
source.commit('added file')
851
build_tree(source.basis_tree(), target)
852
self.assertEqual([DuplicateEntry('Moved existing file to',
853
'name.moved', 'name', None, 'new-name')], target.conflicts())
855
def test_raises_in_populated(self):
856
source = self.make_branch_and_tree('source')
857
self.build_tree(['source/name'])
859
source.commit('added name')
860
target = self.make_branch_and_tree('target')
861
self.build_tree(['target/name'])
863
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
867
class MockTransform(object):
869
def has_named_child(self, by_parent, parent_id, name):
870
for child_id in by_parent[parent_id]:
874
elif name == "name.~%s~" % child_id:
878
class MockEntry(object):
880
object.__init__(self)
883
class TestGetBackupName(TestCase):
884
def test_get_backup_name(self):
886
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
887
self.assertEqual(name, 'name.~1~')
888
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
889
self.assertEqual(name, 'name.~2~')
890
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
891
self.assertEqual(name, 'name.~1~')
892
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
893
self.assertEqual(name, 'name.~1~')
894
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
895
self.assertEqual(name, 'name.~4~')