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_preserve_mode(self):
544
"""File mode is preserved when replacing content"""
545
if sys.platform == 'win32':
546
raise TestSkipped('chmod has no effect on win32')
547
transform, root = self.get_transform()
548
transform.new_file('file1', root, 'contents', 'file1-id', True)
550
self.assertTrue(self.wt.is_executable('file1-id'))
551
transform, root = self.get_transform()
552
file1_id = transform.trans_id_tree_file_id('file1-id')
553
transform.delete_contents(file1_id)
554
transform.create_file('contents2', file1_id)
556
self.assertTrue(self.wt.is_executable('file1-id'))
558
def test__set_mode_stats_correctly(self):
559
"""_set_mode stats to determine file mode."""
560
if sys.platform == 'win32':
561
raise TestSkipped('chmod has no effect on win32')
565
def instrumented_stat(path):
566
stat_paths.append(path)
567
return real_stat(path)
569
transform, root = self.get_transform()
571
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
572
file_id='bar-id-1', executable=False)
575
transform, root = self.get_transform()
576
bar1_id = transform.trans_id_tree_path('bar')
577
bar2_id = transform.trans_id_tree_path('bar2')
579
os.stat = instrumented_stat
580
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
585
bar1_abspath = self.wt.abspath('bar')
586
self.assertEqual([bar1_abspath], stat_paths)
589
class TransformGroup(object):
590
def __init__(self, dirname):
593
self.wt = BzrDir.create_standalone_workingtree(dirname)
594
self.b = self.wt.branch
595
self.tt = TreeTransform(self.wt)
596
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
598
def conflict_text(tree, merge):
599
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
600
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
603
class TestTransformMerge(TestCaseInTempDir):
604
def test_text_merge(self):
605
base = TransformGroup("base")
606
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
607
base.tt.new_file('b', base.root, 'b1', 'b')
608
base.tt.new_file('c', base.root, 'c', 'c')
609
base.tt.new_file('d', base.root, 'd', 'd')
610
base.tt.new_file('e', base.root, 'e', 'e')
611
base.tt.new_file('f', base.root, 'f', 'f')
612
base.tt.new_directory('g', base.root, 'g')
613
base.tt.new_directory('h', base.root, 'h')
615
other = TransformGroup("other")
616
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
617
other.tt.new_file('b', other.root, 'b2', 'b')
618
other.tt.new_file('c', other.root, 'c2', 'c')
619
other.tt.new_file('d', other.root, 'd', 'd')
620
other.tt.new_file('e', other.root, 'e2', 'e')
621
other.tt.new_file('f', other.root, 'f', 'f')
622
other.tt.new_file('g', other.root, 'g', 'g')
623
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
624
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
626
this = TransformGroup("this")
627
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
628
this.tt.new_file('b', this.root, 'b', 'b')
629
this.tt.new_file('c', this.root, 'c', 'c')
630
this.tt.new_file('d', this.root, 'd2', 'd')
631
this.tt.new_file('e', this.root, 'e2', 'e')
632
this.tt.new_file('f', this.root, 'f', 'f')
633
this.tt.new_file('g', this.root, 'g', 'g')
634
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
635
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
637
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
639
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
640
# three-way text conflict
641
self.assertEqual(this.wt.get_file('b').read(),
642
conflict_text('b', 'b2'))
644
self.assertEqual(this.wt.get_file('c').read(), 'c2')
646
self.assertEqual(this.wt.get_file('d').read(), 'd2')
647
# Ambigious clean merge
648
self.assertEqual(this.wt.get_file('e').read(), 'e2')
650
self.assertEqual(this.wt.get_file('f').read(), 'f')
651
# Correct correct results when THIS == OTHER
652
self.assertEqual(this.wt.get_file('g').read(), 'g')
653
# Text conflict when THIS & OTHER are text and BASE is dir
654
self.assertEqual(this.wt.get_file('h').read(),
655
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
656
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
658
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
660
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
661
self.assertEqual(this.wt.get_file('i').read(),
662
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
663
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
665
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
667
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
668
modified = ['a', 'b', 'c', 'h', 'i']
669
merge_modified = this.wt.merge_modified()
670
self.assertSubset(merge_modified, modified)
671
self.assertEqual(len(merge_modified), len(modified))
672
file(this.wt.id2abspath('a'), 'wb').write('booga')
674
merge_modified = this.wt.merge_modified()
675
self.assertSubset(merge_modified, modified)
676
self.assertEqual(len(merge_modified), len(modified))
680
def test_file_merge(self):
681
if not has_symlinks():
682
raise TestSkipped('Symlinks are not supported on this platform')
683
base = TransformGroup("BASE")
684
this = TransformGroup("THIS")
685
other = TransformGroup("OTHER")
686
for tg in this, base, other:
687
tg.tt.new_directory('a', tg.root, 'a')
688
tg.tt.new_symlink('b', tg.root, 'b', 'b')
689
tg.tt.new_file('c', tg.root, 'c', 'c')
690
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
691
targets = ((base, 'base-e', 'base-f', None, None),
692
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
693
(other, 'other-e', None, 'other-g', 'other-h'))
694
for tg, e_target, f_target, g_target, h_target in targets:
695
for link, target in (('e', e_target), ('f', f_target),
696
('g', g_target), ('h', h_target)):
697
if target is not None:
698
tg.tt.new_symlink(link, tg.root, target, link)
700
for tg in this, base, other:
702
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
703
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
704
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
705
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
706
for suffix in ('THIS', 'BASE', 'OTHER'):
707
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
708
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
709
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
710
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
711
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
712
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
713
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
714
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
715
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
716
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
717
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
718
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
719
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
720
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
722
def test_filename_merge(self):
723
base = TransformGroup("BASE")
724
this = TransformGroup("THIS")
725
other = TransformGroup("OTHER")
726
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
727
for t in [base, this, other]]
728
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
729
for t in [base, this, other]]
730
base.tt.new_directory('c', base_a, 'c')
731
this.tt.new_directory('c1', this_a, 'c')
732
other.tt.new_directory('c', other_b, 'c')
734
base.tt.new_directory('d', base_a, 'd')
735
this.tt.new_directory('d1', this_b, 'd')
736
other.tt.new_directory('d', other_a, 'd')
738
base.tt.new_directory('e', base_a, 'e')
739
this.tt.new_directory('e', this_a, 'e')
740
other.tt.new_directory('e1', other_b, 'e')
742
base.tt.new_directory('f', base_a, 'f')
743
this.tt.new_directory('f1', this_b, 'f')
744
other.tt.new_directory('f1', other_b, 'f')
746
for tg in [this, base, other]:
748
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
749
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
750
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
751
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
752
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
754
def test_filename_merge_conflicts(self):
755
base = TransformGroup("BASE")
756
this = TransformGroup("THIS")
757
other = TransformGroup("OTHER")
758
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
759
for t in [base, this, other]]
760
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
761
for t in [base, this, other]]
763
base.tt.new_file('g', base_a, 'g', 'g')
764
other.tt.new_file('g1', other_b, 'g1', 'g')
766
base.tt.new_file('h', base_a, 'h', 'h')
767
this.tt.new_file('h1', this_b, 'h1', 'h')
769
base.tt.new_file('i', base.root, 'i', 'i')
770
other.tt.new_directory('i1', this_b, 'i')
772
for tg in [this, base, other]:
774
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
776
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
777
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
778
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
779
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
780
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
781
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
782
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
785
class TestBuildTree(tests.TestCaseWithTransport):
787
def test_build_tree(self):
788
if not has_symlinks():
789
raise TestSkipped('Test requires symlink support')
791
a = BzrDir.create_standalone_workingtree('a')
793
file('a/foo/bar', 'wb').write('contents')
794
os.symlink('a/foo/bar', 'a/foo/baz')
795
a.add(['foo', 'foo/bar', 'foo/baz'])
796
a.commit('initial commit')
797
b = BzrDir.create_standalone_workingtree('b')
798
build_tree(a.basis_tree(), b)
799
self.assertIs(os.path.isdir('b/foo'), True)
800
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
801
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
803
def test_file_conflict_handling(self):
804
"""Ensure that when building trees, conflict handling is done"""
805
source = self.make_branch_and_tree('source')
806
target = self.make_branch_and_tree('target')
807
self.build_tree(['source/file', 'target/file'])
808
source.add('file', 'new-file')
809
source.commit('added file')
810
build_tree(source.basis_tree(), target)
811
self.assertEqual([DuplicateEntry('Moved existing file to',
812
'file.moved', 'file', None, 'new-file')],
814
target2 = self.make_branch_and_tree('target2')
815
target_file = file('target2/file', 'wb')
817
source_file = file('source/file', 'rb')
819
target_file.write(source_file.read())
824
build_tree(source.basis_tree(), target2)
825
self.assertEqual([], target2.conflicts())
827
def test_symlink_conflict_handling(self):
828
"""Ensure that when building trees, conflict handling is done"""
829
if not has_symlinks():
830
raise TestSkipped('Test requires symlink support')
831
source = self.make_branch_and_tree('source')
832
os.symlink('foo', 'source/symlink')
833
source.add('symlink', 'new-symlink')
834
source.commit('added file')
835
target = self.make_branch_and_tree('target')
836
os.symlink('bar', 'target/symlink')
837
build_tree(source.basis_tree(), target)
838
self.assertEqual([DuplicateEntry('Moved existing file to',
839
'symlink.moved', 'symlink', None, 'new-symlink')],
841
target = self.make_branch_and_tree('target2')
842
os.symlink('foo', 'target2/symlink')
843
build_tree(source.basis_tree(), target)
844
self.assertEqual([], target.conflicts())
846
def test_directory_conflict_handling(self):
847
"""Ensure that when building trees, conflict handling is done"""
848
source = self.make_branch_and_tree('source')
849
target = self.make_branch_and_tree('target')
850
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
851
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
852
source.commit('added file')
853
build_tree(source.basis_tree(), target)
854
self.assertEqual([], target.conflicts())
855
self.failUnlessExists('target/dir1/file')
857
# Ensure contents are merged
858
target = self.make_branch_and_tree('target2')
859
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
860
build_tree(source.basis_tree(), target)
861
self.assertEqual([], target.conflicts())
862
self.failUnlessExists('target2/dir1/file2')
863
self.failUnlessExists('target2/dir1/file')
865
# Ensure new contents are suppressed for existing branches
866
target = self.make_branch_and_tree('target3')
867
self.make_branch('target3/dir1')
868
self.build_tree(['target3/dir1/file2'])
869
build_tree(source.basis_tree(), target)
870
self.failIfExists('target3/dir1/file')
871
self.failUnlessExists('target3/dir1/file2')
872
self.failUnlessExists('target3/dir1.diverted/file')
873
self.assertEqual([DuplicateEntry('Diverted to',
874
'dir1.diverted', 'dir1', 'new-dir1', None)],
877
target = self.make_branch_and_tree('target4')
878
self.build_tree(['target4/dir1/'])
879
self.make_branch('target4/dir1/file')
880
build_tree(source.basis_tree(), target)
881
self.failUnlessExists('target4/dir1/file')
882
self.assertEqual('directory', file_kind('target4/dir1/file'))
883
self.failUnlessExists('target4/dir1/file.diverted')
884
self.assertEqual([DuplicateEntry('Diverted to',
885
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
888
def test_mixed_conflict_handling(self):
889
"""Ensure that when building trees, conflict handling is done"""
890
source = self.make_branch_and_tree('source')
891
target = self.make_branch_and_tree('target')
892
self.build_tree(['source/name', 'target/name/'])
893
source.add('name', 'new-name')
894
source.commit('added file')
895
build_tree(source.basis_tree(), target)
896
self.assertEqual([DuplicateEntry('Moved existing file to',
897
'name.moved', 'name', None, 'new-name')], target.conflicts())
899
def test_raises_in_populated(self):
900
source = self.make_branch_and_tree('source')
901
self.build_tree(['source/name'])
903
source.commit('added name')
904
target = self.make_branch_and_tree('target')
905
self.build_tree(['target/name'])
907
self.assertRaises(AssertionError, build_tree, source.basis_tree(),
911
class MockTransform(object):
913
def has_named_child(self, by_parent, parent_id, name):
914
for child_id in by_parent[parent_id]:
918
elif name == "name.~%s~" % child_id:
922
class MockEntry(object):
924
object.__init__(self)
927
class TestGetBackupName(TestCase):
928
def test_get_backup_name(self):
930
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
931
self.assertEqual(name, 'name.~1~')
932
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
933
self.assertEqual(name, 'name.~2~')
934
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
935
self.assertEqual(name, 'name.~1~')
936
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
937
self.assertEqual(name, 'name.~1~')
938
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
939
self.assertEqual(name, 'name.~4~')