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
35
class TestTreeTransform(TestCaseInTempDir):
38
super(TestTreeTransform, self).setUp()
39
self.wt = BzrDir.create_standalone_workingtree('.')
42
def get_transform(self):
43
transform = TreeTransform(self.wt)
44
#self.addCleanup(transform.finalize)
45
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
47
def test_existing_limbo(self):
48
limbo_name = urlutils.local_path_from_url(
49
self.wt._control_files.controlfilename('limbo'))
50
transform, root = self.get_transform()
51
os.mkdir(pathjoin(limbo_name, 'hehe'))
52
self.assertRaises(ImmortalLimbo, transform.apply)
53
self.assertRaises(LockError, self.wt.unlock)
54
self.assertRaises(ExistingLimbo, self.get_transform)
55
self.assertRaises(LockError, self.wt.unlock)
56
os.rmdir(pathjoin(limbo_name, 'hehe'))
58
transform, root = self.get_transform()
62
transform, root = self.get_transform()
63
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
64
imaginary_id = transform.trans_id_tree_path('imaginary')
65
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
66
self.assertEqual(imaginary_id, imaginary_id2)
67
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
68
self.assertEqual(transform.final_kind(root), 'directory')
69
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
70
trans_id = transform.create_path('name', root)
71
self.assertIs(transform.final_file_id(trans_id), None)
72
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
73
transform.create_file('contents', trans_id)
74
transform.set_executability(True, trans_id)
75
transform.version_file('my_pretties', trans_id)
76
self.assertRaises(DuplicateKey, transform.version_file,
77
'my_pretties', trans_id)
78
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
79
self.assertEqual(transform.final_parent(trans_id), root)
80
self.assertIs(transform.final_parent(root), ROOT_PARENT)
81
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
82
oz_id = transform.create_path('oz', root)
83
transform.create_directory(oz_id)
84
transform.version_file('ozzie', oz_id)
85
trans_id2 = transform.create_path('name2', root)
86
transform.create_file('contents', trans_id2)
87
transform.set_executability(False, trans_id2)
88
transform.version_file('my_pretties2', trans_id2)
89
modified_paths = transform.apply().modified_paths
90
self.assertEqual('contents', self.wt.get_file_byname('name').read())
91
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
92
self.assertIs(self.wt.is_executable('my_pretties'), True)
93
self.assertIs(self.wt.is_executable('my_pretties2'), False)
94
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
95
self.assertEqual(len(modified_paths), 3)
96
tree_mod_paths = [self.wt.id2abspath(f) for f in
97
('ozzie', 'my_pretties', 'my_pretties2')]
98
self.assertSubset(tree_mod_paths, modified_paths)
99
# is it safe to finalize repeatedly?
103
def test_convenience(self):
104
transform, root = self.get_transform()
105
trans_id = transform.new_file('name', root, 'contents',
107
oz = transform.new_directory('oz', root, 'oz-id')
108
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
109
toto = transform.new_file('toto', dorothy, 'toto-contents',
112
self.assertEqual(len(transform.find_conflicts()), 0)
114
self.assertRaises(ReusingTransform, transform.find_conflicts)
115
self.assertEqual('contents', file(self.wt.abspath('name')).read())
116
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
117
self.assertIs(self.wt.is_executable('my_pretties'), True)
118
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
119
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
120
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
122
self.assertEqual('toto-contents',
123
self.wt.get_file_byname('oz/dorothy/toto').read())
124
self.assertIs(self.wt.is_executable('toto-id'), False)
126
def test_conflicts(self):
127
transform, root = self.get_transform()
128
trans_id = transform.new_file('name', root, 'contents',
130
self.assertEqual(len(transform.find_conflicts()), 0)
131
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
132
self.assertEqual(transform.find_conflicts(),
133
[('duplicate', trans_id, trans_id2, 'name')])
134
self.assertRaises(MalformedTransform, transform.apply)
135
transform.adjust_path('name', trans_id, trans_id2)
136
self.assertEqual(transform.find_conflicts(),
137
[('non-directory parent', trans_id)])
138
tinman_id = transform.trans_id_tree_path('tinman')
139
transform.adjust_path('name', tinman_id, trans_id2)
140
self.assertEqual(transform.find_conflicts(),
141
[('unversioned parent', tinman_id),
142
('missing parent', tinman_id)])
143
lion_id = transform.create_path('lion', root)
144
self.assertEqual(transform.find_conflicts(),
145
[('unversioned parent', tinman_id),
146
('missing parent', tinman_id)])
147
transform.adjust_path('name', lion_id, trans_id2)
148
self.assertEqual(transform.find_conflicts(),
149
[('unversioned parent', lion_id),
150
('missing parent', lion_id)])
151
transform.version_file("Courage", lion_id)
152
self.assertEqual(transform.find_conflicts(),
153
[('missing parent', lion_id),
154
('versioning no contents', lion_id)])
155
transform.adjust_path('name2', root, trans_id2)
156
self.assertEqual(transform.find_conflicts(),
157
[('versioning no contents', lion_id)])
158
transform.create_file('Contents, okay?', lion_id)
159
transform.adjust_path('name2', trans_id2, trans_id2)
160
self.assertEqual(transform.find_conflicts(),
161
[('parent loop', trans_id2),
162
('non-directory parent', trans_id2)])
163
transform.adjust_path('name2', root, trans_id2)
164
oz_id = transform.new_directory('oz', root)
165
transform.set_executability(True, oz_id)
166
self.assertEqual(transform.find_conflicts(),
167
[('unversioned executability', oz_id)])
168
transform.version_file('oz-id', oz_id)
169
self.assertEqual(transform.find_conflicts(),
170
[('non-file executability', oz_id)])
171
transform.set_executability(None, oz_id)
172
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
174
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
175
self.assertEqual('contents', file(self.wt.abspath('name')).read())
176
transform2, root = self.get_transform()
177
oz_id = transform2.trans_id_tree_file_id('oz-id')
178
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
179
result = transform2.find_conflicts()
180
fp = FinalPaths(transform2)
181
self.assert_('oz/tip' in transform2._tree_path_ids)
182
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
183
self.assertEqual(len(result), 2)
184
self.assertEqual((result[0][0], result[0][1]),
185
('duplicate', newtip))
186
self.assertEqual((result[1][0], result[1][2]),
187
('duplicate id', newtip))
188
transform2.finalize()
189
transform3 = TreeTransform(self.wt)
190
self.addCleanup(transform3.finalize)
191
oz_id = transform3.trans_id_tree_file_id('oz-id')
192
transform3.delete_contents(oz_id)
193
self.assertEqual(transform3.find_conflicts(),
194
[('missing parent', oz_id)])
195
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
196
tip_id = transform3.trans_id_tree_file_id('tip-id')
197
transform3.adjust_path('tip', root_id, tip_id)
200
def test_add_del(self):
201
start, root = self.get_transform()
202
start.new_directory('a', root, 'a')
204
transform, root = self.get_transform()
205
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
206
transform.new_directory('a', root, 'a')
209
def test_unversioning(self):
210
create_tree, root = self.get_transform()
211
parent_id = create_tree.new_directory('parent', root, 'parent-id')
212
create_tree.new_file('child', parent_id, 'child', 'child-id')
214
unversion = TreeTransform(self.wt)
215
self.addCleanup(unversion.finalize)
216
parent = unversion.trans_id_tree_path('parent')
217
unversion.unversion_file(parent)
218
self.assertEqual(unversion.find_conflicts(),
219
[('unversioned parent', parent_id)])
220
file_id = unversion.trans_id_tree_file_id('child-id')
221
unversion.unversion_file(file_id)
224
def test_name_invariants(self):
225
create_tree, root = self.get_transform()
227
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
228
create_tree.new_file('name1', root, 'hello1', 'name1')
229
create_tree.new_file('name2', root, 'hello2', 'name2')
230
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
231
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
232
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
233
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
236
mangle_tree,root = self.get_transform()
237
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
239
name1 = mangle_tree.trans_id_tree_file_id('name1')
240
name2 = mangle_tree.trans_id_tree_file_id('name2')
241
mangle_tree.adjust_path('name2', root, name1)
242
mangle_tree.adjust_path('name1', root, name2)
244
#tests for deleting parent directories
245
ddir = mangle_tree.trans_id_tree_file_id('ddir')
246
mangle_tree.delete_contents(ddir)
247
dfile = mangle_tree.trans_id_tree_file_id('dfile')
248
mangle_tree.delete_versioned(dfile)
249
mangle_tree.unversion_file(dfile)
250
mfile = mangle_tree.trans_id_tree_file_id('mfile')
251
mangle_tree.adjust_path('mfile', root, mfile)
253
#tests for adding parent directories
254
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
255
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
256
mangle_tree.adjust_path('mfile2', newdir, mfile2)
257
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
258
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
259
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
260
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
262
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
263
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
264
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
265
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
266
self.assertEqual(file(mfile2_path).read(), 'later2')
267
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
268
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
269
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
270
self.assertEqual(file(newfile_path).read(), 'hello3')
271
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
272
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
273
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
275
def test_both_rename(self):
276
create_tree,root = self.get_transform()
277
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
278
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
280
mangle_tree,root = self.get_transform()
281
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
282
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
283
mangle_tree.adjust_path('test', root, selftest)
284
mangle_tree.adjust_path('test_too_much', root, selftest)
285
mangle_tree.set_executability(True, blackbox)
288
def test_both_rename2(self):
289
create_tree,root = self.get_transform()
290
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
291
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
292
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
293
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
296
mangle_tree,root = self.get_transform()
297
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
298
tests = mangle_tree.trans_id_tree_file_id('tests-id')
299
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
300
mangle_tree.adjust_path('selftest', bzrlib, tests)
301
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
302
mangle_tree.set_executability(True, test_too_much)
305
def test_both_rename3(self):
306
create_tree,root = self.get_transform()
307
tests = create_tree.new_directory('tests', root, 'tests-id')
308
create_tree.new_file('test_too_much.py', tests, 'hello1',
311
mangle_tree,root = self.get_transform()
312
tests = mangle_tree.trans_id_tree_file_id('tests-id')
313
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
314
mangle_tree.adjust_path('selftest', root, tests)
315
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
316
mangle_tree.set_executability(True, test_too_much)
319
def test_move_dangling_ie(self):
320
create_tree, root = self.get_transform()
322
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
323
create_tree.new_file('name1', root, 'hello1', 'name1')
325
delete_contents, root = self.get_transform()
326
file = delete_contents.trans_id_tree_file_id('name1')
327
delete_contents.delete_contents(file)
328
delete_contents.apply()
329
move_id, root = self.get_transform()
330
name1 = move_id.trans_id_tree_file_id('name1')
331
newdir = move_id.new_directory('dir', root, 'newdir')
332
move_id.adjust_path('name2', newdir, name1)
335
def test_replace_dangling_ie(self):
336
create_tree, root = self.get_transform()
338
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
339
create_tree.new_file('name1', root, 'hello1', 'name1')
341
delete_contents = TreeTransform(self.wt)
342
self.addCleanup(delete_contents.finalize)
343
file = delete_contents.trans_id_tree_file_id('name1')
344
delete_contents.delete_contents(file)
345
delete_contents.apply()
346
delete_contents.finalize()
347
replace = TreeTransform(self.wt)
348
self.addCleanup(replace.finalize)
349
name2 = replace.new_file('name2', root, 'hello2', 'name1')
350
conflicts = replace.find_conflicts()
351
name1 = replace.trans_id_tree_file_id('name1')
352
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
353
resolve_conflicts(replace)
356
def test_symlinks(self):
357
if not has_symlinks():
358
raise TestSkipped('Symlinks are not supported on this platform')
359
transform,root = self.get_transform()
360
oz_id = transform.new_directory('oz', root, 'oz-id')
361
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
363
wiz_id = transform.create_path('wizard2', oz_id)
364
transform.create_symlink('behind_curtain', wiz_id)
365
transform.version_file('wiz-id2', wiz_id)
366
transform.set_executability(True, wiz_id)
367
self.assertEqual(transform.find_conflicts(),
368
[('non-file executability', wiz_id)])
369
transform.set_executability(None, wiz_id)
371
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
372
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
373
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
375
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
379
def get_conflicted(self):
380
create,root = self.get_transform()
381
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
382
oz = create.new_directory('oz', root, 'oz-id')
383
create.new_directory('emeraldcity', oz, 'emerald-id')
385
conflicts,root = self.get_transform()
386
# set up duplicate entry, duplicate id
387
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
389
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
390
oz = conflicts.trans_id_tree_file_id('oz-id')
391
# set up DeletedParent parent conflict
392
conflicts.delete_versioned(oz)
393
emerald = conflicts.trans_id_tree_file_id('emerald-id')
394
# set up MissingParent conflict
395
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
396
conflicts.adjust_path('munchkincity', root, munchkincity)
397
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
399
conflicts.adjust_path('emeraldcity', emerald, emerald)
400
return conflicts, emerald, oz, old_dorothy, new_dorothy
402
def test_conflict_resolution(self):
403
conflicts, emerald, oz, old_dorothy, new_dorothy =\
404
self.get_conflicted()
405
resolve_conflicts(conflicts)
406
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
407
self.assertIs(conflicts.final_file_id(old_dorothy), None)
408
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
409
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
410
self.assertEqual(conflicts.final_parent(emerald), oz)
413
def test_cook_conflicts(self):
414
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
415
raw_conflicts = resolve_conflicts(tt)
416
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
417
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
418
'dorothy', None, 'dorothy-id')
419
self.assertEqual(cooked_conflicts[0], duplicate)
420
duplicate_id = DuplicateID('Unversioned existing file',
421
'dorothy.moved', 'dorothy', None,
423
self.assertEqual(cooked_conflicts[1], duplicate_id)
424
missing_parent = MissingParent('Created directory', 'munchkincity',
426
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
427
self.assertEqual(cooked_conflicts[2], missing_parent)
428
unversioned_parent = UnversionedParent('Versioned directory',
431
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
433
self.assertEqual(cooked_conflicts[3], unversioned_parent)
434
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
435
'oz/emeraldcity', 'emerald-id', 'emerald-id')
436
self.assertEqual(cooked_conflicts[4], deleted_parent)
437
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
438
self.assertEqual(cooked_conflicts[6], parent_loop)
439
self.assertEqual(len(cooked_conflicts), 7)
442
def test_string_conflicts(self):
443
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
444
raw_conflicts = resolve_conflicts(tt)
445
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
447
conflicts_s = [str(c) for c in cooked_conflicts]
448
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
449
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
450
'Moved existing file to '
452
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
453
'Unversioned existing file '
455
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
456
' munchkincity. Created directory.')
457
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
458
' versioned, but has versioned'
459
' children. Versioned directory.')
460
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
461
" is not empty. Not deleting.")
462
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
463
' versioned, but has versioned'
464
' children. Versioned directory.')
465
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
466
' oz/emeraldcity. Cancelled move.')
468
def test_moving_versioned_directories(self):
469
create, root = self.get_transform()
470
kansas = create.new_directory('kansas', root, 'kansas-id')
471
create.new_directory('house', kansas, 'house-id')
472
create.new_directory('oz', root, 'oz-id')
474
cyclone, root = self.get_transform()
475
oz = cyclone.trans_id_tree_file_id('oz-id')
476
house = cyclone.trans_id_tree_file_id('house-id')
477
cyclone.adjust_path('house', oz, house)
480
def test_moving_root(self):
481
create, root = self.get_transform()
482
fun = create.new_directory('fun', root, 'fun-id')
483
create.new_directory('sun', root, 'sun-id')
484
create.new_directory('moon', root, 'moon')
486
transform, root = self.get_transform()
487
transform.adjust_root_path('oldroot', fun)
488
new_root=transform.trans_id_tree_path('')
489
transform.version_file('new-root', new_root)
492
def test_renames(self):
493
create, root = self.get_transform()
494
old = create.new_directory('old-parent', root, 'old-id')
495
intermediate = create.new_directory('intermediate', old, 'im-id')
496
myfile = create.new_file('myfile', intermediate, 'myfile-text',
499
rename, root = self.get_transform()
500
old = rename.trans_id_file_id('old-id')
501
rename.adjust_path('new', root, old)
502
myfile = rename.trans_id_file_id('myfile-id')
503
rename.set_executability(True, myfile)
506
def test_find_interesting(self):
507
create, root = self.get_transform()
509
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
510
create.new_file('uvfile', root, 'othertext')
512
self.assertEqual(find_interesting(wt, wt, ['vfile']),
514
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
517
def test_set_executability_order(self):
518
"""Ensure that executability behaves the same, no matter what order.
520
- create file and set executability simultaneously
521
- create file and set executability afterward
522
- unsetting the executability of a file whose executability has not been
523
declared should throw an exception (this may happen when a
524
merge attempts to create a file with a duplicate ID)
526
transform, root = self.get_transform()
528
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
530
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
531
transform.set_executability(True, sac)
532
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
533
self.assertRaises(KeyError, transform.set_executability, None, uws)
535
self.assertTrue(wt.is_executable('soc'))
536
self.assertTrue(wt.is_executable('sac'))
539
class TransformGroup(object):
540
def __init__(self, dirname):
543
self.wt = BzrDir.create_standalone_workingtree(dirname)
544
self.b = self.wt.branch
545
self.tt = TreeTransform(self.wt)
546
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
548
def conflict_text(tree, merge):
549
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
550
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
553
class TestTransformMerge(TestCaseInTempDir):
554
def test_text_merge(self):
555
base = TransformGroup("base")
556
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
557
base.tt.new_file('b', base.root, 'b1', 'b')
558
base.tt.new_file('c', base.root, 'c', 'c')
559
base.tt.new_file('d', base.root, 'd', 'd')
560
base.tt.new_file('e', base.root, 'e', 'e')
561
base.tt.new_file('f', base.root, 'f', 'f')
562
base.tt.new_directory('g', base.root, 'g')
563
base.tt.new_directory('h', base.root, 'h')
565
other = TransformGroup("other")
566
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
567
other.tt.new_file('b', other.root, 'b2', 'b')
568
other.tt.new_file('c', other.root, 'c2', 'c')
569
other.tt.new_file('d', other.root, 'd', 'd')
570
other.tt.new_file('e', other.root, 'e2', 'e')
571
other.tt.new_file('f', other.root, 'f', 'f')
572
other.tt.new_file('g', other.root, 'g', 'g')
573
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
574
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
576
this = TransformGroup("this")
577
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
578
this.tt.new_file('b', this.root, 'b', 'b')
579
this.tt.new_file('c', this.root, 'c', 'c')
580
this.tt.new_file('d', this.root, 'd2', 'd')
581
this.tt.new_file('e', this.root, 'e2', 'e')
582
this.tt.new_file('f', this.root, 'f', 'f')
583
this.tt.new_file('g', this.root, 'g', 'g')
584
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
585
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
587
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
589
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
590
# three-way text conflict
591
self.assertEqual(this.wt.get_file('b').read(),
592
conflict_text('b', 'b2'))
594
self.assertEqual(this.wt.get_file('c').read(), 'c2')
596
self.assertEqual(this.wt.get_file('d').read(), 'd2')
597
# Ambigious clean merge
598
self.assertEqual(this.wt.get_file('e').read(), 'e2')
600
self.assertEqual(this.wt.get_file('f').read(), 'f')
601
# Correct correct results when THIS == OTHER
602
self.assertEqual(this.wt.get_file('g').read(), 'g')
603
# Text conflict when THIS & OTHER are text and BASE is dir
604
self.assertEqual(this.wt.get_file('h').read(),
605
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
606
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
608
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
610
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
611
self.assertEqual(this.wt.get_file('i').read(),
612
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
613
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
615
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
617
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
618
modified = ['a', 'b', 'c', 'h', 'i']
619
merge_modified = this.wt.merge_modified()
620
self.assertSubset(merge_modified, modified)
621
self.assertEqual(len(merge_modified), len(modified))
622
file(this.wt.id2abspath('a'), 'wb').write('booga')
624
merge_modified = this.wt.merge_modified()
625
self.assertSubset(merge_modified, modified)
626
self.assertEqual(len(merge_modified), len(modified))
630
def test_file_merge(self):
631
if not has_symlinks():
632
raise TestSkipped('Symlinks are not supported on this platform')
633
base = TransformGroup("BASE")
634
this = TransformGroup("THIS")
635
other = TransformGroup("OTHER")
636
for tg in this, base, other:
637
tg.tt.new_directory('a', tg.root, 'a')
638
tg.tt.new_symlink('b', tg.root, 'b', 'b')
639
tg.tt.new_file('c', tg.root, 'c', 'c')
640
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
641
targets = ((base, 'base-e', 'base-f', None, None),
642
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
643
(other, 'other-e', None, 'other-g', 'other-h'))
644
for tg, e_target, f_target, g_target, h_target in targets:
645
for link, target in (('e', e_target), ('f', f_target),
646
('g', g_target), ('h', h_target)):
647
if target is not None:
648
tg.tt.new_symlink(link, tg.root, target, link)
650
for tg in this, base, other:
652
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
653
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
654
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
655
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
656
for suffix in ('THIS', 'BASE', 'OTHER'):
657
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
658
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
659
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
660
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
661
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
662
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
663
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
664
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
665
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
666
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
667
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
668
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
669
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
670
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
672
def test_filename_merge(self):
673
base = TransformGroup("BASE")
674
this = TransformGroup("THIS")
675
other = TransformGroup("OTHER")
676
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
677
for t in [base, this, other]]
678
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
679
for t in [base, this, other]]
680
base.tt.new_directory('c', base_a, 'c')
681
this.tt.new_directory('c1', this_a, 'c')
682
other.tt.new_directory('c', other_b, 'c')
684
base.tt.new_directory('d', base_a, 'd')
685
this.tt.new_directory('d1', this_b, 'd')
686
other.tt.new_directory('d', other_a, 'd')
688
base.tt.new_directory('e', base_a, 'e')
689
this.tt.new_directory('e', this_a, 'e')
690
other.tt.new_directory('e1', other_b, 'e')
692
base.tt.new_directory('f', base_a, 'f')
693
this.tt.new_directory('f1', this_b, 'f')
694
other.tt.new_directory('f1', other_b, 'f')
696
for tg in [this, base, other]:
698
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
699
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
700
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
701
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
702
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
704
def test_filename_merge_conflicts(self):
705
base = TransformGroup("BASE")
706
this = TransformGroup("THIS")
707
other = TransformGroup("OTHER")
708
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
709
for t in [base, this, other]]
710
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
711
for t in [base, this, other]]
713
base.tt.new_file('g', base_a, 'g', 'g')
714
other.tt.new_file('g1', other_b, 'g1', 'g')
716
base.tt.new_file('h', base_a, 'h', 'h')
717
this.tt.new_file('h1', this_b, 'h1', 'h')
719
base.tt.new_file('i', base.root, 'i', 'i')
720
other.tt.new_directory('i1', this_b, 'i')
722
for tg in [this, base, other]:
724
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
726
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
727
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
728
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
729
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
730
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
731
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
732
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
734
class TestBuildTree(tests.TestCaseWithTransport):
736
def test_build_tree(self):
737
if not has_symlinks():
738
raise TestSkipped('Test requires symlink support')
740
a = BzrDir.create_standalone_workingtree('a')
742
file('a/foo/bar', 'wb').write('contents')
743
os.symlink('a/foo/bar', 'a/foo/baz')
744
a.add(['foo', 'foo/bar', 'foo/baz'])
745
a.commit('initial commit')
746
b = BzrDir.create_standalone_workingtree('b')
747
build_tree(a.basis_tree(), b)
748
self.assertIs(os.path.isdir('b/foo'), True)
749
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
750
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
752
def test_file_conflict_handling(self):
753
"""Ensure that when building trees, conflict handling is done"""
754
source = self.make_branch_and_tree('source')
755
target = self.make_branch_and_tree('target')
756
self.build_tree(['source/file', 'target/file'])
757
source.add('file', 'new-file')
758
source.commit('added file')
759
build_tree(source.basis_tree(), target)
760
self.assertEqual([DuplicateEntry('Moved existing file to',
761
'file.moved', 'file', None, 'new-file')],
763
target2 = self.make_branch_and_tree('target2')
764
target_file = file('target2/file', 'wb')
766
source_file = file('source/file', 'rb')
768
target_file.write(source_file.read())
773
build_tree(source.basis_tree(), target2)
774
self.assertEqual([], target2.conflicts())
776
def test_symlink_conflict_handling(self):
777
"""Ensure that when building trees, conflict handling is done"""
778
if not has_symlinks():
779
raise TestSkipped('Test requires symlink support')
780
source = self.make_branch_and_tree('source')
781
os.symlink('foo', 'source/symlink')
782
source.add('symlink', 'new-symlink')
783
source.commit('added file')
784
target = self.make_branch_and_tree('target')
785
os.symlink('bar', 'target/symlink')
786
build_tree(source.basis_tree(), target)
787
self.assertEqual([DuplicateEntry('Moved existing file to',
788
'symlink.moved', 'symlink', None, 'new-symlink')],
790
target = self.make_branch_and_tree('target2')
791
os.symlink('foo', 'target2/symlink')
792
build_tree(source.basis_tree(), target)
793
self.assertEqual([], target.conflicts())
795
def test_directory_conflict_handling(self):
796
"""Ensure that when building trees, conflict handling is done"""
797
source = self.make_branch_and_tree('source')
798
target = self.make_branch_and_tree('target')
799
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
800
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
801
source.commit('added file')
802
build_tree(source.basis_tree(), target)
803
self.assertEqual([], target.conflicts())
804
self.failUnlessExists('target/dir1/file')
806
# Ensure contents are merged
807
target = self.make_branch_and_tree('target2')
808
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
809
build_tree(source.basis_tree(), target)
810
self.assertEqual([], target.conflicts())
811
self.failUnlessExists('target2/dir1/file2')
812
self.failUnlessExists('target2/dir1/file')
814
# Ensure new contents are suppressed for existing branches
815
target = self.make_branch_and_tree('target3')
816
self.make_branch('target3/dir1')
817
self.build_tree(['target3/dir1/file2'])
818
build_tree(source.basis_tree(), target)
819
self.failIfExists('target3/dir1/file')
820
self.failUnlessExists('target3/dir1/file2')
821
self.failUnlessExists('target3/dir1.diverted/file')
822
self.assertEqual([DuplicateEntry('Diverted to',
823
'dir1.diverted', 'dir1', 'new-dir1', None)],
826
target = self.make_branch_and_tree('target4')
827
self.build_tree(['target4/dir1/'])
828
self.make_branch('target4/dir1/file')
829
build_tree(source.basis_tree(), target)
830
self.failUnlessExists('target4/dir1/file')
831
self.assertEqual('directory', file_kind('target4/dir1/file'))
832
self.failUnlessExists('target4/dir1/file.diverted')
833
self.assertEqual([DuplicateEntry('Diverted to',
834
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
837
def test_mixed_conflict_handling(self):
838
"""Ensure that when building trees, conflict handling is done"""
839
source = self.make_branch_and_tree('source')
840
target = self.make_branch_and_tree('target')
841
self.build_tree(['source/name', 'target/name/'])
842
source.add('name', 'new-name')
843
source.commit('added file')
844
build_tree(source.basis_tree(), target)
845
self.assertEqual([DuplicateEntry('Moved existing file to',
846
'name.moved', 'name', None, 'new-name')], target.conflicts())
848
def test_raises_in_populated(self):
849
source = self.make_branch_and_tree('source')
850
self.build_tree(['source/name'])
852
source.commit('added name')
853
target = self.make_branch_and_tree('target')
854
self.build_tree(['target/name'])
856
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
860
class MockTransform(object):
862
def has_named_child(self, by_parent, parent_id, name):
863
for child_id in by_parent[parent_id]:
867
elif name == "name.~%s~" % child_id:
871
class MockEntry(object):
873
object.__init__(self)
876
class TestGetBackupName(TestCase):
877
def test_get_backup_name(self):
879
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
880
self.assertEqual(name, 'name.~1~')
881
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
882
self.assertEqual(name, 'name.~2~')
883
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
884
self.assertEqual(name, 'name.~1~')
885
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
886
self.assertEqual(name, 'name.~1~')
887
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
888
self.assertEqual(name, 'name.~4~')