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
28
from bzrlib.bzrdir import BzrDir
29
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
UnversionedParent, ParentLoop, DeletingParent,)
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
ReusingTransform, CantMoveRoot,
33
PathsNotVersionedError, ExistingLimbo,
34
ExistingPendingDeletion, ImmortalLimbo,
35
ImmortalPendingDeletion, LockError)
36
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
37
from bzrlib.merge import Merge3Merger
38
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
39
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
40
resolve_conflicts, cook_conflicts,
41
find_interesting, build_tree, get_backup_name,
42
change_entry, _FileMover)
45
class TestTreeTransform(tests.TestCaseWithTransport):
48
super(TestTreeTransform, self).setUp()
49
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
52
def get_transform(self):
53
transform = TreeTransform(self.wt)
54
#self.addCleanup(transform.finalize)
55
return transform, transform.root
57
def test_existing_limbo(self):
58
transform, root = self.get_transform()
59
limbo_name = transform._limbodir
60
deletion_path = transform._deletiondir
61
os.mkdir(pathjoin(limbo_name, 'hehe'))
62
self.assertRaises(ImmortalLimbo, transform.apply)
63
self.assertRaises(LockError, self.wt.unlock)
64
self.assertRaises(ExistingLimbo, self.get_transform)
65
self.assertRaises(LockError, self.wt.unlock)
66
os.rmdir(pathjoin(limbo_name, 'hehe'))
68
os.rmdir(deletion_path)
69
transform, root = self.get_transform()
72
def test_existing_pending_deletion(self):
73
transform, root = self.get_transform()
74
deletion_path = self._limbodir = urlutils.local_path_from_url(
75
transform._tree._control_files.controlfilename('pending-deletion'))
76
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
77
self.assertRaises(ImmortalPendingDeletion, transform.apply)
78
self.assertRaises(LockError, self.wt.unlock)
79
self.assertRaises(ExistingPendingDeletion, self.get_transform)
82
transform, root = self.get_transform()
83
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
84
imaginary_id = transform.trans_id_tree_path('imaginary')
85
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
86
self.assertEqual(imaginary_id, imaginary_id2)
87
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
88
self.assertEqual(transform.final_kind(root), 'directory')
89
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
90
trans_id = transform.create_path('name', root)
91
self.assertIs(transform.final_file_id(trans_id), None)
92
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
93
transform.create_file('contents', trans_id)
94
transform.set_executability(True, trans_id)
95
transform.version_file('my_pretties', trans_id)
96
self.assertRaises(DuplicateKey, transform.version_file,
97
'my_pretties', trans_id)
98
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
99
self.assertEqual(transform.final_parent(trans_id), root)
100
self.assertIs(transform.final_parent(root), ROOT_PARENT)
101
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
102
oz_id = transform.create_path('oz', root)
103
transform.create_directory(oz_id)
104
transform.version_file('ozzie', oz_id)
105
trans_id2 = transform.create_path('name2', root)
106
transform.create_file('contents', trans_id2)
107
transform.set_executability(False, trans_id2)
108
transform.version_file('my_pretties2', trans_id2)
109
modified_paths = transform.apply().modified_paths
110
self.assertEqual('contents', self.wt.get_file_byname('name').read())
111
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
112
self.assertIs(self.wt.is_executable('my_pretties'), True)
113
self.assertIs(self.wt.is_executable('my_pretties2'), False)
114
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
115
self.assertEqual(len(modified_paths), 3)
116
tree_mod_paths = [self.wt.id2abspath(f) for f in
117
('ozzie', 'my_pretties', 'my_pretties2')]
118
self.assertSubset(tree_mod_paths, modified_paths)
119
# is it safe to finalize repeatedly?
123
def test_convenience(self):
124
transform, root = self.get_transform()
125
trans_id = transform.new_file('name', root, 'contents',
127
oz = transform.new_directory('oz', root, 'oz-id')
128
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
129
toto = transform.new_file('toto', dorothy, 'toto-contents',
132
self.assertEqual(len(transform.find_conflicts()), 0)
134
self.assertRaises(ReusingTransform, transform.find_conflicts)
135
self.assertEqual('contents', file(self.wt.abspath('name')).read())
136
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
137
self.assertIs(self.wt.is_executable('my_pretties'), True)
138
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
139
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
140
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
142
self.assertEqual('toto-contents',
143
self.wt.get_file_byname('oz/dorothy/toto').read())
144
self.assertIs(self.wt.is_executable('toto-id'), False)
146
def test_tree_reference(self):
147
transform, root = self.get_transform()
148
tree = transform._tree
149
trans_id = transform.new_directory('reference', root, 'subtree-id')
150
transform.set_tree_reference('subtree-revision', trans_id)
153
self.addCleanup(tree.unlock)
154
self.assertEqual('subtree-revision',
155
tree.inventory['subtree-id'].reference_revision)
157
def test_conflicts(self):
158
transform, root = self.get_transform()
159
trans_id = transform.new_file('name', root, 'contents',
161
self.assertEqual(len(transform.find_conflicts()), 0)
162
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
163
self.assertEqual(transform.find_conflicts(),
164
[('duplicate', trans_id, trans_id2, 'name')])
165
self.assertRaises(MalformedTransform, transform.apply)
166
transform.adjust_path('name', trans_id, trans_id2)
167
self.assertEqual(transform.find_conflicts(),
168
[('non-directory parent', trans_id)])
169
tinman_id = transform.trans_id_tree_path('tinman')
170
transform.adjust_path('name', tinman_id, trans_id2)
171
self.assertEqual(transform.find_conflicts(),
172
[('unversioned parent', tinman_id),
173
('missing parent', tinman_id)])
174
lion_id = transform.create_path('lion', root)
175
self.assertEqual(transform.find_conflicts(),
176
[('unversioned parent', tinman_id),
177
('missing parent', tinman_id)])
178
transform.adjust_path('name', lion_id, trans_id2)
179
self.assertEqual(transform.find_conflicts(),
180
[('unversioned parent', lion_id),
181
('missing parent', lion_id)])
182
transform.version_file("Courage", lion_id)
183
self.assertEqual(transform.find_conflicts(),
184
[('missing parent', lion_id),
185
('versioning no contents', lion_id)])
186
transform.adjust_path('name2', root, trans_id2)
187
self.assertEqual(transform.find_conflicts(),
188
[('versioning no contents', lion_id)])
189
transform.create_file('Contents, okay?', lion_id)
190
transform.adjust_path('name2', trans_id2, trans_id2)
191
self.assertEqual(transform.find_conflicts(),
192
[('parent loop', trans_id2),
193
('non-directory parent', trans_id2)])
194
transform.adjust_path('name2', root, trans_id2)
195
oz_id = transform.new_directory('oz', root)
196
transform.set_executability(True, oz_id)
197
self.assertEqual(transform.find_conflicts(),
198
[('unversioned executability', oz_id)])
199
transform.version_file('oz-id', oz_id)
200
self.assertEqual(transform.find_conflicts(),
201
[('non-file executability', oz_id)])
202
transform.set_executability(None, oz_id)
203
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
205
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
206
self.assertEqual('contents', file(self.wt.abspath('name')).read())
207
transform2, root = self.get_transform()
208
oz_id = transform2.trans_id_tree_file_id('oz-id')
209
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
210
result = transform2.find_conflicts()
211
fp = FinalPaths(transform2)
212
self.assert_('oz/tip' in transform2._tree_path_ids)
213
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
214
self.assertEqual(len(result), 2)
215
self.assertEqual((result[0][0], result[0][1]),
216
('duplicate', newtip))
217
self.assertEqual((result[1][0], result[1][2]),
218
('duplicate id', newtip))
219
transform2.finalize()
220
transform3 = TreeTransform(self.wt)
221
self.addCleanup(transform3.finalize)
222
oz_id = transform3.trans_id_tree_file_id('oz-id')
223
transform3.delete_contents(oz_id)
224
self.assertEqual(transform3.find_conflicts(),
225
[('missing parent', oz_id)])
226
root_id = transform3.root
227
tip_id = transform3.trans_id_tree_file_id('tip-id')
228
transform3.adjust_path('tip', root_id, tip_id)
231
def test_add_del(self):
232
start, root = self.get_transform()
233
start.new_directory('a', root, 'a')
235
transform, root = self.get_transform()
236
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
237
transform.new_directory('a', root, 'a')
240
def test_unversioning(self):
241
create_tree, root = self.get_transform()
242
parent_id = create_tree.new_directory('parent', root, 'parent-id')
243
create_tree.new_file('child', parent_id, 'child', 'child-id')
245
unversion = TreeTransform(self.wt)
246
self.addCleanup(unversion.finalize)
247
parent = unversion.trans_id_tree_path('parent')
248
unversion.unversion_file(parent)
249
self.assertEqual(unversion.find_conflicts(),
250
[('unversioned parent', parent_id)])
251
file_id = unversion.trans_id_tree_file_id('child-id')
252
unversion.unversion_file(file_id)
255
def test_name_invariants(self):
256
create_tree, root = self.get_transform()
258
root = create_tree.root
259
create_tree.new_file('name1', root, 'hello1', 'name1')
260
create_tree.new_file('name2', root, 'hello2', 'name2')
261
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
262
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
263
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
264
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
267
mangle_tree,root = self.get_transform()
268
root = mangle_tree.root
270
name1 = mangle_tree.trans_id_tree_file_id('name1')
271
name2 = mangle_tree.trans_id_tree_file_id('name2')
272
mangle_tree.adjust_path('name2', root, name1)
273
mangle_tree.adjust_path('name1', root, name2)
275
#tests for deleting parent directories
276
ddir = mangle_tree.trans_id_tree_file_id('ddir')
277
mangle_tree.delete_contents(ddir)
278
dfile = mangle_tree.trans_id_tree_file_id('dfile')
279
mangle_tree.delete_versioned(dfile)
280
mangle_tree.unversion_file(dfile)
281
mfile = mangle_tree.trans_id_tree_file_id('mfile')
282
mangle_tree.adjust_path('mfile', root, mfile)
284
#tests for adding parent directories
285
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
286
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
287
mangle_tree.adjust_path('mfile2', newdir, mfile2)
288
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
289
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
290
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
291
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
293
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
294
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
295
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
296
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
297
self.assertEqual(file(mfile2_path).read(), 'later2')
298
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
299
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
300
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
301
self.assertEqual(file(newfile_path).read(), 'hello3')
302
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
303
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
304
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
306
def test_both_rename(self):
307
create_tree,root = self.get_transform()
308
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
309
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
311
mangle_tree,root = self.get_transform()
312
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
313
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
314
mangle_tree.adjust_path('test', root, selftest)
315
mangle_tree.adjust_path('test_too_much', root, selftest)
316
mangle_tree.set_executability(True, blackbox)
319
def test_both_rename2(self):
320
create_tree,root = self.get_transform()
321
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
322
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
323
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
324
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
327
mangle_tree,root = self.get_transform()
328
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
329
tests = mangle_tree.trans_id_tree_file_id('tests-id')
330
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
331
mangle_tree.adjust_path('selftest', bzrlib, tests)
332
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
333
mangle_tree.set_executability(True, test_too_much)
336
def test_both_rename3(self):
337
create_tree,root = self.get_transform()
338
tests = create_tree.new_directory('tests', root, 'tests-id')
339
create_tree.new_file('test_too_much.py', tests, 'hello1',
342
mangle_tree,root = self.get_transform()
343
tests = mangle_tree.trans_id_tree_file_id('tests-id')
344
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
345
mangle_tree.adjust_path('selftest', root, tests)
346
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
347
mangle_tree.set_executability(True, test_too_much)
350
def test_move_dangling_ie(self):
351
create_tree, root = self.get_transform()
353
root = create_tree.root
354
create_tree.new_file('name1', root, 'hello1', 'name1')
356
delete_contents, root = self.get_transform()
357
file = delete_contents.trans_id_tree_file_id('name1')
358
delete_contents.delete_contents(file)
359
delete_contents.apply()
360
move_id, root = self.get_transform()
361
name1 = move_id.trans_id_tree_file_id('name1')
362
newdir = move_id.new_directory('dir', root, 'newdir')
363
move_id.adjust_path('name2', newdir, name1)
366
def test_replace_dangling_ie(self):
367
create_tree, root = self.get_transform()
369
root = create_tree.root
370
create_tree.new_file('name1', root, 'hello1', 'name1')
372
delete_contents = TreeTransform(self.wt)
373
self.addCleanup(delete_contents.finalize)
374
file = delete_contents.trans_id_tree_file_id('name1')
375
delete_contents.delete_contents(file)
376
delete_contents.apply()
377
delete_contents.finalize()
378
replace = TreeTransform(self.wt)
379
self.addCleanup(replace.finalize)
380
name2 = replace.new_file('name2', root, 'hello2', 'name1')
381
conflicts = replace.find_conflicts()
382
name1 = replace.trans_id_tree_file_id('name1')
383
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
384
resolve_conflicts(replace)
387
def test_symlinks(self):
388
if not has_symlinks():
389
raise TestSkipped('Symlinks are not supported on this platform')
390
transform,root = self.get_transform()
391
oz_id = transform.new_directory('oz', root, 'oz-id')
392
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
394
wiz_id = transform.create_path('wizard2', oz_id)
395
transform.create_symlink('behind_curtain', wiz_id)
396
transform.version_file('wiz-id2', wiz_id)
397
transform.set_executability(True, wiz_id)
398
self.assertEqual(transform.find_conflicts(),
399
[('non-file executability', wiz_id)])
400
transform.set_executability(None, wiz_id)
402
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
403
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
404
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
406
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
409
def get_conflicted(self):
410
create,root = self.get_transform()
411
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
412
oz = create.new_directory('oz', root, 'oz-id')
413
create.new_directory('emeraldcity', oz, 'emerald-id')
415
conflicts,root = self.get_transform()
416
# set up duplicate entry, duplicate id
417
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
419
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
420
oz = conflicts.trans_id_tree_file_id('oz-id')
421
# set up DeletedParent parent conflict
422
conflicts.delete_versioned(oz)
423
emerald = conflicts.trans_id_tree_file_id('emerald-id')
424
# set up MissingParent conflict
425
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
426
conflicts.adjust_path('munchkincity', root, munchkincity)
427
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
429
conflicts.adjust_path('emeraldcity', emerald, emerald)
430
return conflicts, emerald, oz, old_dorothy, new_dorothy
432
def test_conflict_resolution(self):
433
conflicts, emerald, oz, old_dorothy, new_dorothy =\
434
self.get_conflicted()
435
resolve_conflicts(conflicts)
436
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
437
self.assertIs(conflicts.final_file_id(old_dorothy), None)
438
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
439
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
440
self.assertEqual(conflicts.final_parent(emerald), oz)
443
def test_cook_conflicts(self):
444
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
445
raw_conflicts = resolve_conflicts(tt)
446
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
447
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
448
'dorothy', None, 'dorothy-id')
449
self.assertEqual(cooked_conflicts[0], duplicate)
450
duplicate_id = DuplicateID('Unversioned existing file',
451
'dorothy.moved', 'dorothy', None,
453
self.assertEqual(cooked_conflicts[1], duplicate_id)
454
missing_parent = MissingParent('Created directory', 'munchkincity',
456
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
457
self.assertEqual(cooked_conflicts[2], missing_parent)
458
unversioned_parent = UnversionedParent('Versioned directory',
461
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
463
self.assertEqual(cooked_conflicts[3], unversioned_parent)
464
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
465
'oz/emeraldcity', 'emerald-id', 'emerald-id')
466
self.assertEqual(cooked_conflicts[4], deleted_parent)
467
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
468
self.assertEqual(cooked_conflicts[6], parent_loop)
469
self.assertEqual(len(cooked_conflicts), 7)
472
def test_string_conflicts(self):
473
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
474
raw_conflicts = resolve_conflicts(tt)
475
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
477
conflicts_s = [str(c) for c in cooked_conflicts]
478
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
479
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
480
'Moved existing file to '
482
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
483
'Unversioned existing file '
485
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
486
' munchkincity. Created directory.')
487
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
488
' versioned, but has versioned'
489
' children. Versioned directory.')
490
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
491
" is not empty. Not deleting.")
492
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
493
' versioned, but has versioned'
494
' children. Versioned directory.')
495
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
496
' oz/emeraldcity. Cancelled move.')
498
def test_moving_versioned_directories(self):
499
create, root = self.get_transform()
500
kansas = create.new_directory('kansas', root, 'kansas-id')
501
create.new_directory('house', kansas, 'house-id')
502
create.new_directory('oz', root, 'oz-id')
504
cyclone, root = self.get_transform()
505
oz = cyclone.trans_id_tree_file_id('oz-id')
506
house = cyclone.trans_id_tree_file_id('house-id')
507
cyclone.adjust_path('house', oz, house)
510
def test_moving_root(self):
511
create, root = self.get_transform()
512
fun = create.new_directory('fun', root, 'fun-id')
513
create.new_directory('sun', root, 'sun-id')
514
create.new_directory('moon', root, 'moon')
516
transform, root = self.get_transform()
517
transform.adjust_root_path('oldroot', fun)
518
new_root=transform.trans_id_tree_path('')
519
transform.version_file('new-root', new_root)
522
def test_renames(self):
523
create, root = self.get_transform()
524
old = create.new_directory('old-parent', root, 'old-id')
525
intermediate = create.new_directory('intermediate', old, 'im-id')
526
myfile = create.new_file('myfile', intermediate, 'myfile-text',
529
rename, root = self.get_transform()
530
old = rename.trans_id_file_id('old-id')
531
rename.adjust_path('new', root, old)
532
myfile = rename.trans_id_file_id('myfile-id')
533
rename.set_executability(True, myfile)
536
def test_find_interesting(self):
537
create, root = self.get_transform()
539
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
540
create.new_file('uvfile', root, 'othertext')
542
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
543
find_interesting, wt, wt, ['vfile'])
544
self.assertEqual(result, set(['myfile-id']))
546
def test_set_executability_order(self):
547
"""Ensure that executability behaves the same, no matter what order.
549
- create file and set executability simultaneously
550
- create file and set executability afterward
551
- unsetting the executability of a file whose executability has not been
552
declared should throw an exception (this may happen when a
553
merge attempts to create a file with a duplicate ID)
555
transform, root = self.get_transform()
557
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
559
sac = transform.new_file('set_after_creation', root,
560
'Set after creation', 'sac')
561
transform.set_executability(True, sac)
562
uws = transform.new_file('unset_without_set', root, 'Unset badly',
564
self.assertRaises(KeyError, transform.set_executability, None, uws)
566
self.assertTrue(wt.is_executable('soc'))
567
self.assertTrue(wt.is_executable('sac'))
569
def test_preserve_mode(self):
570
"""File mode is preserved when replacing content"""
571
if sys.platform == 'win32':
572
raise TestSkipped('chmod has no effect on win32')
573
transform, root = self.get_transform()
574
transform.new_file('file1', root, 'contents', 'file1-id', True)
576
self.assertTrue(self.wt.is_executable('file1-id'))
577
transform, root = self.get_transform()
578
file1_id = transform.trans_id_tree_file_id('file1-id')
579
transform.delete_contents(file1_id)
580
transform.create_file('contents2', file1_id)
582
self.assertTrue(self.wt.is_executable('file1-id'))
584
def test__set_mode_stats_correctly(self):
585
"""_set_mode stats to determine file mode."""
586
if sys.platform == 'win32':
587
raise TestSkipped('chmod has no effect on win32')
591
def instrumented_stat(path):
592
stat_paths.append(path)
593
return real_stat(path)
595
transform, root = self.get_transform()
597
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
598
file_id='bar-id-1', executable=False)
601
transform, root = self.get_transform()
602
bar1_id = transform.trans_id_tree_path('bar')
603
bar2_id = transform.trans_id_tree_path('bar2')
605
os.stat = instrumented_stat
606
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
611
bar1_abspath = self.wt.abspath('bar')
612
self.assertEqual([bar1_abspath], stat_paths)
614
def test_iter_changes(self):
615
self.wt.set_root_id('eert_toor')
616
transform, root = self.get_transform()
617
transform.new_file('old', root, 'blah', 'id-1', True)
619
transform, root = self.get_transform()
621
self.assertEqual([], list(transform._iter_changes()))
622
old = transform.trans_id_tree_file_id('id-1')
623
transform.unversion_file(old)
624
self.assertEqual([('id-1', ('old', None), False, (True, False),
625
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
626
(True, True))], list(transform._iter_changes()))
627
transform.new_directory('new', root, 'id-1')
628
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
629
('eert_toor', 'eert_toor'), ('old', 'new'),
630
('file', 'directory'),
631
(True, False))], list(transform._iter_changes()))
635
def test_iter_changes_new(self):
636
self.wt.set_root_id('eert_toor')
637
transform, root = self.get_transform()
638
transform.new_file('old', root, 'blah')
640
transform, root = self.get_transform()
642
old = transform.trans_id_tree_path('old')
643
transform.version_file('id-1', old)
644
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
645
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
646
(False, False))], list(transform._iter_changes()))
650
def test_iter_changes_modifications(self):
651
self.wt.set_root_id('eert_toor')
652
transform, root = self.get_transform()
653
transform.new_file('old', root, 'blah', 'id-1')
654
transform.new_file('new', root, 'blah')
655
transform.new_directory('subdir', root, 'subdir-id')
657
transform, root = self.get_transform()
659
old = transform.trans_id_tree_path('old')
660
subdir = transform.trans_id_tree_file_id('subdir-id')
661
new = transform.trans_id_tree_path('new')
662
self.assertEqual([], list(transform._iter_changes()))
665
transform.delete_contents(old)
666
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
667
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
668
(False, False))], list(transform._iter_changes()))
671
transform.create_file('blah', old)
672
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
673
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
674
(False, False))], list(transform._iter_changes()))
675
transform.cancel_deletion(old)
676
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
677
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
678
(False, False))], list(transform._iter_changes()))
679
transform.cancel_creation(old)
681
# move file_id to a different file
682
self.assertEqual([], list(transform._iter_changes()))
683
transform.unversion_file(old)
684
transform.version_file('id-1', new)
685
transform.adjust_path('old', root, new)
686
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
687
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
688
(False, False))], list(transform._iter_changes()))
689
transform.cancel_versioning(new)
690
transform._removed_id = set()
693
self.assertEqual([], list(transform._iter_changes()))
694
transform.set_executability(True, old)
695
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
696
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
697
(False, True))], list(transform._iter_changes()))
698
transform.set_executability(None, old)
701
self.assertEqual([], list(transform._iter_changes()))
702
transform.adjust_path('new', root, old)
703
transform._new_parent = {}
704
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
705
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
706
(False, False))], list(transform._iter_changes()))
707
transform._new_name = {}
710
self.assertEqual([], list(transform._iter_changes()))
711
transform.adjust_path('new', subdir, old)
712
transform._new_name = {}
713
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
714
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
715
('file', 'file'), (False, False))],
716
list(transform._iter_changes()))
717
transform._new_path = {}
722
def test_iter_changes_modified_bleed(self):
723
self.wt.set_root_id('eert_toor')
724
"""Modified flag should not bleed from one change to another"""
725
# unfortunately, we have no guarantee that file1 (which is modified)
726
# will be applied before file2. And if it's applied after file2, it
727
# obviously can't bleed into file2's change output. But for now, it
729
transform, root = self.get_transform()
730
transform.new_file('file1', root, 'blah', 'id-1')
731
transform.new_file('file2', root, 'blah', 'id-2')
733
transform, root = self.get_transform()
735
transform.delete_contents(transform.trans_id_file_id('id-1'))
736
transform.set_executability(True,
737
transform.trans_id_file_id('id-2'))
738
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
739
('eert_toor', 'eert_toor'), ('file1', u'file1'),
740
('file', None), (False, False)),
741
('id-2', (u'file2', u'file2'), False, (True, True),
742
('eert_toor', 'eert_toor'), ('file2', u'file2'),
743
('file', 'file'), (False, True))],
744
list(transform._iter_changes()))
748
def test_iter_changes_move_missing(self):
749
"""Test moving ids with no files around"""
750
self.wt.set_root_id('toor_eert')
751
# Need two steps because versioning a non-existant file is a conflict.
752
transform, root = self.get_transform()
753
transform.new_directory('floater', root, 'floater-id')
755
transform, root = self.get_transform()
756
transform.delete_contents(transform.trans_id_tree_path('floater'))
758
transform, root = self.get_transform()
759
floater = transform.trans_id_tree_path('floater')
761
transform.adjust_path('flitter', root, floater)
762
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
763
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
764
(None, None), (False, False))], list(transform._iter_changes()))
768
def test_iter_changes_pointless(self):
769
"""Ensure that no-ops are not treated as modifications"""
770
self.wt.set_root_id('eert_toor')
771
transform, root = self.get_transform()
772
transform.new_file('old', root, 'blah', 'id-1')
773
transform.new_directory('subdir', root, 'subdir-id')
775
transform, root = self.get_transform()
777
old = transform.trans_id_tree_path('old')
778
subdir = transform.trans_id_tree_file_id('subdir-id')
779
self.assertEqual([], list(transform._iter_changes()))
780
transform.delete_contents(subdir)
781
transform.create_directory(subdir)
782
transform.set_executability(False, old)
783
transform.unversion_file(old)
784
transform.version_file('id-1', old)
785
transform.adjust_path('old', root, old)
786
self.assertEqual([], list(transform._iter_changes()))
790
def test_rename_count(self):
791
transform, root = self.get_transform()
792
transform.new_file('name1', root, 'contents')
793
self.assertEqual(transform.rename_count, 0)
795
self.assertEqual(transform.rename_count, 1)
796
transform2, root = self.get_transform()
797
transform2.adjust_path('name2', root,
798
transform2.trans_id_tree_path('name1'))
799
self.assertEqual(transform2.rename_count, 0)
801
self.assertEqual(transform2.rename_count, 2)
803
def test_change_parent(self):
804
"""Ensure that after we change a parent, the results are still right.
806
Renames and parent changes on pending transforms can happen as part
807
of conflict resolution, and are explicitly permitted by the
810
This test ensures they work correctly with the rename-avoidance
813
transform, root = self.get_transform()
814
parent1 = transform.new_directory('parent1', root)
815
child1 = transform.new_file('child1', parent1, 'contents')
816
parent2 = transform.new_directory('parent2', root)
817
transform.adjust_path('child1', parent2, child1)
819
self.failIfExists(self.wt.abspath('parent1/child1'))
820
self.failUnlessExists(self.wt.abspath('parent2/child1'))
821
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
822
# no rename for child1 (counting only renames during apply)
823
self.failUnlessEqual(2, transform.rename_count)
825
def test_cancel_parent(self):
826
"""Cancelling a parent doesn't cause deletion of a non-empty directory
828
This is like the test_change_parent, except that we cancel the parent
829
before adjusting the path. The transform must detect that the
830
directory is non-empty, and move children to safe locations.
832
transform, root = self.get_transform()
833
parent1 = transform.new_directory('parent1', root)
834
child1 = transform.new_file('child1', parent1, 'contents')
835
child2 = transform.new_file('child2', parent1, 'contents')
837
transform.cancel_creation(parent1)
839
self.fail('Failed to move child1 before deleting parent1')
840
transform.cancel_creation(child2)
841
transform.create_directory(parent1)
843
transform.cancel_creation(parent1)
844
# If the transform incorrectly believes that child2 is still in
845
# parent1's limbo directory, it will try to rename it and fail
846
# because was already moved by the first cancel_creation.
848
self.fail('Transform still thinks child2 is a child of parent1')
849
parent2 = transform.new_directory('parent2', root)
850
transform.adjust_path('child1', parent2, child1)
852
self.failIfExists(self.wt.abspath('parent1'))
853
self.failUnlessExists(self.wt.abspath('parent2/child1'))
854
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
855
self.failUnlessEqual(2, transform.rename_count)
857
def test_adjust_and_cancel(self):
858
"""Make sure adjust_path keeps track of limbo children properly"""
859
transform, root = self.get_transform()
860
parent1 = transform.new_directory('parent1', root)
861
child1 = transform.new_file('child1', parent1, 'contents')
862
parent2 = transform.new_directory('parent2', root)
863
transform.adjust_path('child1', parent2, child1)
864
transform.cancel_creation(child1)
866
transform.cancel_creation(parent1)
867
# if the transform thinks child1 is still in parent1's limbo
868
# directory, it will attempt to move it and fail.
870
self.fail('Transform still thinks child1 is a child of parent1')
873
def test_noname_contents(self):
874
"""TreeTransform should permit deferring naming files."""
875
transform, root = self.get_transform()
876
parent = transform.trans_id_file_id('parent-id')
878
transform.create_directory(parent)
880
self.fail("Can't handle contents with no name")
883
def test_noname_contents_nested(self):
884
"""TreeTransform should permit deferring naming files."""
885
transform, root = self.get_transform()
886
parent = transform.trans_id_file_id('parent-id')
888
transform.create_directory(parent)
890
self.fail("Can't handle contents with no name")
891
child = transform.new_directory('child', parent)
892
transform.adjust_path('parent', root, parent)
894
self.failUnlessExists(self.wt.abspath('parent/child'))
895
self.assertEqual(1, transform.rename_count)
897
def test_reuse_name(self):
898
"""Avoid reusing the same limbo name for different files"""
899
transform, root = self.get_transform()
900
parent = transform.new_directory('parent', root)
901
child1 = transform.new_directory('child', parent)
903
child2 = transform.new_directory('child', parent)
905
self.fail('Tranform tried to use the same limbo name twice')
906
transform.adjust_path('child2', parent, child2)
908
# limbo/new-1 => parent, limbo/new-3 => parent/child2
909
# child2 is put into top-level limbo because child1 has already
910
# claimed the direct limbo path when child2 is created. There is no
911
# advantage in renaming files once they're in top-level limbo, except
913
self.assertEqual(2, transform.rename_count)
915
def test_reuse_when_first_moved(self):
916
"""Don't avoid direct paths when it is safe to use them"""
917
transform, root = self.get_transform()
918
parent = transform.new_directory('parent', root)
919
child1 = transform.new_directory('child', parent)
920
transform.adjust_path('child1', parent, child1)
921
child2 = transform.new_directory('child', parent)
923
# limbo/new-1 => parent
924
self.assertEqual(1, transform.rename_count)
926
def test_reuse_after_cancel(self):
927
"""Don't avoid direct paths when it is safe to use them"""
928
transform, root = self.get_transform()
929
parent2 = transform.new_directory('parent2', root)
930
child1 = transform.new_directory('child1', parent2)
931
transform.cancel_creation(parent2)
932
transform.create_directory(parent2)
933
child2 = transform.new_directory('child1', parent2)
934
transform.adjust_path('child2', parent2, child1)
936
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
937
self.assertEqual(2, transform.rename_count)
939
def test_finalize_order(self):
940
"""Finalize must be done in child-to-parent order"""
941
transform, root = self.get_transform()
942
parent = transform.new_directory('parent', root)
943
child = transform.new_directory('child', parent)
947
self.fail('Tried to remove parent before child1')
949
def test_cancel_with_cancelled_child_should_succeed(self):
950
transform, root = self.get_transform()
951
parent = transform.new_directory('parent', root)
952
child = transform.new_directory('child', parent)
953
transform.cancel_creation(child)
954
transform.cancel_creation(parent)
957
def test_change_entry(self):
958
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
959
self.callDeprecated([txt], change_entry, None, None, None, None, None,
963
class TransformGroup(object):
964
def __init__(self, dirname, root_id):
967
self.wt = BzrDir.create_standalone_workingtree(dirname)
968
self.wt.set_root_id(root_id)
969
self.b = self.wt.branch
970
self.tt = TreeTransform(self.wt)
971
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
974
def conflict_text(tree, merge):
975
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
976
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
979
class TestTransformMerge(TestCaseInTempDir):
980
def test_text_merge(self):
981
root_id = generate_ids.gen_root_id()
982
base = TransformGroup("base", root_id)
983
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
984
base.tt.new_file('b', base.root, 'b1', 'b')
985
base.tt.new_file('c', base.root, 'c', 'c')
986
base.tt.new_file('d', base.root, 'd', 'd')
987
base.tt.new_file('e', base.root, 'e', 'e')
988
base.tt.new_file('f', base.root, 'f', 'f')
989
base.tt.new_directory('g', base.root, 'g')
990
base.tt.new_directory('h', base.root, 'h')
992
other = TransformGroup("other", root_id)
993
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
994
other.tt.new_file('b', other.root, 'b2', 'b')
995
other.tt.new_file('c', other.root, 'c2', 'c')
996
other.tt.new_file('d', other.root, 'd', 'd')
997
other.tt.new_file('e', other.root, 'e2', 'e')
998
other.tt.new_file('f', other.root, 'f', 'f')
999
other.tt.new_file('g', other.root, 'g', 'g')
1000
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1001
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1003
this = TransformGroup("this", root_id)
1004
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1005
this.tt.new_file('b', this.root, 'b', 'b')
1006
this.tt.new_file('c', this.root, 'c', 'c')
1007
this.tt.new_file('d', this.root, 'd2', 'd')
1008
this.tt.new_file('e', this.root, 'e2', 'e')
1009
this.tt.new_file('f', this.root, 'f', 'f')
1010
this.tt.new_file('g', this.root, 'g', 'g')
1011
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1012
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1014
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1016
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1017
# three-way text conflict
1018
self.assertEqual(this.wt.get_file('b').read(),
1019
conflict_text('b', 'b2'))
1021
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1023
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1024
# Ambigious clean merge
1025
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1027
self.assertEqual(this.wt.get_file('f').read(), 'f')
1028
# Correct correct results when THIS == OTHER
1029
self.assertEqual(this.wt.get_file('g').read(), 'g')
1030
# Text conflict when THIS & OTHER are text and BASE is dir
1031
self.assertEqual(this.wt.get_file('h').read(),
1032
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1033
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1035
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1037
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1038
self.assertEqual(this.wt.get_file('i').read(),
1039
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1040
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1042
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1044
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1045
modified = ['a', 'b', 'c', 'h', 'i']
1046
merge_modified = this.wt.merge_modified()
1047
self.assertSubset(merge_modified, modified)
1048
self.assertEqual(len(merge_modified), len(modified))
1049
file(this.wt.id2abspath('a'), 'wb').write('booga')
1051
merge_modified = this.wt.merge_modified()
1052
self.assertSubset(merge_modified, modified)
1053
self.assertEqual(len(merge_modified), len(modified))
1057
def test_file_merge(self):
1058
if not has_symlinks():
1059
raise TestSkipped('Symlinks are not supported on this platform')
1060
root_id = generate_ids.gen_root_id()
1061
base = TransformGroup("BASE", root_id)
1062
this = TransformGroup("THIS", root_id)
1063
other = TransformGroup("OTHER", root_id)
1064
for tg in this, base, other:
1065
tg.tt.new_directory('a', tg.root, 'a')
1066
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1067
tg.tt.new_file('c', tg.root, 'c', 'c')
1068
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1069
targets = ((base, 'base-e', 'base-f', None, None),
1070
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1071
(other, 'other-e', None, 'other-g', 'other-h'))
1072
for tg, e_target, f_target, g_target, h_target in targets:
1073
for link, target in (('e', e_target), ('f', f_target),
1074
('g', g_target), ('h', h_target)):
1075
if target is not None:
1076
tg.tt.new_symlink(link, tg.root, target, link)
1078
for tg in this, base, other:
1080
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1081
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1082
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1083
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1084
for suffix in ('THIS', 'BASE', 'OTHER'):
1085
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1086
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1087
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1088
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1089
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1090
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1091
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1092
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1093
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1094
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1095
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1096
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1097
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1098
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1100
def test_filename_merge(self):
1101
root_id = generate_ids.gen_root_id()
1102
base = TransformGroup("BASE", root_id)
1103
this = TransformGroup("THIS", root_id)
1104
other = TransformGroup("OTHER", root_id)
1105
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1106
for t in [base, this, other]]
1107
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1108
for t in [base, this, other]]
1109
base.tt.new_directory('c', base_a, 'c')
1110
this.tt.new_directory('c1', this_a, 'c')
1111
other.tt.new_directory('c', other_b, 'c')
1113
base.tt.new_directory('d', base_a, 'd')
1114
this.tt.new_directory('d1', this_b, 'd')
1115
other.tt.new_directory('d', other_a, 'd')
1117
base.tt.new_directory('e', base_a, 'e')
1118
this.tt.new_directory('e', this_a, 'e')
1119
other.tt.new_directory('e1', other_b, 'e')
1121
base.tt.new_directory('f', base_a, 'f')
1122
this.tt.new_directory('f1', this_b, 'f')
1123
other.tt.new_directory('f1', other_b, 'f')
1125
for tg in [this, base, other]:
1127
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1128
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1129
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1130
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1131
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1133
def test_filename_merge_conflicts(self):
1134
root_id = generate_ids.gen_root_id()
1135
base = TransformGroup("BASE", root_id)
1136
this = TransformGroup("THIS", root_id)
1137
other = TransformGroup("OTHER", root_id)
1138
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1139
for t in [base, this, other]]
1140
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1141
for t in [base, this, other]]
1143
base.tt.new_file('g', base_a, 'g', 'g')
1144
other.tt.new_file('g1', other_b, 'g1', 'g')
1146
base.tt.new_file('h', base_a, 'h', 'h')
1147
this.tt.new_file('h1', this_b, 'h1', 'h')
1149
base.tt.new_file('i', base.root, 'i', 'i')
1150
other.tt.new_directory('i1', this_b, 'i')
1152
for tg in [this, base, other]:
1154
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1156
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1157
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1158
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1159
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1160
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1161
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1162
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1165
class TestBuildTree(tests.TestCaseWithTransport):
1167
def test_build_tree(self):
1168
if not has_symlinks():
1169
raise TestSkipped('Test requires symlink support')
1171
a = BzrDir.create_standalone_workingtree('a')
1173
file('a/foo/bar', 'wb').write('contents')
1174
os.symlink('a/foo/bar', 'a/foo/baz')
1175
a.add(['foo', 'foo/bar', 'foo/baz'])
1176
a.commit('initial commit')
1177
b = BzrDir.create_standalone_workingtree('b')
1178
basis = a.basis_tree()
1180
self.addCleanup(basis.unlock)
1181
build_tree(basis, b)
1182
self.assertIs(os.path.isdir('b/foo'), True)
1183
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1184
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1186
def test_build_with_references(self):
1187
tree = self.make_branch_and_tree('source',
1188
format='dirstate-with-subtree')
1189
subtree = self.make_branch_and_tree('source/subtree',
1190
format='dirstate-with-subtree')
1191
tree.add_reference(subtree)
1192
tree.commit('a revision')
1193
tree.branch.create_checkout('target')
1194
self.failUnlessExists('target')
1195
self.failUnlessExists('target/subtree')
1197
def test_file_conflict_handling(self):
1198
"""Ensure that when building trees, conflict handling is done"""
1199
source = self.make_branch_and_tree('source')
1200
target = self.make_branch_and_tree('target')
1201
self.build_tree(['source/file', 'target/file'])
1202
source.add('file', 'new-file')
1203
source.commit('added file')
1204
build_tree(source.basis_tree(), target)
1205
self.assertEqual([DuplicateEntry('Moved existing file to',
1206
'file.moved', 'file', None, 'new-file')],
1208
target2 = self.make_branch_and_tree('target2')
1209
target_file = file('target2/file', 'wb')
1211
source_file = file('source/file', 'rb')
1213
target_file.write(source_file.read())
1218
build_tree(source.basis_tree(), target2)
1219
self.assertEqual([], target2.conflicts())
1221
def test_symlink_conflict_handling(self):
1222
"""Ensure that when building trees, conflict handling is done"""
1223
if not has_symlinks():
1224
raise TestSkipped('Test requires symlink support')
1225
source = self.make_branch_and_tree('source')
1226
os.symlink('foo', 'source/symlink')
1227
source.add('symlink', 'new-symlink')
1228
source.commit('added file')
1229
target = self.make_branch_and_tree('target')
1230
os.symlink('bar', 'target/symlink')
1231
build_tree(source.basis_tree(), target)
1232
self.assertEqual([DuplicateEntry('Moved existing file to',
1233
'symlink.moved', 'symlink', None, 'new-symlink')],
1235
target = self.make_branch_and_tree('target2')
1236
os.symlink('foo', 'target2/symlink')
1237
build_tree(source.basis_tree(), target)
1238
self.assertEqual([], target.conflicts())
1240
def test_directory_conflict_handling(self):
1241
"""Ensure that when building trees, conflict handling is done"""
1242
source = self.make_branch_and_tree('source')
1243
target = self.make_branch_and_tree('target')
1244
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1245
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1246
source.commit('added file')
1247
build_tree(source.basis_tree(), target)
1248
self.assertEqual([], target.conflicts())
1249
self.failUnlessExists('target/dir1/file')
1251
# Ensure contents are merged
1252
target = self.make_branch_and_tree('target2')
1253
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1254
build_tree(source.basis_tree(), target)
1255
self.assertEqual([], target.conflicts())
1256
self.failUnlessExists('target2/dir1/file2')
1257
self.failUnlessExists('target2/dir1/file')
1259
# Ensure new contents are suppressed for existing branches
1260
target = self.make_branch_and_tree('target3')
1261
self.make_branch('target3/dir1')
1262
self.build_tree(['target3/dir1/file2'])
1263
build_tree(source.basis_tree(), target)
1264
self.failIfExists('target3/dir1/file')
1265
self.failUnlessExists('target3/dir1/file2')
1266
self.failUnlessExists('target3/dir1.diverted/file')
1267
self.assertEqual([DuplicateEntry('Diverted to',
1268
'dir1.diverted', 'dir1', 'new-dir1', None)],
1271
target = self.make_branch_and_tree('target4')
1272
self.build_tree(['target4/dir1/'])
1273
self.make_branch('target4/dir1/file')
1274
build_tree(source.basis_tree(), target)
1275
self.failUnlessExists('target4/dir1/file')
1276
self.assertEqual('directory', file_kind('target4/dir1/file'))
1277
self.failUnlessExists('target4/dir1/file.diverted')
1278
self.assertEqual([DuplicateEntry('Diverted to',
1279
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1282
def test_mixed_conflict_handling(self):
1283
"""Ensure that when building trees, conflict handling is done"""
1284
source = self.make_branch_and_tree('source')
1285
target = self.make_branch_and_tree('target')
1286
self.build_tree(['source/name', 'target/name/'])
1287
source.add('name', 'new-name')
1288
source.commit('added file')
1289
build_tree(source.basis_tree(), target)
1290
self.assertEqual([DuplicateEntry('Moved existing file to',
1291
'name.moved', 'name', None, 'new-name')], target.conflicts())
1293
def test_raises_in_populated(self):
1294
source = self.make_branch_and_tree('source')
1295
self.build_tree(['source/name'])
1297
source.commit('added name')
1298
target = self.make_branch_and_tree('target')
1299
self.build_tree(['target/name'])
1301
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1302
build_tree, source.basis_tree(), target)
1304
def test_build_tree_rename_count(self):
1305
source = self.make_branch_and_tree('source')
1306
self.build_tree(['source/file1', 'source/dir1/'])
1307
source.add(['file1', 'dir1'])
1308
source.commit('add1')
1309
target1 = self.make_branch_and_tree('target1')
1310
transform_result = build_tree(source.basis_tree(), target1)
1311
self.assertEqual(2, transform_result.rename_count)
1313
self.build_tree(['source/dir1/file2'])
1314
source.add(['dir1/file2'])
1315
source.commit('add3')
1316
target2 = self.make_branch_and_tree('target2')
1317
transform_result = build_tree(source.basis_tree(), target2)
1318
# children of non-root directories should not be renamed
1319
self.assertEqual(2, transform_result.rename_count)
1322
class MockTransform(object):
1324
def has_named_child(self, by_parent, parent_id, name):
1325
for child_id in by_parent[parent_id]:
1329
elif name == "name.~%s~" % child_id:
1334
class MockEntry(object):
1336
object.__init__(self)
1339
class TestGetBackupName(TestCase):
1340
def test_get_backup_name(self):
1341
tt = MockTransform()
1342
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1343
self.assertEqual(name, 'name.~1~')
1344
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1345
self.assertEqual(name, 'name.~2~')
1346
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1347
self.assertEqual(name, 'name.~1~')
1348
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1349
self.assertEqual(name, 'name.~1~')
1350
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1351
self.assertEqual(name, 'name.~4~')
1354
class TestFileMover(tests.TestCaseWithTransport):
1356
def test_file_mover(self):
1357
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1358
mover = _FileMover()
1359
mover.rename('a', 'q')
1360
self.failUnlessExists('q')
1361
self.failIfExists('a')
1362
self.failUnlessExists('q/b')
1363
self.failUnlessExists('c')
1364
self.failUnlessExists('c/d')
1366
def test_pre_delete_rollback(self):
1367
self.build_tree(['a/'])
1368
mover = _FileMover()
1369
mover.pre_delete('a', 'q')
1370
self.failUnlessExists('q')
1371
self.failIfExists('a')
1373
self.failIfExists('q')
1374
self.failUnlessExists('a')
1376
def test_apply_deletions(self):
1377
self.build_tree(['a/', 'b/'])
1378
mover = _FileMover()
1379
mover.pre_delete('a', 'q')
1380
mover.pre_delete('b', 'r')
1381
self.failUnlessExists('q')
1382
self.failUnlessExists('r')
1383
self.failIfExists('a')
1384
self.failIfExists('b')
1385
mover.apply_deletions()
1386
self.failIfExists('q')
1387
self.failIfExists('r')
1388
self.failIfExists('a')
1389
self.failIfExists('b')
1391
def test_file_mover_rollback(self):
1392
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1393
mover = _FileMover()
1394
mover.rename('c/d', 'c/f')
1395
mover.rename('c/e', 'c/d')
1397
mover.rename('a', 'c')
1400
self.failUnlessExists('a')
1401
self.failUnlessExists('c/d')
1404
class Bogus(Exception):
1408
class TestTransformRollback(tests.TestCaseWithTransport):
1410
class ExceptionFileMover(_FileMover):
1412
def __init__(self, bad_source=None, bad_target=None):
1413
_FileMover.__init__(self)
1414
self.bad_source = bad_source
1415
self.bad_target = bad_target
1417
def rename(self, source, target):
1418
if (self.bad_source is not None and
1419
source.endswith(self.bad_source)):
1421
elif (self.bad_target is not None and
1422
target.endswith(self.bad_target)):
1425
_FileMover.rename(self, source, target)
1427
def test_rollback_rename(self):
1428
tree = self.make_branch_and_tree('.')
1429
self.build_tree(['a/', 'a/b'])
1430
tt = TreeTransform(tree)
1431
self.addCleanup(tt.finalize)
1432
a_id = tt.trans_id_tree_path('a')
1433
tt.adjust_path('c', tt.root, a_id)
1434
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1435
self.assertRaises(Bogus, tt.apply,
1436
_mover=self.ExceptionFileMover(bad_source='a'))
1437
self.failUnlessExists('a')
1438
self.failUnlessExists('a/b')
1440
self.failUnlessExists('c')
1441
self.failUnlessExists('c/d')
1443
def test_rollback_rename_into_place(self):
1444
tree = self.make_branch_and_tree('.')
1445
self.build_tree(['a/', 'a/b'])
1446
tt = TreeTransform(tree)
1447
self.addCleanup(tt.finalize)
1448
a_id = tt.trans_id_tree_path('a')
1449
tt.adjust_path('c', tt.root, a_id)
1450
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1451
self.assertRaises(Bogus, tt.apply,
1452
_mover=self.ExceptionFileMover(bad_target='c/d'))
1453
self.failUnlessExists('a')
1454
self.failUnlessExists('a/b')
1456
self.failUnlessExists('c')
1457
self.failUnlessExists('c/d')
1459
def test_rollback_deletion(self):
1460
tree = self.make_branch_and_tree('.')
1461
self.build_tree(['a/', 'a/b'])
1462
tt = TreeTransform(tree)
1463
self.addCleanup(tt.finalize)
1464
a_id = tt.trans_id_tree_path('a')
1465
tt.delete_contents(a_id)
1466
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1467
self.assertRaises(Bogus, tt.apply,
1468
_mover=self.ExceptionFileMover(bad_target='d'))
1469
self.failUnlessExists('a')
1470
self.failUnlessExists('a/b')