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
25
from bzrlib.bzrdir import BzrDir
26
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
27
UnversionedParent, ParentLoop, DeletingParent,)
28
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
29
ReusingTransform, CantMoveRoot,
30
PathsNotVersionedError, ExistingLimbo,
31
ImmortalLimbo, LockError)
32
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
33
from bzrlib.merge import Merge3Merger
34
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
35
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
36
resolve_conflicts, cook_conflicts,
37
find_interesting, build_tree, get_backup_name)
40
class TestTreeTransform(TestCaseInTempDir):
43
super(TestTreeTransform, self).setUp()
44
self.wt = BzrDir.create_standalone_workingtree('.')
47
def get_transform(self):
48
transform = TreeTransform(self.wt)
49
#self.addCleanup(transform.finalize)
50
return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
52
def test_existing_limbo(self):
53
limbo_name = urlutils.local_path_from_url(
54
self.wt._control_files.controlfilename('limbo'))
55
transform, root = self.get_transform()
56
os.mkdir(pathjoin(limbo_name, 'hehe'))
57
self.assertRaises(ImmortalLimbo, transform.apply)
58
self.assertRaises(LockError, self.wt.unlock)
59
self.assertRaises(ExistingLimbo, self.get_transform)
60
self.assertRaises(LockError, self.wt.unlock)
61
os.rmdir(pathjoin(limbo_name, 'hehe'))
63
transform, root = self.get_transform()
67
transform, root = self.get_transform()
68
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
69
imaginary_id = transform.trans_id_tree_path('imaginary')
70
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
71
self.assertEqual(imaginary_id, imaginary_id2)
72
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
73
self.assertEqual(transform.final_kind(root), 'directory')
74
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
75
trans_id = transform.create_path('name', root)
76
self.assertIs(transform.final_file_id(trans_id), None)
77
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
78
transform.create_file('contents', trans_id)
79
transform.set_executability(True, trans_id)
80
transform.version_file('my_pretties', trans_id)
81
self.assertRaises(DuplicateKey, transform.version_file,
82
'my_pretties', trans_id)
83
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
84
self.assertEqual(transform.final_parent(trans_id), root)
85
self.assertIs(transform.final_parent(root), ROOT_PARENT)
86
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
87
oz_id = transform.create_path('oz', root)
88
transform.create_directory(oz_id)
89
transform.version_file('ozzie', oz_id)
90
trans_id2 = transform.create_path('name2', root)
91
transform.create_file('contents', trans_id2)
92
transform.set_executability(False, trans_id2)
93
transform.version_file('my_pretties2', trans_id2)
94
modified_paths = transform.apply().modified_paths
95
self.assertEqual('contents', self.wt.get_file_byname('name').read())
96
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
97
self.assertIs(self.wt.is_executable('my_pretties'), True)
98
self.assertIs(self.wt.is_executable('my_pretties2'), False)
99
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
100
self.assertEqual(len(modified_paths), 3)
101
tree_mod_paths = [self.wt.id2abspath(f) for f in
102
('ozzie', 'my_pretties', 'my_pretties2')]
103
self.assertSubset(tree_mod_paths, modified_paths)
104
# is it safe to finalize repeatedly?
108
def test_convenience(self):
109
transform, root = self.get_transform()
110
trans_id = transform.new_file('name', root, 'contents',
112
oz = transform.new_directory('oz', root, 'oz-id')
113
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
114
toto = transform.new_file('toto', dorothy, 'toto-contents',
117
self.assertEqual(len(transform.find_conflicts()), 0)
119
self.assertRaises(ReusingTransform, transform.find_conflicts)
120
self.assertEqual('contents', file(self.wt.abspath('name')).read())
121
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
122
self.assertIs(self.wt.is_executable('my_pretties'), True)
123
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
124
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
125
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
127
self.assertEqual('toto-contents',
128
self.wt.get_file_byname('oz/dorothy/toto').read())
129
self.assertIs(self.wt.is_executable('toto-id'), False)
131
def test_conflicts(self):
132
transform, root = self.get_transform()
133
trans_id = transform.new_file('name', root, 'contents',
135
self.assertEqual(len(transform.find_conflicts()), 0)
136
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
137
self.assertEqual(transform.find_conflicts(),
138
[('duplicate', trans_id, trans_id2, 'name')])
139
self.assertRaises(MalformedTransform, transform.apply)
140
transform.adjust_path('name', trans_id, trans_id2)
141
self.assertEqual(transform.find_conflicts(),
142
[('non-directory parent', trans_id)])
143
tinman_id = transform.trans_id_tree_path('tinman')
144
transform.adjust_path('name', tinman_id, trans_id2)
145
self.assertEqual(transform.find_conflicts(),
146
[('unversioned parent', tinman_id),
147
('missing parent', tinman_id)])
148
lion_id = transform.create_path('lion', root)
149
self.assertEqual(transform.find_conflicts(),
150
[('unversioned parent', tinman_id),
151
('missing parent', tinman_id)])
152
transform.adjust_path('name', lion_id, trans_id2)
153
self.assertEqual(transform.find_conflicts(),
154
[('unversioned parent', lion_id),
155
('missing parent', lion_id)])
156
transform.version_file("Courage", lion_id)
157
self.assertEqual(transform.find_conflicts(),
158
[('missing parent', lion_id),
159
('versioning no contents', lion_id)])
160
transform.adjust_path('name2', root, trans_id2)
161
self.assertEqual(transform.find_conflicts(),
162
[('versioning no contents', lion_id)])
163
transform.create_file('Contents, okay?', lion_id)
164
transform.adjust_path('name2', trans_id2, trans_id2)
165
self.assertEqual(transform.find_conflicts(),
166
[('parent loop', trans_id2),
167
('non-directory parent', trans_id2)])
168
transform.adjust_path('name2', root, trans_id2)
169
oz_id = transform.new_directory('oz', root)
170
transform.set_executability(True, oz_id)
171
self.assertEqual(transform.find_conflicts(),
172
[('unversioned executability', oz_id)])
173
transform.version_file('oz-id', oz_id)
174
self.assertEqual(transform.find_conflicts(),
175
[('non-file executability', oz_id)])
176
transform.set_executability(None, oz_id)
177
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
179
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
180
self.assertEqual('contents', file(self.wt.abspath('name')).read())
181
transform2, root = self.get_transform()
182
oz_id = transform2.trans_id_tree_file_id('oz-id')
183
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
184
result = transform2.find_conflicts()
185
fp = FinalPaths(transform2)
186
self.assert_('oz/tip' in transform2._tree_path_ids)
187
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
188
self.assertEqual(len(result), 2)
189
self.assertEqual((result[0][0], result[0][1]),
190
('duplicate', newtip))
191
self.assertEqual((result[1][0], result[1][2]),
192
('duplicate id', newtip))
193
transform2.finalize()
194
transform3 = TreeTransform(self.wt)
195
self.addCleanup(transform3.finalize)
196
oz_id = transform3.trans_id_tree_file_id('oz-id')
197
transform3.delete_contents(oz_id)
198
self.assertEqual(transform3.find_conflicts(),
199
[('missing parent', oz_id)])
200
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
201
tip_id = transform3.trans_id_tree_file_id('tip-id')
202
transform3.adjust_path('tip', root_id, tip_id)
205
def test_add_del(self):
206
start, root = self.get_transform()
207
start.new_directory('a', root, 'a')
209
transform, root = self.get_transform()
210
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
211
transform.new_directory('a', root, 'a')
214
def test_unversioning(self):
215
create_tree, root = self.get_transform()
216
parent_id = create_tree.new_directory('parent', root, 'parent-id')
217
create_tree.new_file('child', parent_id, 'child', 'child-id')
219
unversion = TreeTransform(self.wt)
220
self.addCleanup(unversion.finalize)
221
parent = unversion.trans_id_tree_path('parent')
222
unversion.unversion_file(parent)
223
self.assertEqual(unversion.find_conflicts(),
224
[('unversioned parent', parent_id)])
225
file_id = unversion.trans_id_tree_file_id('child-id')
226
unversion.unversion_file(file_id)
229
def test_name_invariants(self):
230
create_tree, root = self.get_transform()
232
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
233
create_tree.new_file('name1', root, 'hello1', 'name1')
234
create_tree.new_file('name2', root, 'hello2', 'name2')
235
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
236
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
237
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
238
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
241
mangle_tree,root = self.get_transform()
242
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
244
name1 = mangle_tree.trans_id_tree_file_id('name1')
245
name2 = mangle_tree.trans_id_tree_file_id('name2')
246
mangle_tree.adjust_path('name2', root, name1)
247
mangle_tree.adjust_path('name1', root, name2)
249
#tests for deleting parent directories
250
ddir = mangle_tree.trans_id_tree_file_id('ddir')
251
mangle_tree.delete_contents(ddir)
252
dfile = mangle_tree.trans_id_tree_file_id('dfile')
253
mangle_tree.delete_versioned(dfile)
254
mangle_tree.unversion_file(dfile)
255
mfile = mangle_tree.trans_id_tree_file_id('mfile')
256
mangle_tree.adjust_path('mfile', root, mfile)
258
#tests for adding parent directories
259
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
260
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
261
mangle_tree.adjust_path('mfile2', newdir, mfile2)
262
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
263
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
264
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
265
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
267
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
268
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
269
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
270
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
271
self.assertEqual(file(mfile2_path).read(), 'later2')
272
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
273
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
274
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
275
self.assertEqual(file(newfile_path).read(), 'hello3')
276
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
277
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
278
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
280
def test_both_rename(self):
281
create_tree,root = self.get_transform()
282
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
283
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
285
mangle_tree,root = self.get_transform()
286
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
287
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
288
mangle_tree.adjust_path('test', root, selftest)
289
mangle_tree.adjust_path('test_too_much', root, selftest)
290
mangle_tree.set_executability(True, blackbox)
293
def test_both_rename2(self):
294
create_tree,root = self.get_transform()
295
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
296
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
297
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
298
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
301
mangle_tree,root = self.get_transform()
302
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
303
tests = mangle_tree.trans_id_tree_file_id('tests-id')
304
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
305
mangle_tree.adjust_path('selftest', bzrlib, tests)
306
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
307
mangle_tree.set_executability(True, test_too_much)
310
def test_both_rename3(self):
311
create_tree,root = self.get_transform()
312
tests = create_tree.new_directory('tests', root, 'tests-id')
313
create_tree.new_file('test_too_much.py', tests, 'hello1',
316
mangle_tree,root = self.get_transform()
317
tests = mangle_tree.trans_id_tree_file_id('tests-id')
318
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
319
mangle_tree.adjust_path('selftest', root, tests)
320
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
321
mangle_tree.set_executability(True, test_too_much)
324
def test_move_dangling_ie(self):
325
create_tree, root = self.get_transform()
327
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
328
create_tree.new_file('name1', root, 'hello1', 'name1')
330
delete_contents, root = self.get_transform()
331
file = delete_contents.trans_id_tree_file_id('name1')
332
delete_contents.delete_contents(file)
333
delete_contents.apply()
334
move_id, root = self.get_transform()
335
name1 = move_id.trans_id_tree_file_id('name1')
336
newdir = move_id.new_directory('dir', root, 'newdir')
337
move_id.adjust_path('name2', newdir, name1)
340
def test_replace_dangling_ie(self):
341
create_tree, root = self.get_transform()
343
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
344
create_tree.new_file('name1', root, 'hello1', 'name1')
346
delete_contents = TreeTransform(self.wt)
347
self.addCleanup(delete_contents.finalize)
348
file = delete_contents.trans_id_tree_file_id('name1')
349
delete_contents.delete_contents(file)
350
delete_contents.apply()
351
delete_contents.finalize()
352
replace = TreeTransform(self.wt)
353
self.addCleanup(replace.finalize)
354
name2 = replace.new_file('name2', root, 'hello2', 'name1')
355
conflicts = replace.find_conflicts()
356
name1 = replace.trans_id_tree_file_id('name1')
357
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
358
resolve_conflicts(replace)
361
def test_symlinks(self):
362
if not has_symlinks():
363
raise TestSkipped('Symlinks are not supported on this platform')
364
transform,root = self.get_transform()
365
oz_id = transform.new_directory('oz', root, 'oz-id')
366
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
368
wiz_id = transform.create_path('wizard2', oz_id)
369
transform.create_symlink('behind_curtain', wiz_id)
370
transform.version_file('wiz-id2', wiz_id)
371
transform.set_executability(True, wiz_id)
372
self.assertEqual(transform.find_conflicts(),
373
[('non-file executability', wiz_id)])
374
transform.set_executability(None, wiz_id)
376
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
377
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
378
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
380
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
384
def get_conflicted(self):
385
create,root = self.get_transform()
386
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
387
oz = create.new_directory('oz', root, 'oz-id')
388
create.new_directory('emeraldcity', oz, 'emerald-id')
390
conflicts,root = self.get_transform()
391
# set up duplicate entry, duplicate id
392
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
394
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
395
oz = conflicts.trans_id_tree_file_id('oz-id')
396
# set up DeletedParent parent conflict
397
conflicts.delete_versioned(oz)
398
emerald = conflicts.trans_id_tree_file_id('emerald-id')
399
# set up MissingParent conflict
400
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
401
conflicts.adjust_path('munchkincity', root, munchkincity)
402
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
404
conflicts.adjust_path('emeraldcity', emerald, emerald)
405
return conflicts, emerald, oz, old_dorothy, new_dorothy
407
def test_conflict_resolution(self):
408
conflicts, emerald, oz, old_dorothy, new_dorothy =\
409
self.get_conflicted()
410
resolve_conflicts(conflicts)
411
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
412
self.assertIs(conflicts.final_file_id(old_dorothy), None)
413
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
414
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
415
self.assertEqual(conflicts.final_parent(emerald), oz)
418
def test_cook_conflicts(self):
419
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
420
raw_conflicts = resolve_conflicts(tt)
421
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
422
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
423
'dorothy', None, 'dorothy-id')
424
self.assertEqual(cooked_conflicts[0], duplicate)
425
duplicate_id = DuplicateID('Unversioned existing file',
426
'dorothy.moved', 'dorothy', None,
428
self.assertEqual(cooked_conflicts[1], duplicate_id)
429
missing_parent = MissingParent('Created directory', 'munchkincity',
431
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
432
self.assertEqual(cooked_conflicts[2], missing_parent)
433
unversioned_parent = UnversionedParent('Versioned directory',
436
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
438
self.assertEqual(cooked_conflicts[3], unversioned_parent)
439
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
440
'oz/emeraldcity', 'emerald-id', 'emerald-id')
441
self.assertEqual(cooked_conflicts[4], deleted_parent)
442
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
443
self.assertEqual(cooked_conflicts[6], parent_loop)
444
self.assertEqual(len(cooked_conflicts), 7)
447
def test_string_conflicts(self):
448
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
449
raw_conflicts = resolve_conflicts(tt)
450
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
452
conflicts_s = [str(c) for c in cooked_conflicts]
453
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
454
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
455
'Moved existing file to '
457
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
458
'Unversioned existing file '
460
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
461
' munchkincity. Created directory.')
462
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
463
' versioned, but has versioned'
464
' children. Versioned directory.')
465
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
466
" is not empty. Not deleting.")
467
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
468
' versioned, but has versioned'
469
' children. Versioned directory.')
470
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
471
' oz/emeraldcity. Cancelled move.')
473
def test_moving_versioned_directories(self):
474
create, root = self.get_transform()
475
kansas = create.new_directory('kansas', root, 'kansas-id')
476
create.new_directory('house', kansas, 'house-id')
477
create.new_directory('oz', root, 'oz-id')
479
cyclone, root = self.get_transform()
480
oz = cyclone.trans_id_tree_file_id('oz-id')
481
house = cyclone.trans_id_tree_file_id('house-id')
482
cyclone.adjust_path('house', oz, house)
485
def test_moving_root(self):
486
create, root = self.get_transform()
487
fun = create.new_directory('fun', root, 'fun-id')
488
create.new_directory('sun', root, 'sun-id')
489
create.new_directory('moon', root, 'moon')
491
transform, root = self.get_transform()
492
transform.adjust_root_path('oldroot', fun)
493
new_root=transform.trans_id_tree_path('')
494
transform.version_file('new-root', new_root)
497
def test_renames(self):
498
create, root = self.get_transform()
499
old = create.new_directory('old-parent', root, 'old-id')
500
intermediate = create.new_directory('intermediate', old, 'im-id')
501
myfile = create.new_file('myfile', intermediate, 'myfile-text',
504
rename, root = self.get_transform()
505
old = rename.trans_id_file_id('old-id')
506
rename.adjust_path('new', root, old)
507
myfile = rename.trans_id_file_id('myfile-id')
508
rename.set_executability(True, myfile)
511
def test_find_interesting(self):
512
create, root = self.get_transform()
514
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
515
create.new_file('uvfile', root, 'othertext')
517
self.assertEqual(find_interesting(wt, wt, ['vfile']),
519
self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
522
def test_set_executability_order(self):
523
"""Ensure that executability behaves the same, no matter what order.
525
- create file and set executability simultaneously
526
- create file and set executability afterward
527
- unsetting the executability of a file whose executability has not been
528
declared should throw an exception (this may happen when a
529
merge attempts to create a file with a duplicate ID)
531
transform, root = self.get_transform()
533
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
535
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
536
transform.set_executability(True, sac)
537
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
538
self.assertRaises(KeyError, transform.set_executability, None, uws)
540
self.assertTrue(wt.is_executable('soc'))
541
self.assertTrue(wt.is_executable('sac'))
543
def test__set_mode_stats_correctly(self):
544
"""_set_mode stats to determine file mode."""
545
if sys.platform == 'win32':
546
raise TestSkipped('chmod has no effect on win32')
550
def instrumented_stat(path):
551
stat_paths.append(path)
552
return real_stat(path)
554
transform, root = self.get_transform()
556
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
557
file_id='bar-id-1', executable=False)
560
transform, root = self.get_transform()
561
bar1_id = transform.trans_id_tree_path('bar')
562
bar2_id = transform.trans_id_tree_path('bar2')
564
os.stat = instrumented_stat
565
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
570
bar1_abspath = self.wt.abspath('bar')
571
self.assertEqual([bar1_abspath], stat_paths)
574
class TransformGroup(object):
575
def __init__(self, dirname):
578
self.wt = BzrDir.create_standalone_workingtree(dirname)
579
self.b = self.wt.branch
580
self.tt = TreeTransform(self.wt)
581
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
583
def conflict_text(tree, merge):
584
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
585
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
588
class TestTransformMerge(TestCaseInTempDir):
589
def test_text_merge(self):
590
base = TransformGroup("base")
591
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
592
base.tt.new_file('b', base.root, 'b1', 'b')
593
base.tt.new_file('c', base.root, 'c', 'c')
594
base.tt.new_file('d', base.root, 'd', 'd')
595
base.tt.new_file('e', base.root, 'e', 'e')
596
base.tt.new_file('f', base.root, 'f', 'f')
597
base.tt.new_directory('g', base.root, 'g')
598
base.tt.new_directory('h', base.root, 'h')
600
other = TransformGroup("other")
601
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
602
other.tt.new_file('b', other.root, 'b2', 'b')
603
other.tt.new_file('c', other.root, 'c2', 'c')
604
other.tt.new_file('d', other.root, 'd', 'd')
605
other.tt.new_file('e', other.root, 'e2', 'e')
606
other.tt.new_file('f', other.root, 'f', 'f')
607
other.tt.new_file('g', other.root, 'g', 'g')
608
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
609
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
611
this = TransformGroup("this")
612
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
613
this.tt.new_file('b', this.root, 'b', 'b')
614
this.tt.new_file('c', this.root, 'c', 'c')
615
this.tt.new_file('d', this.root, 'd2', 'd')
616
this.tt.new_file('e', this.root, 'e2', 'e')
617
this.tt.new_file('f', this.root, 'f', 'f')
618
this.tt.new_file('g', this.root, 'g', 'g')
619
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
620
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
622
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
624
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
625
# three-way text conflict
626
self.assertEqual(this.wt.get_file('b').read(),
627
conflict_text('b', 'b2'))
629
self.assertEqual(this.wt.get_file('c').read(), 'c2')
631
self.assertEqual(this.wt.get_file('d').read(), 'd2')
632
# Ambigious clean merge
633
self.assertEqual(this.wt.get_file('e').read(), 'e2')
635
self.assertEqual(this.wt.get_file('f').read(), 'f')
636
# Correct correct results when THIS == OTHER
637
self.assertEqual(this.wt.get_file('g').read(), 'g')
638
# Text conflict when THIS & OTHER are text and BASE is dir
639
self.assertEqual(this.wt.get_file('h').read(),
640
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
641
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
643
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
645
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
646
self.assertEqual(this.wt.get_file('i').read(),
647
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
648
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
650
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
652
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
653
modified = ['a', 'b', 'c', 'h', 'i']
654
merge_modified = this.wt.merge_modified()
655
self.assertSubset(merge_modified, modified)
656
self.assertEqual(len(merge_modified), len(modified))
657
file(this.wt.id2abspath('a'), 'wb').write('booga')
659
merge_modified = this.wt.merge_modified()
660
self.assertSubset(merge_modified, modified)
661
self.assertEqual(len(merge_modified), len(modified))
665
def test_file_merge(self):
666
if not has_symlinks():
667
raise TestSkipped('Symlinks are not supported on this platform')
668
base = TransformGroup("BASE")
669
this = TransformGroup("THIS")
670
other = TransformGroup("OTHER")
671
for tg in this, base, other:
672
tg.tt.new_directory('a', tg.root, 'a')
673
tg.tt.new_symlink('b', tg.root, 'b', 'b')
674
tg.tt.new_file('c', tg.root, 'c', 'c')
675
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
676
targets = ((base, 'base-e', 'base-f', None, None),
677
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
678
(other, 'other-e', None, 'other-g', 'other-h'))
679
for tg, e_target, f_target, g_target, h_target in targets:
680
for link, target in (('e', e_target), ('f', f_target),
681
('g', g_target), ('h', h_target)):
682
if target is not None:
683
tg.tt.new_symlink(link, tg.root, target, link)
685
for tg in this, base, other:
687
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
688
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
689
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
690
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
691
for suffix in ('THIS', 'BASE', 'OTHER'):
692
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
693
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
694
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
695
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
696
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
697
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
698
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
699
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
700
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
701
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
702
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
703
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
704
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
705
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
707
def test_filename_merge(self):
708
base = TransformGroup("BASE")
709
this = TransformGroup("THIS")
710
other = TransformGroup("OTHER")
711
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
712
for t in [base, this, other]]
713
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
714
for t in [base, this, other]]
715
base.tt.new_directory('c', base_a, 'c')
716
this.tt.new_directory('c1', this_a, 'c')
717
other.tt.new_directory('c', other_b, 'c')
719
base.tt.new_directory('d', base_a, 'd')
720
this.tt.new_directory('d1', this_b, 'd')
721
other.tt.new_directory('d', other_a, 'd')
723
base.tt.new_directory('e', base_a, 'e')
724
this.tt.new_directory('e', this_a, 'e')
725
other.tt.new_directory('e1', other_b, 'e')
727
base.tt.new_directory('f', base_a, 'f')
728
this.tt.new_directory('f1', this_b, 'f')
729
other.tt.new_directory('f1', other_b, 'f')
731
for tg in [this, base, other]:
733
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
734
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
735
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
736
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
737
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
739
def test_filename_merge_conflicts(self):
740
base = TransformGroup("BASE")
741
this = TransformGroup("THIS")
742
other = TransformGroup("OTHER")
743
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
744
for t in [base, this, other]]
745
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
746
for t in [base, this, other]]
748
base.tt.new_file('g', base_a, 'g', 'g')
749
other.tt.new_file('g1', other_b, 'g1', 'g')
751
base.tt.new_file('h', base_a, 'h', 'h')
752
this.tt.new_file('h1', this_b, 'h1', 'h')
754
base.tt.new_file('i', base.root, 'i', 'i')
755
other.tt.new_directory('i1', this_b, 'i')
757
for tg in [this, base, other]:
759
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
761
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
762
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
763
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
764
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
765
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
766
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
767
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
770
class TestBuildTree(tests.TestCaseWithTransport):
772
def test_build_tree(self):
773
if not has_symlinks():
774
raise TestSkipped('Test requires symlink support')
776
a = BzrDir.create_standalone_workingtree('a')
778
file('a/foo/bar', 'wb').write('contents')
779
os.symlink('a/foo/bar', 'a/foo/baz')
780
a.add(['foo', 'foo/bar', 'foo/baz'])
781
a.commit('initial commit')
782
b = BzrDir.create_standalone_workingtree('b')
783
build_tree(a.basis_tree(), b)
784
self.assertIs(os.path.isdir('b/foo'), True)
785
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
786
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
788
def test_file_conflict_handling(self):
789
"""Ensure that when building trees, conflict handling is done"""
790
source = self.make_branch_and_tree('source')
791
target = self.make_branch_and_tree('target')
792
self.build_tree(['source/file', 'target/file'])
793
source.add('file', 'new-file')
794
source.commit('added file')
795
build_tree(source.basis_tree(), target)
796
self.assertEqual([DuplicateEntry('Moved existing file to',
797
'file.moved', 'file', None, 'new-file')],
799
target2 = self.make_branch_and_tree('target2')
800
target_file = file('target2/file', 'wb')
802
source_file = file('source/file', 'rb')
804
target_file.write(source_file.read())
809
build_tree(source.basis_tree(), target2)
810
self.assertEqual([], target2.conflicts())
812
def test_symlink_conflict_handling(self):
813
"""Ensure that when building trees, conflict handling is done"""
814
if not has_symlinks():
815
raise TestSkipped('Test requires symlink support')
816
source = self.make_branch_and_tree('source')
817
os.symlink('foo', 'source/symlink')
818
source.add('symlink', 'new-symlink')
819
source.commit('added file')
820
target = self.make_branch_and_tree('target')
821
os.symlink('bar', 'target/symlink')
822
build_tree(source.basis_tree(), target)
823
self.assertEqual([DuplicateEntry('Moved existing file to',
824
'symlink.moved', 'symlink', None, 'new-symlink')],
826
target = self.make_branch_and_tree('target2')
827
os.symlink('foo', 'target2/symlink')
828
build_tree(source.basis_tree(), target)
829
self.assertEqual([], target.conflicts())
831
def test_directory_conflict_handling(self):
832
"""Ensure that when building trees, conflict handling is done"""
833
source = self.make_branch_and_tree('source')
834
target = self.make_branch_and_tree('target')
835
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
836
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
837
source.commit('added file')
838
build_tree(source.basis_tree(), target)
839
self.assertEqual([], target.conflicts())
840
self.failUnlessExists('target/dir1/file')
842
# Ensure contents are merged
843
target = self.make_branch_and_tree('target2')
844
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
845
build_tree(source.basis_tree(), target)
846
self.assertEqual([], target.conflicts())
847
self.failUnlessExists('target2/dir1/file2')
848
self.failUnlessExists('target2/dir1/file')
850
# Ensure new contents are suppressed for existing branches
851
target = self.make_branch_and_tree('target3')
852
self.make_branch('target3/dir1')
853
self.build_tree(['target3/dir1/file2'])
854
build_tree(source.basis_tree(), target)
855
self.failIfExists('target3/dir1/file')
856
self.failUnlessExists('target3/dir1/file2')
857
self.failUnlessExists('target3/dir1.diverted/file')
858
self.assertEqual([DuplicateEntry('Diverted to',
859
'dir1.diverted', 'dir1', 'new-dir1', None)],
862
target = self.make_branch_and_tree('target4')
863
self.build_tree(['target4/dir1/'])
864
self.make_branch('target4/dir1/file')
865
build_tree(source.basis_tree(), target)
866
self.failUnlessExists('target4/dir1/file')
867
self.assertEqual('directory', file_kind('target4/dir1/file'))
868
self.failUnlessExists('target4/dir1/file.diverted')
869
self.assertEqual([DuplicateEntry('Diverted to',
870
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
873
def test_mixed_conflict_handling(self):
874
"""Ensure that when building trees, conflict handling is done"""
875
source = self.make_branch_and_tree('source')
876
target = self.make_branch_and_tree('target')
877
self.build_tree(['source/name', 'target/name/'])
878
source.add('name', 'new-name')
879
source.commit('added file')
880
build_tree(source.basis_tree(), target)
881
self.assertEqual([DuplicateEntry('Moved existing file to',
882
'name.moved', 'name', None, 'new-name')], target.conflicts())
884
def test_raises_in_populated(self):
885
source = self.make_branch_and_tree('source')
886
self.build_tree(['source/name'])
888
source.commit('added name')
889
target = self.make_branch_and_tree('target')
890
self.build_tree(['target/name'])
892
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
896
class MockTransform(object):
898
def has_named_child(self, by_parent, parent_id, name):
899
for child_id in by_parent[parent_id]:
903
elif name == "name.~%s~" % child_id:
907
class MockEntry(object):
909
object.__init__(self)
912
class TestGetBackupName(TestCase):
913
def test_get_backup_name(self):
915
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
916
self.assertEqual(name, 'name.~1~')
917
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
918
self.assertEqual(name, 'name.~2~')
919
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
920
self.assertEqual(name, 'name.~1~')
921
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
922
self.assertEqual(name, 'name.~1~')
923
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
924
self.assertEqual(name, 'name.~4~')