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
27
from bzrlib.bzrdir import BzrDir
28
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
29
UnversionedParent, ParentLoop, DeletingParent,)
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
31
ReusingTransform, CantMoveRoot,
32
PathsNotVersionedError, ExistingLimbo,
33
ImmortalLimbo, LockError)
34
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
35
from bzrlib.merge import Merge3Merger
36
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
37
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
38
resolve_conflicts, cook_conflicts,
39
find_interesting, build_tree, get_backup_name)
42
class TestTreeTransform(TestCaseInTempDir):
45
super(TestTreeTransform, self).setUp()
46
self.wt = BzrDir.create_standalone_workingtree('.')
49
def get_transform(self):
50
transform = TreeTransform(self.wt)
51
#self.addCleanup(transform.finalize)
52
return transform, transform.root
54
def test_existing_limbo(self):
55
limbo_name = urlutils.local_path_from_url(
56
self.wt._control_files.controlfilename('limbo'))
57
transform, root = self.get_transform()
58
os.mkdir(pathjoin(limbo_name, 'hehe'))
59
self.assertRaises(ImmortalLimbo, transform.apply)
60
self.assertRaises(LockError, self.wt.unlock)
61
self.assertRaises(ExistingLimbo, self.get_transform)
62
self.assertRaises(LockError, self.wt.unlock)
63
os.rmdir(pathjoin(limbo_name, 'hehe'))
65
transform, root = self.get_transform()
69
transform, root = self.get_transform()
70
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
71
imaginary_id = transform.trans_id_tree_path('imaginary')
72
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
73
self.assertEqual(imaginary_id, imaginary_id2)
74
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
75
self.assertEqual(transform.final_kind(root), 'directory')
76
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
77
trans_id = transform.create_path('name', root)
78
self.assertIs(transform.final_file_id(trans_id), None)
79
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
80
transform.create_file('contents', trans_id)
81
transform.set_executability(True, trans_id)
82
transform.version_file('my_pretties', trans_id)
83
self.assertRaises(DuplicateKey, transform.version_file,
84
'my_pretties', trans_id)
85
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
86
self.assertEqual(transform.final_parent(trans_id), root)
87
self.assertIs(transform.final_parent(root), ROOT_PARENT)
88
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
89
oz_id = transform.create_path('oz', root)
90
transform.create_directory(oz_id)
91
transform.version_file('ozzie', oz_id)
92
trans_id2 = transform.create_path('name2', root)
93
transform.create_file('contents', trans_id2)
94
transform.set_executability(False, trans_id2)
95
transform.version_file('my_pretties2', trans_id2)
96
modified_paths = transform.apply().modified_paths
97
self.assertEqual('contents', self.wt.get_file_byname('name').read())
98
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
99
self.assertIs(self.wt.is_executable('my_pretties'), True)
100
self.assertIs(self.wt.is_executable('my_pretties2'), False)
101
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
102
self.assertEqual(len(modified_paths), 3)
103
tree_mod_paths = [self.wt.id2abspath(f) for f in
104
('ozzie', 'my_pretties', 'my_pretties2')]
105
self.assertSubset(tree_mod_paths, modified_paths)
106
# is it safe to finalize repeatedly?
110
def test_convenience(self):
111
transform, root = self.get_transform()
112
trans_id = transform.new_file('name', root, 'contents',
114
oz = transform.new_directory('oz', root, 'oz-id')
115
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
116
toto = transform.new_file('toto', dorothy, 'toto-contents',
119
self.assertEqual(len(transform.find_conflicts()), 0)
121
self.assertRaises(ReusingTransform, transform.find_conflicts)
122
self.assertEqual('contents', file(self.wt.abspath('name')).read())
123
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
124
self.assertIs(self.wt.is_executable('my_pretties'), True)
125
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
126
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
127
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
129
self.assertEqual('toto-contents',
130
self.wt.get_file_byname('oz/dorothy/toto').read())
131
self.assertIs(self.wt.is_executable('toto-id'), False)
133
def test_conflicts(self):
134
transform, root = self.get_transform()
135
trans_id = transform.new_file('name', root, 'contents',
137
self.assertEqual(len(transform.find_conflicts()), 0)
138
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
139
self.assertEqual(transform.find_conflicts(),
140
[('duplicate', trans_id, trans_id2, 'name')])
141
self.assertRaises(MalformedTransform, transform.apply)
142
transform.adjust_path('name', trans_id, trans_id2)
143
self.assertEqual(transform.find_conflicts(),
144
[('non-directory parent', trans_id)])
145
tinman_id = transform.trans_id_tree_path('tinman')
146
transform.adjust_path('name', tinman_id, trans_id2)
147
self.assertEqual(transform.find_conflicts(),
148
[('unversioned parent', tinman_id),
149
('missing parent', tinman_id)])
150
lion_id = transform.create_path('lion', root)
151
self.assertEqual(transform.find_conflicts(),
152
[('unversioned parent', tinman_id),
153
('missing parent', tinman_id)])
154
transform.adjust_path('name', lion_id, trans_id2)
155
self.assertEqual(transform.find_conflicts(),
156
[('unversioned parent', lion_id),
157
('missing parent', lion_id)])
158
transform.version_file("Courage", lion_id)
159
self.assertEqual(transform.find_conflicts(),
160
[('missing parent', lion_id),
161
('versioning no contents', lion_id)])
162
transform.adjust_path('name2', root, trans_id2)
163
self.assertEqual(transform.find_conflicts(),
164
[('versioning no contents', lion_id)])
165
transform.create_file('Contents, okay?', lion_id)
166
transform.adjust_path('name2', trans_id2, trans_id2)
167
self.assertEqual(transform.find_conflicts(),
168
[('parent loop', trans_id2),
169
('non-directory parent', trans_id2)])
170
transform.adjust_path('name2', root, trans_id2)
171
oz_id = transform.new_directory('oz', root)
172
transform.set_executability(True, oz_id)
173
self.assertEqual(transform.find_conflicts(),
174
[('unversioned executability', oz_id)])
175
transform.version_file('oz-id', oz_id)
176
self.assertEqual(transform.find_conflicts(),
177
[('non-file executability', oz_id)])
178
transform.set_executability(None, oz_id)
179
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
181
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
182
self.assertEqual('contents', file(self.wt.abspath('name')).read())
183
transform2, root = self.get_transform()
184
oz_id = transform2.trans_id_tree_file_id('oz-id')
185
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
186
result = transform2.find_conflicts()
187
fp = FinalPaths(transform2)
188
self.assert_('oz/tip' in transform2._tree_path_ids)
189
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
190
self.assertEqual(len(result), 2)
191
self.assertEqual((result[0][0], result[0][1]),
192
('duplicate', newtip))
193
self.assertEqual((result[1][0], result[1][2]),
194
('duplicate id', newtip))
195
transform2.finalize()
196
transform3 = TreeTransform(self.wt)
197
self.addCleanup(transform3.finalize)
198
oz_id = transform3.trans_id_tree_file_id('oz-id')
199
transform3.delete_contents(oz_id)
200
self.assertEqual(transform3.find_conflicts(),
201
[('missing parent', oz_id)])
202
root_id = transform3.root
203
tip_id = transform3.trans_id_tree_file_id('tip-id')
204
transform3.adjust_path('tip', root_id, tip_id)
207
def test_add_del(self):
208
start, root = self.get_transform()
209
start.new_directory('a', root, 'a')
211
transform, root = self.get_transform()
212
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
213
transform.new_directory('a', root, 'a')
216
def test_unversioning(self):
217
create_tree, root = self.get_transform()
218
parent_id = create_tree.new_directory('parent', root, 'parent-id')
219
create_tree.new_file('child', parent_id, 'child', 'child-id')
221
unversion = TreeTransform(self.wt)
222
self.addCleanup(unversion.finalize)
223
parent = unversion.trans_id_tree_path('parent')
224
unversion.unversion_file(parent)
225
self.assertEqual(unversion.find_conflicts(),
226
[('unversioned parent', parent_id)])
227
file_id = unversion.trans_id_tree_file_id('child-id')
228
unversion.unversion_file(file_id)
231
def test_name_invariants(self):
232
create_tree, root = self.get_transform()
234
root = create_tree.root
235
create_tree.new_file('name1', root, 'hello1', 'name1')
236
create_tree.new_file('name2', root, 'hello2', 'name2')
237
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
238
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
239
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
240
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
243
mangle_tree,root = self.get_transform()
244
root = mangle_tree.root
246
name1 = mangle_tree.trans_id_tree_file_id('name1')
247
name2 = mangle_tree.trans_id_tree_file_id('name2')
248
mangle_tree.adjust_path('name2', root, name1)
249
mangle_tree.adjust_path('name1', root, name2)
251
#tests for deleting parent directories
252
ddir = mangle_tree.trans_id_tree_file_id('ddir')
253
mangle_tree.delete_contents(ddir)
254
dfile = mangle_tree.trans_id_tree_file_id('dfile')
255
mangle_tree.delete_versioned(dfile)
256
mangle_tree.unversion_file(dfile)
257
mfile = mangle_tree.trans_id_tree_file_id('mfile')
258
mangle_tree.adjust_path('mfile', root, mfile)
260
#tests for adding parent directories
261
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
262
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
263
mangle_tree.adjust_path('mfile2', newdir, mfile2)
264
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
265
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
266
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
267
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
269
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
270
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
271
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
272
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
273
self.assertEqual(file(mfile2_path).read(), 'later2')
274
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
275
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
276
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
277
self.assertEqual(file(newfile_path).read(), 'hello3')
278
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
279
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
280
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
282
def test_both_rename(self):
283
create_tree,root = self.get_transform()
284
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
285
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
287
mangle_tree,root = self.get_transform()
288
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
289
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
290
mangle_tree.adjust_path('test', root, selftest)
291
mangle_tree.adjust_path('test_too_much', root, selftest)
292
mangle_tree.set_executability(True, blackbox)
295
def test_both_rename2(self):
296
create_tree,root = self.get_transform()
297
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
298
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
299
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
300
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
303
mangle_tree,root = self.get_transform()
304
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
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', bzrlib, tests)
308
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
309
mangle_tree.set_executability(True, test_too_much)
312
def test_both_rename3(self):
313
create_tree,root = self.get_transform()
314
tests = create_tree.new_directory('tests', root, 'tests-id')
315
create_tree.new_file('test_too_much.py', tests, 'hello1',
318
mangle_tree,root = self.get_transform()
319
tests = mangle_tree.trans_id_tree_file_id('tests-id')
320
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
321
mangle_tree.adjust_path('selftest', root, tests)
322
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
323
mangle_tree.set_executability(True, test_too_much)
326
def test_move_dangling_ie(self):
327
create_tree, root = self.get_transform()
329
root = create_tree.root
330
create_tree.new_file('name1', root, 'hello1', 'name1')
332
delete_contents, root = self.get_transform()
333
file = delete_contents.trans_id_tree_file_id('name1')
334
delete_contents.delete_contents(file)
335
delete_contents.apply()
336
move_id, root = self.get_transform()
337
name1 = move_id.trans_id_tree_file_id('name1')
338
newdir = move_id.new_directory('dir', root, 'newdir')
339
move_id.adjust_path('name2', newdir, name1)
342
def test_replace_dangling_ie(self):
343
create_tree, root = self.get_transform()
345
root = create_tree.root
346
create_tree.new_file('name1', root, 'hello1', 'name1')
348
delete_contents = TreeTransform(self.wt)
349
self.addCleanup(delete_contents.finalize)
350
file = delete_contents.trans_id_tree_file_id('name1')
351
delete_contents.delete_contents(file)
352
delete_contents.apply()
353
delete_contents.finalize()
354
replace = TreeTransform(self.wt)
355
self.addCleanup(replace.finalize)
356
name2 = replace.new_file('name2', root, 'hello2', 'name1')
357
conflicts = replace.find_conflicts()
358
name1 = replace.trans_id_tree_file_id('name1')
359
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
360
resolve_conflicts(replace)
363
def test_symlinks(self):
364
if not has_symlinks():
365
raise TestSkipped('Symlinks are not supported on this platform')
366
transform,root = self.get_transform()
367
oz_id = transform.new_directory('oz', root, 'oz-id')
368
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
370
wiz_id = transform.create_path('wizard2', oz_id)
371
transform.create_symlink('behind_curtain', wiz_id)
372
transform.version_file('wiz-id2', wiz_id)
373
transform.set_executability(True, wiz_id)
374
self.assertEqual(transform.find_conflicts(),
375
[('non-file executability', wiz_id)])
376
transform.set_executability(None, wiz_id)
378
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
379
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
380
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
382
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
386
def get_conflicted(self):
387
create,root = self.get_transform()
388
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
389
oz = create.new_directory('oz', root, 'oz-id')
390
create.new_directory('emeraldcity', oz, 'emerald-id')
392
conflicts,root = self.get_transform()
393
# set up duplicate entry, duplicate id
394
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
396
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
397
oz = conflicts.trans_id_tree_file_id('oz-id')
398
# set up DeletedParent parent conflict
399
conflicts.delete_versioned(oz)
400
emerald = conflicts.trans_id_tree_file_id('emerald-id')
401
# set up MissingParent conflict
402
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
403
conflicts.adjust_path('munchkincity', root, munchkincity)
404
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
406
conflicts.adjust_path('emeraldcity', emerald, emerald)
407
return conflicts, emerald, oz, old_dorothy, new_dorothy
409
def test_conflict_resolution(self):
410
conflicts, emerald, oz, old_dorothy, new_dorothy =\
411
self.get_conflicted()
412
resolve_conflicts(conflicts)
413
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
414
self.assertIs(conflicts.final_file_id(old_dorothy), None)
415
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
416
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
417
self.assertEqual(conflicts.final_parent(emerald), oz)
420
def test_cook_conflicts(self):
421
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
422
raw_conflicts = resolve_conflicts(tt)
423
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
424
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
425
'dorothy', None, 'dorothy-id')
426
self.assertEqual(cooked_conflicts[0], duplicate)
427
duplicate_id = DuplicateID('Unversioned existing file',
428
'dorothy.moved', 'dorothy', None,
430
self.assertEqual(cooked_conflicts[1], duplicate_id)
431
missing_parent = MissingParent('Created directory', 'munchkincity',
433
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
434
self.assertEqual(cooked_conflicts[2], missing_parent)
435
unversioned_parent = UnversionedParent('Versioned directory',
438
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
440
self.assertEqual(cooked_conflicts[3], unversioned_parent)
441
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
442
'oz/emeraldcity', 'emerald-id', 'emerald-id')
443
self.assertEqual(cooked_conflicts[4], deleted_parent)
444
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
445
self.assertEqual(cooked_conflicts[6], parent_loop)
446
self.assertEqual(len(cooked_conflicts), 7)
449
def test_string_conflicts(self):
450
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
451
raw_conflicts = resolve_conflicts(tt)
452
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
454
conflicts_s = [str(c) for c in cooked_conflicts]
455
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
456
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
457
'Moved existing file to '
459
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
460
'Unversioned existing file '
462
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
463
' munchkincity. Created directory.')
464
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
465
' versioned, but has versioned'
466
' children. Versioned directory.')
467
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
468
" is not empty. Not deleting.")
469
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
470
' versioned, but has versioned'
471
' children. Versioned directory.')
472
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
473
' oz/emeraldcity. Cancelled move.')
475
def test_moving_versioned_directories(self):
476
create, root = self.get_transform()
477
kansas = create.new_directory('kansas', root, 'kansas-id')
478
create.new_directory('house', kansas, 'house-id')
479
create.new_directory('oz', root, 'oz-id')
481
cyclone, root = self.get_transform()
482
oz = cyclone.trans_id_tree_file_id('oz-id')
483
house = cyclone.trans_id_tree_file_id('house-id')
484
cyclone.adjust_path('house', oz, house)
487
def test_moving_root(self):
488
create, root = self.get_transform()
489
fun = create.new_directory('fun', root, 'fun-id')
490
create.new_directory('sun', root, 'sun-id')
491
create.new_directory('moon', root, 'moon')
493
transform, root = self.get_transform()
494
transform.adjust_root_path('oldroot', fun)
495
new_root=transform.trans_id_tree_path('')
496
transform.version_file('new-root', new_root)
499
def test_renames(self):
500
create, root = self.get_transform()
501
old = create.new_directory('old-parent', root, 'old-id')
502
intermediate = create.new_directory('intermediate', old, 'im-id')
503
myfile = create.new_file('myfile', intermediate, 'myfile-text',
506
rename, root = self.get_transform()
507
old = rename.trans_id_file_id('old-id')
508
rename.adjust_path('new', root, old)
509
myfile = rename.trans_id_file_id('myfile-id')
510
rename.set_executability(True, myfile)
513
def test_find_interesting(self):
514
create, root = self.get_transform()
516
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
517
create.new_file('uvfile', root, 'othertext')
519
self.assertEqual(find_interesting(wt, wt, ['vfile']),
521
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
524
def test_set_executability_order(self):
525
"""Ensure that executability behaves the same, no matter what order.
527
- create file and set executability simultaneously
528
- create file and set executability afterward
529
- unsetting the executability of a file whose executability has not been
530
declared should throw an exception (this may happen when a
531
merge attempts to create a file with a duplicate ID)
533
transform, root = self.get_transform()
535
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
537
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
538
transform.set_executability(True, sac)
539
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
540
self.assertRaises(KeyError, transform.set_executability, None, uws)
542
self.assertTrue(wt.is_executable('soc'))
543
self.assertTrue(wt.is_executable('sac'))
545
def test_preserve_mode(self):
546
"""File mode is preserved when replacing content"""
547
if sys.platform == 'win32':
548
raise TestSkipped('chmod has no effect on win32')
549
transform, root = self.get_transform()
550
transform.new_file('file1', root, 'contents', 'file1-id', True)
552
self.assertTrue(self.wt.is_executable('file1-id'))
553
transform, root = self.get_transform()
554
file1_id = transform.trans_id_tree_file_id('file1-id')
555
transform.delete_contents(file1_id)
556
transform.create_file('contents2', file1_id)
558
self.assertTrue(self.wt.is_executable('file1-id'))
560
def test__set_mode_stats_correctly(self):
561
"""_set_mode stats to determine file mode."""
562
if sys.platform == 'win32':
563
raise TestSkipped('chmod has no effect on win32')
567
def instrumented_stat(path):
568
stat_paths.append(path)
569
return real_stat(path)
571
transform, root = self.get_transform()
573
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
574
file_id='bar-id-1', executable=False)
577
transform, root = self.get_transform()
578
bar1_id = transform.trans_id_tree_path('bar')
579
bar2_id = transform.trans_id_tree_path('bar2')
581
os.stat = instrumented_stat
582
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
587
bar1_abspath = self.wt.abspath('bar')
588
self.assertEqual([bar1_abspath], stat_paths)
591
class TransformGroup(object):
592
def __init__(self, dirname, root_id):
595
self.wt = BzrDir.create_standalone_workingtree(dirname)
596
self.wt.set_root_id(root_id)
597
self.b = self.wt.branch
598
self.tt = TreeTransform(self.wt)
599
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
601
def conflict_text(tree, merge):
602
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
603
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
606
class TestTransformMerge(TestCaseInTempDir):
607
def test_text_merge(self):
608
root_id = generate_ids.gen_root_id()
609
base = TransformGroup("base", root_id)
610
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
611
base.tt.new_file('b', base.root, 'b1', 'b')
612
base.tt.new_file('c', base.root, 'c', 'c')
613
base.tt.new_file('d', base.root, 'd', 'd')
614
base.tt.new_file('e', base.root, 'e', 'e')
615
base.tt.new_file('f', base.root, 'f', 'f')
616
base.tt.new_directory('g', base.root, 'g')
617
base.tt.new_directory('h', base.root, 'h')
619
other = TransformGroup("other", root_id)
620
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
621
other.tt.new_file('b', other.root, 'b2', 'b')
622
other.tt.new_file('c', other.root, 'c2', 'c')
623
other.tt.new_file('d', other.root, 'd', 'd')
624
other.tt.new_file('e', other.root, 'e2', 'e')
625
other.tt.new_file('f', other.root, 'f', 'f')
626
other.tt.new_file('g', other.root, 'g', 'g')
627
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
628
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
630
this = TransformGroup("this", root_id)
631
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
632
this.tt.new_file('b', this.root, 'b', 'b')
633
this.tt.new_file('c', this.root, 'c', 'c')
634
this.tt.new_file('d', this.root, 'd2', 'd')
635
this.tt.new_file('e', this.root, 'e2', 'e')
636
this.tt.new_file('f', this.root, 'f', 'f')
637
this.tt.new_file('g', this.root, 'g', 'g')
638
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
639
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
641
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
643
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
644
# three-way text conflict
645
self.assertEqual(this.wt.get_file('b').read(),
646
conflict_text('b', 'b2'))
648
self.assertEqual(this.wt.get_file('c').read(), 'c2')
650
self.assertEqual(this.wt.get_file('d').read(), 'd2')
651
# Ambigious clean merge
652
self.assertEqual(this.wt.get_file('e').read(), 'e2')
654
self.assertEqual(this.wt.get_file('f').read(), 'f')
655
# Correct correct results when THIS == OTHER
656
self.assertEqual(this.wt.get_file('g').read(), 'g')
657
# Text conflict when THIS & OTHER are text and BASE is dir
658
self.assertEqual(this.wt.get_file('h').read(),
659
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
660
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
662
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
664
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
665
self.assertEqual(this.wt.get_file('i').read(),
666
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
667
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
669
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
671
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
672
modified = ['a', 'b', 'c', 'h', 'i']
673
merge_modified = this.wt.merge_modified()
674
self.assertSubset(merge_modified, modified)
675
self.assertEqual(len(merge_modified), len(modified))
676
file(this.wt.id2abspath('a'), 'wb').write('booga')
678
merge_modified = this.wt.merge_modified()
679
self.assertSubset(merge_modified, modified)
680
self.assertEqual(len(merge_modified), len(modified))
684
def test_file_merge(self):
685
if not has_symlinks():
686
raise TestSkipped('Symlinks are not supported on this platform')
687
root_id = generate_ids.gen_root_id()
688
base = TransformGroup("BASE", root_id)
689
this = TransformGroup("THIS", root_id)
690
other = TransformGroup("OTHER", root_id)
691
for tg in this, base, other:
692
tg.tt.new_directory('a', tg.root, 'a')
693
tg.tt.new_symlink('b', tg.root, 'b', 'b')
694
tg.tt.new_file('c', tg.root, 'c', 'c')
695
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
696
targets = ((base, 'base-e', 'base-f', None, None),
697
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
698
(other, 'other-e', None, 'other-g', 'other-h'))
699
for tg, e_target, f_target, g_target, h_target in targets:
700
for link, target in (('e', e_target), ('f', f_target),
701
('g', g_target), ('h', h_target)):
702
if target is not None:
703
tg.tt.new_symlink(link, tg.root, target, link)
705
for tg in this, base, other:
707
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
708
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
709
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
710
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
711
for suffix in ('THIS', 'BASE', 'OTHER'):
712
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
713
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
714
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
715
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
716
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
717
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
718
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
719
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
720
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
721
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
722
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
723
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
724
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
725
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
727
def test_filename_merge(self):
728
root_id = generate_ids.gen_root_id()
729
base = TransformGroup("BASE", root_id)
730
this = TransformGroup("THIS", root_id)
731
other = TransformGroup("OTHER", root_id)
732
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
733
for t in [base, this, other]]
734
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
735
for t in [base, this, other]]
736
base.tt.new_directory('c', base_a, 'c')
737
this.tt.new_directory('c1', this_a, 'c')
738
other.tt.new_directory('c', other_b, 'c')
740
base.tt.new_directory('d', base_a, 'd')
741
this.tt.new_directory('d1', this_b, 'd')
742
other.tt.new_directory('d', other_a, 'd')
744
base.tt.new_directory('e', base_a, 'e')
745
this.tt.new_directory('e', this_a, 'e')
746
other.tt.new_directory('e1', other_b, 'e')
748
base.tt.new_directory('f', base_a, 'f')
749
this.tt.new_directory('f1', this_b, 'f')
750
other.tt.new_directory('f1', other_b, 'f')
752
for tg in [this, base, other]:
754
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
755
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
756
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
757
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
758
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
760
def test_filename_merge_conflicts(self):
761
root_id = generate_ids.gen_root_id()
762
base = TransformGroup("BASE", root_id)
763
this = TransformGroup("THIS", root_id)
764
other = TransformGroup("OTHER", root_id)
765
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
766
for t in [base, this, other]]
767
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
768
for t in [base, this, other]]
770
base.tt.new_file('g', base_a, 'g', 'g')
771
other.tt.new_file('g1', other_b, 'g1', 'g')
773
base.tt.new_file('h', base_a, 'h', 'h')
774
this.tt.new_file('h1', this_b, 'h1', 'h')
776
base.tt.new_file('i', base.root, 'i', 'i')
777
other.tt.new_directory('i1', this_b, 'i')
779
for tg in [this, base, other]:
781
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
783
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
784
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
785
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
786
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
787
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
788
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
789
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
792
class TestBuildTree(tests.TestCaseWithTransport):
794
def test_build_tree(self):
795
if not has_symlinks():
796
raise TestSkipped('Test requires symlink support')
798
a = BzrDir.create_standalone_workingtree('a')
800
file('a/foo/bar', 'wb').write('contents')
801
os.symlink('a/foo/bar', 'a/foo/baz')
802
a.add(['foo', 'foo/bar', 'foo/baz'])
803
a.commit('initial commit')
804
b = BzrDir.create_standalone_workingtree('b')
805
build_tree(a.basis_tree(), b)
806
self.assertIs(os.path.isdir('b/foo'), True)
807
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
808
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
810
def test_file_conflict_handling(self):
811
"""Ensure that when building trees, conflict handling is done"""
812
source = self.make_branch_and_tree('source')
813
target = self.make_branch_and_tree('target')
814
self.build_tree(['source/file', 'target/file'])
815
source.add('file', 'new-file')
816
source.commit('added file')
817
build_tree(source.basis_tree(), target)
818
self.assertEqual([DuplicateEntry('Moved existing file to',
819
'file.moved', 'file', None, 'new-file')],
821
target2 = self.make_branch_and_tree('target2')
822
target_file = file('target2/file', 'wb')
824
source_file = file('source/file', 'rb')
826
target_file.write(source_file.read())
831
build_tree(source.basis_tree(), target2)
832
self.assertEqual([], target2.conflicts())
834
def test_symlink_conflict_handling(self):
835
"""Ensure that when building trees, conflict handling is done"""
836
if not has_symlinks():
837
raise TestSkipped('Test requires symlink support')
838
source = self.make_branch_and_tree('source')
839
os.symlink('foo', 'source/symlink')
840
source.add('symlink', 'new-symlink')
841
source.commit('added file')
842
target = self.make_branch_and_tree('target')
843
os.symlink('bar', 'target/symlink')
844
build_tree(source.basis_tree(), target)
845
self.assertEqual([DuplicateEntry('Moved existing file to',
846
'symlink.moved', 'symlink', None, 'new-symlink')],
848
target = self.make_branch_and_tree('target2')
849
os.symlink('foo', 'target2/symlink')
850
build_tree(source.basis_tree(), target)
851
self.assertEqual([], target.conflicts())
853
def test_directory_conflict_handling(self):
854
"""Ensure that when building trees, conflict handling is done"""
855
source = self.make_branch_and_tree('source')
856
target = self.make_branch_and_tree('target')
857
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
858
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
859
source.commit('added file')
860
build_tree(source.basis_tree(), target)
861
self.assertEqual([], target.conflicts())
862
self.failUnlessExists('target/dir1/file')
864
# Ensure contents are merged
865
target = self.make_branch_and_tree('target2')
866
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
867
build_tree(source.basis_tree(), target)
868
self.assertEqual([], target.conflicts())
869
self.failUnlessExists('target2/dir1/file2')
870
self.failUnlessExists('target2/dir1/file')
872
# Ensure new contents are suppressed for existing branches
873
target = self.make_branch_and_tree('target3')
874
self.make_branch('target3/dir1')
875
self.build_tree(['target3/dir1/file2'])
876
build_tree(source.basis_tree(), target)
877
self.failIfExists('target3/dir1/file')
878
self.failUnlessExists('target3/dir1/file2')
879
self.failUnlessExists('target3/dir1.diverted/file')
880
self.assertEqual([DuplicateEntry('Diverted to',
881
'dir1.diverted', 'dir1', 'new-dir1', None)],
884
target = self.make_branch_and_tree('target4')
885
self.build_tree(['target4/dir1/'])
886
self.make_branch('target4/dir1/file')
887
build_tree(source.basis_tree(), target)
888
self.failUnlessExists('target4/dir1/file')
889
self.assertEqual('directory', file_kind('target4/dir1/file'))
890
self.failUnlessExists('target4/dir1/file.diverted')
891
self.assertEqual([DuplicateEntry('Diverted to',
892
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
895
def test_mixed_conflict_handling(self):
896
"""Ensure that when building trees, conflict handling is done"""
897
source = self.make_branch_and_tree('source')
898
target = self.make_branch_and_tree('target')
899
self.build_tree(['source/name', 'target/name/'])
900
source.add('name', 'new-name')
901
source.commit('added file')
902
build_tree(source.basis_tree(), target)
903
self.assertEqual([DuplicateEntry('Moved existing file to',
904
'name.moved', 'name', None, 'new-name')], target.conflicts())
906
def test_raises_in_populated(self):
907
source = self.make_branch_and_tree('source')
908
self.build_tree(['source/name'])
910
source.commit('added name')
911
target = self.make_branch_and_tree('target')
912
self.build_tree(['target/name'])
914
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
915
build_tree, source.basis_tree(), target)
918
class MockTransform(object):
920
def has_named_child(self, by_parent, parent_id, name):
921
for child_id in by_parent[parent_id]:
925
elif name == "name.~%s~" % child_id:
929
class MockEntry(object):
931
object.__init__(self)
934
class TestGetBackupName(TestCase):
935
def test_get_backup_name(self):
937
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
938
self.assertEqual(name, 'name.~1~')
939
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
940
self.assertEqual(name, 'name.~2~')
941
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
942
self.assertEqual(name, 'name.~1~')
943
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
944
self.assertEqual(name, 'name.~1~')
945
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
946
self.assertEqual(name, 'name.~4~')