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, pathjoin
37
from bzrlib.merge import Merge3Merger
38
from bzrlib.tests import (
44
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
45
resolve_conflicts, cook_conflicts,
46
find_interesting, build_tree, get_backup_name,
47
change_entry, _FileMover)
50
class TestTreeTransform(tests.TestCaseWithTransport):
53
super(TestTreeTransform, self).setUp()
54
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
57
def get_transform(self):
58
transform = TreeTransform(self.wt)
59
#self.addCleanup(transform.finalize)
60
return transform, transform.root
62
def test_existing_limbo(self):
63
transform, root = self.get_transform()
64
limbo_name = transform._limbodir
65
deletion_path = transform._deletiondir
66
os.mkdir(pathjoin(limbo_name, 'hehe'))
67
self.assertRaises(ImmortalLimbo, transform.apply)
68
self.assertRaises(LockError, self.wt.unlock)
69
self.assertRaises(ExistingLimbo, self.get_transform)
70
self.assertRaises(LockError, self.wt.unlock)
71
os.rmdir(pathjoin(limbo_name, 'hehe'))
73
os.rmdir(deletion_path)
74
transform, root = self.get_transform()
77
def test_existing_pending_deletion(self):
78
transform, root = self.get_transform()
79
deletion_path = self._limbodir = urlutils.local_path_from_url(
80
transform._tree._control_files.controlfilename('pending-deletion'))
81
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
82
self.assertRaises(ImmortalPendingDeletion, transform.apply)
83
self.assertRaises(LockError, self.wt.unlock)
84
self.assertRaises(ExistingPendingDeletion, self.get_transform)
87
transform, root = self.get_transform()
88
self.wt.lock_tree_write()
89
self.addCleanup(self.wt.unlock)
90
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
91
imaginary_id = transform.trans_id_tree_path('imaginary')
92
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
93
self.assertEqual(imaginary_id, imaginary_id2)
94
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
95
self.assertEqual(transform.final_kind(root), 'directory')
96
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
97
trans_id = transform.create_path('name', root)
98
self.assertIs(transform.final_file_id(trans_id), None)
99
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
100
transform.create_file('contents', trans_id)
101
transform.set_executability(True, trans_id)
102
transform.version_file('my_pretties', trans_id)
103
self.assertRaises(DuplicateKey, transform.version_file,
104
'my_pretties', trans_id)
105
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
106
self.assertEqual(transform.final_parent(trans_id), root)
107
self.assertIs(transform.final_parent(root), ROOT_PARENT)
108
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
109
oz_id = transform.create_path('oz', root)
110
transform.create_directory(oz_id)
111
transform.version_file('ozzie', oz_id)
112
trans_id2 = transform.create_path('name2', root)
113
transform.create_file('contents', trans_id2)
114
transform.set_executability(False, trans_id2)
115
transform.version_file('my_pretties2', trans_id2)
116
modified_paths = transform.apply().modified_paths
117
self.assertEqual('contents', self.wt.get_file_byname('name').read())
118
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
119
self.assertIs(self.wt.is_executable('my_pretties'), True)
120
self.assertIs(self.wt.is_executable('my_pretties2'), False)
121
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
122
self.assertEqual(len(modified_paths), 3)
123
tree_mod_paths = [self.wt.id2abspath(f) for f in
124
('ozzie', 'my_pretties', 'my_pretties2')]
125
self.assertSubset(tree_mod_paths, modified_paths)
126
# is it safe to finalize repeatedly?
130
def test_convenience(self):
131
transform, root = self.get_transform()
132
self.wt.lock_tree_write()
133
self.addCleanup(self.wt.unlock)
134
trans_id = transform.new_file('name', root, 'contents',
136
oz = transform.new_directory('oz', root, 'oz-id')
137
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
138
toto = transform.new_file('toto', dorothy, 'toto-contents',
141
self.assertEqual(len(transform.find_conflicts()), 0)
143
self.assertRaises(ReusingTransform, transform.find_conflicts)
144
self.assertEqual('contents', file(self.wt.abspath('name')).read())
145
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
146
self.assertIs(self.wt.is_executable('my_pretties'), True)
147
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
148
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
149
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
151
self.assertEqual('toto-contents',
152
self.wt.get_file_byname('oz/dorothy/toto').read())
153
self.assertIs(self.wt.is_executable('toto-id'), False)
155
def test_tree_reference(self):
156
transform, root = self.get_transform()
157
tree = transform._tree
158
trans_id = transform.new_directory('reference', root, 'subtree-id')
159
transform.set_tree_reference('subtree-revision', trans_id)
162
self.addCleanup(tree.unlock)
163
self.assertEqual('subtree-revision',
164
tree.inventory['subtree-id'].reference_revision)
166
def test_conflicts(self):
167
transform, root = self.get_transform()
168
trans_id = transform.new_file('name', root, 'contents',
170
self.assertEqual(len(transform.find_conflicts()), 0)
171
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
172
self.assertEqual(transform.find_conflicts(),
173
[('duplicate', trans_id, trans_id2, 'name')])
174
self.assertRaises(MalformedTransform, transform.apply)
175
transform.adjust_path('name', trans_id, trans_id2)
176
self.assertEqual(transform.find_conflicts(),
177
[('non-directory parent', trans_id)])
178
tinman_id = transform.trans_id_tree_path('tinman')
179
transform.adjust_path('name', tinman_id, trans_id2)
180
self.assertEqual(transform.find_conflicts(),
181
[('unversioned parent', tinman_id),
182
('missing parent', tinman_id)])
183
lion_id = transform.create_path('lion', root)
184
self.assertEqual(transform.find_conflicts(),
185
[('unversioned parent', tinman_id),
186
('missing parent', tinman_id)])
187
transform.adjust_path('name', lion_id, trans_id2)
188
self.assertEqual(transform.find_conflicts(),
189
[('unversioned parent', lion_id),
190
('missing parent', lion_id)])
191
transform.version_file("Courage", lion_id)
192
self.assertEqual(transform.find_conflicts(),
193
[('missing parent', lion_id),
194
('versioning no contents', lion_id)])
195
transform.adjust_path('name2', root, trans_id2)
196
self.assertEqual(transform.find_conflicts(),
197
[('versioning no contents', lion_id)])
198
transform.create_file('Contents, okay?', lion_id)
199
transform.adjust_path('name2', trans_id2, trans_id2)
200
self.assertEqual(transform.find_conflicts(),
201
[('parent loop', trans_id2),
202
('non-directory parent', trans_id2)])
203
transform.adjust_path('name2', root, trans_id2)
204
oz_id = transform.new_directory('oz', root)
205
transform.set_executability(True, oz_id)
206
self.assertEqual(transform.find_conflicts(),
207
[('unversioned executability', oz_id)])
208
transform.version_file('oz-id', oz_id)
209
self.assertEqual(transform.find_conflicts(),
210
[('non-file executability', oz_id)])
211
transform.set_executability(None, oz_id)
212
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
214
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
215
self.assertEqual('contents', file(self.wt.abspath('name')).read())
216
transform2, root = self.get_transform()
217
oz_id = transform2.trans_id_tree_file_id('oz-id')
218
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
219
result = transform2.find_conflicts()
220
fp = FinalPaths(transform2)
221
self.assert_('oz/tip' in transform2._tree_path_ids)
222
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
223
self.assertEqual(len(result), 2)
224
self.assertEqual((result[0][0], result[0][1]),
225
('duplicate', newtip))
226
self.assertEqual((result[1][0], result[1][2]),
227
('duplicate id', newtip))
228
transform2.finalize()
229
transform3 = TreeTransform(self.wt)
230
self.addCleanup(transform3.finalize)
231
oz_id = transform3.trans_id_tree_file_id('oz-id')
232
transform3.delete_contents(oz_id)
233
self.assertEqual(transform3.find_conflicts(),
234
[('missing parent', oz_id)])
235
root_id = transform3.root
236
tip_id = transform3.trans_id_tree_file_id('tip-id')
237
transform3.adjust_path('tip', root_id, tip_id)
240
def test_add_del(self):
241
start, root = self.get_transform()
242
start.new_directory('a', root, 'a')
244
transform, root = self.get_transform()
245
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
246
transform.new_directory('a', root, 'a')
249
def test_unversioning(self):
250
create_tree, root = self.get_transform()
251
parent_id = create_tree.new_directory('parent', root, 'parent-id')
252
create_tree.new_file('child', parent_id, 'child', 'child-id')
254
unversion = TreeTransform(self.wt)
255
self.addCleanup(unversion.finalize)
256
parent = unversion.trans_id_tree_path('parent')
257
unversion.unversion_file(parent)
258
self.assertEqual(unversion.find_conflicts(),
259
[('unversioned parent', parent_id)])
260
file_id = unversion.trans_id_tree_file_id('child-id')
261
unversion.unversion_file(file_id)
264
def test_name_invariants(self):
265
create_tree, root = self.get_transform()
267
root = create_tree.root
268
create_tree.new_file('name1', root, 'hello1', 'name1')
269
create_tree.new_file('name2', root, 'hello2', 'name2')
270
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
271
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
272
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
273
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
276
mangle_tree,root = self.get_transform()
277
root = mangle_tree.root
279
name1 = mangle_tree.trans_id_tree_file_id('name1')
280
name2 = mangle_tree.trans_id_tree_file_id('name2')
281
mangle_tree.adjust_path('name2', root, name1)
282
mangle_tree.adjust_path('name1', root, name2)
284
#tests for deleting parent directories
285
ddir = mangle_tree.trans_id_tree_file_id('ddir')
286
mangle_tree.delete_contents(ddir)
287
dfile = mangle_tree.trans_id_tree_file_id('dfile')
288
mangle_tree.delete_versioned(dfile)
289
mangle_tree.unversion_file(dfile)
290
mfile = mangle_tree.trans_id_tree_file_id('mfile')
291
mangle_tree.adjust_path('mfile', root, mfile)
293
#tests for adding parent directories
294
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
295
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
296
mangle_tree.adjust_path('mfile2', newdir, mfile2)
297
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
298
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
299
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
300
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
302
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
303
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
304
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
305
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
306
self.assertEqual(file(mfile2_path).read(), 'later2')
307
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
308
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
309
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
310
self.assertEqual(file(newfile_path).read(), 'hello3')
311
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
312
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
313
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
315
def test_both_rename(self):
316
create_tree,root = self.get_transform()
317
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
318
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
320
mangle_tree,root = self.get_transform()
321
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
322
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
323
mangle_tree.adjust_path('test', root, selftest)
324
mangle_tree.adjust_path('test_too_much', root, selftest)
325
mangle_tree.set_executability(True, blackbox)
328
def test_both_rename2(self):
329
create_tree,root = self.get_transform()
330
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
331
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
332
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
333
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
336
mangle_tree,root = self.get_transform()
337
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
338
tests = mangle_tree.trans_id_tree_file_id('tests-id')
339
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
340
mangle_tree.adjust_path('selftest', bzrlib, tests)
341
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
342
mangle_tree.set_executability(True, test_too_much)
345
def test_both_rename3(self):
346
create_tree,root = self.get_transform()
347
tests = create_tree.new_directory('tests', root, 'tests-id')
348
create_tree.new_file('test_too_much.py', tests, 'hello1',
351
mangle_tree,root = self.get_transform()
352
tests = mangle_tree.trans_id_tree_file_id('tests-id')
353
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
354
mangle_tree.adjust_path('selftest', root, tests)
355
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
356
mangle_tree.set_executability(True, test_too_much)
359
def test_move_dangling_ie(self):
360
create_tree, root = self.get_transform()
362
root = create_tree.root
363
create_tree.new_file('name1', root, 'hello1', 'name1')
365
delete_contents, root = self.get_transform()
366
file = delete_contents.trans_id_tree_file_id('name1')
367
delete_contents.delete_contents(file)
368
delete_contents.apply()
369
move_id, root = self.get_transform()
370
name1 = move_id.trans_id_tree_file_id('name1')
371
newdir = move_id.new_directory('dir', root, 'newdir')
372
move_id.adjust_path('name2', newdir, name1)
375
def test_replace_dangling_ie(self):
376
create_tree, root = self.get_transform()
378
root = create_tree.root
379
create_tree.new_file('name1', root, 'hello1', 'name1')
381
delete_contents = TreeTransform(self.wt)
382
self.addCleanup(delete_contents.finalize)
383
file = delete_contents.trans_id_tree_file_id('name1')
384
delete_contents.delete_contents(file)
385
delete_contents.apply()
386
delete_contents.finalize()
387
replace = TreeTransform(self.wt)
388
self.addCleanup(replace.finalize)
389
name2 = replace.new_file('name2', root, 'hello2', 'name1')
390
conflicts = replace.find_conflicts()
391
name1 = replace.trans_id_tree_file_id('name1')
392
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
393
resolve_conflicts(replace)
396
def test_symlinks(self):
397
self.requireFeature(SymlinkFeature)
398
transform,root = self.get_transform()
399
oz_id = transform.new_directory('oz', root, 'oz-id')
400
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
402
wiz_id = transform.create_path('wizard2', oz_id)
403
transform.create_symlink('behind_curtain', wiz_id)
404
transform.version_file('wiz-id2', wiz_id)
405
transform.set_executability(True, wiz_id)
406
self.assertEqual(transform.find_conflicts(),
407
[('non-file executability', wiz_id)])
408
transform.set_executability(None, wiz_id)
410
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
411
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
412
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
414
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
417
def test_unable_create_symlink(self):
419
wt = self.make_branch_and_tree('.')
420
tt = TreeTransform(wt) # TreeTransform obtains write lock
422
tt.new_symlink('foo', tt.root, 'bar')
426
os_symlink = getattr(os, 'symlink', None)
429
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
431
"Unable to create symlink 'foo' on this platform",
435
os.symlink = os_symlink
437
def get_conflicted(self):
438
create,root = self.get_transform()
439
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
440
oz = create.new_directory('oz', root, 'oz-id')
441
create.new_directory('emeraldcity', oz, 'emerald-id')
443
conflicts,root = self.get_transform()
444
# set up duplicate entry, duplicate id
445
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
447
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
448
oz = conflicts.trans_id_tree_file_id('oz-id')
449
# set up DeletedParent parent conflict
450
conflicts.delete_versioned(oz)
451
emerald = conflicts.trans_id_tree_file_id('emerald-id')
452
# set up MissingParent conflict
453
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
454
conflicts.adjust_path('munchkincity', root, munchkincity)
455
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
457
conflicts.adjust_path('emeraldcity', emerald, emerald)
458
return conflicts, emerald, oz, old_dorothy, new_dorothy
460
def test_conflict_resolution(self):
461
conflicts, emerald, oz, old_dorothy, new_dorothy =\
462
self.get_conflicted()
463
resolve_conflicts(conflicts)
464
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
465
self.assertIs(conflicts.final_file_id(old_dorothy), None)
466
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
467
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
468
self.assertEqual(conflicts.final_parent(emerald), oz)
471
def test_cook_conflicts(self):
472
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
473
raw_conflicts = resolve_conflicts(tt)
474
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
475
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
476
'dorothy', None, 'dorothy-id')
477
self.assertEqual(cooked_conflicts[0], duplicate)
478
duplicate_id = DuplicateID('Unversioned existing file',
479
'dorothy.moved', 'dorothy', None,
481
self.assertEqual(cooked_conflicts[1], duplicate_id)
482
missing_parent = MissingParent('Created directory', 'munchkincity',
484
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
485
self.assertEqual(cooked_conflicts[2], missing_parent)
486
unversioned_parent = UnversionedParent('Versioned directory',
489
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
491
self.assertEqual(cooked_conflicts[3], unversioned_parent)
492
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
493
'oz/emeraldcity', 'emerald-id', 'emerald-id')
494
self.assertEqual(cooked_conflicts[4], deleted_parent)
495
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
496
self.assertEqual(cooked_conflicts[6], parent_loop)
497
self.assertEqual(len(cooked_conflicts), 7)
500
def test_string_conflicts(self):
501
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
502
raw_conflicts = resolve_conflicts(tt)
503
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
505
conflicts_s = [str(c) for c in cooked_conflicts]
506
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
507
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
508
'Moved existing file to '
510
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
511
'Unversioned existing file '
513
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
514
' munchkincity. Created directory.')
515
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
516
' versioned, but has versioned'
517
' children. Versioned directory.')
518
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
519
" is not empty. Not deleting.")
520
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
521
' versioned, but has versioned'
522
' children. Versioned directory.')
523
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
524
' oz/emeraldcity. Cancelled move.')
526
def test_moving_versioned_directories(self):
527
create, root = self.get_transform()
528
kansas = create.new_directory('kansas', root, 'kansas-id')
529
create.new_directory('house', kansas, 'house-id')
530
create.new_directory('oz', root, 'oz-id')
532
cyclone, root = self.get_transform()
533
oz = cyclone.trans_id_tree_file_id('oz-id')
534
house = cyclone.trans_id_tree_file_id('house-id')
535
cyclone.adjust_path('house', oz, house)
538
def test_moving_root(self):
539
create, root = self.get_transform()
540
fun = create.new_directory('fun', root, 'fun-id')
541
create.new_directory('sun', root, 'sun-id')
542
create.new_directory('moon', root, 'moon')
544
transform, root = self.get_transform()
545
transform.adjust_root_path('oldroot', fun)
546
new_root=transform.trans_id_tree_path('')
547
transform.version_file('new-root', new_root)
550
def test_renames(self):
551
create, root = self.get_transform()
552
old = create.new_directory('old-parent', root, 'old-id')
553
intermediate = create.new_directory('intermediate', old, 'im-id')
554
myfile = create.new_file('myfile', intermediate, 'myfile-text',
557
rename, root = self.get_transform()
558
old = rename.trans_id_file_id('old-id')
559
rename.adjust_path('new', root, old)
560
myfile = rename.trans_id_file_id('myfile-id')
561
rename.set_executability(True, myfile)
564
def test_find_interesting(self):
565
create, root = self.get_transform()
567
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
568
create.new_file('uvfile', root, 'othertext')
570
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
571
find_interesting, wt, wt, ['vfile'])
572
self.assertEqual(result, set(['myfile-id']))
574
def test_set_executability_order(self):
575
"""Ensure that executability behaves the same, no matter what order.
577
- create file and set executability simultaneously
578
- create file and set executability afterward
579
- unsetting the executability of a file whose executability has not been
580
declared should throw an exception (this may happen when a
581
merge attempts to create a file with a duplicate ID)
583
transform, root = self.get_transform()
586
self.addCleanup(wt.unlock)
587
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
589
sac = transform.new_file('set_after_creation', root,
590
'Set after creation', 'sac')
591
transform.set_executability(True, sac)
592
uws = transform.new_file('unset_without_set', root, 'Unset badly',
594
self.assertRaises(KeyError, transform.set_executability, None, uws)
596
self.assertTrue(wt.is_executable('soc'))
597
self.assertTrue(wt.is_executable('sac'))
599
def test_preserve_mode(self):
600
"""File mode is preserved when replacing content"""
601
if sys.platform == 'win32':
602
raise TestSkipped('chmod has no effect on win32')
603
transform, root = self.get_transform()
604
transform.new_file('file1', root, 'contents', 'file1-id', True)
606
self.assertTrue(self.wt.is_executable('file1-id'))
607
transform, root = self.get_transform()
608
file1_id = transform.trans_id_tree_file_id('file1-id')
609
transform.delete_contents(file1_id)
610
transform.create_file('contents2', file1_id)
612
self.assertTrue(self.wt.is_executable('file1-id'))
614
def test__set_mode_stats_correctly(self):
615
"""_set_mode stats to determine file mode."""
616
if sys.platform == 'win32':
617
raise TestSkipped('chmod has no effect on win32')
621
def instrumented_stat(path):
622
stat_paths.append(path)
623
return real_stat(path)
625
transform, root = self.get_transform()
627
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
628
file_id='bar-id-1', executable=False)
631
transform, root = self.get_transform()
632
bar1_id = transform.trans_id_tree_path('bar')
633
bar2_id = transform.trans_id_tree_path('bar2')
635
os.stat = instrumented_stat
636
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
641
bar1_abspath = self.wt.abspath('bar')
642
self.assertEqual([bar1_abspath], stat_paths)
644
def test_iter_changes(self):
645
self.wt.set_root_id('eert_toor')
646
transform, root = self.get_transform()
647
transform.new_file('old', root, 'blah', 'id-1', True)
649
transform, root = self.get_transform()
651
self.assertEqual([], list(transform._iter_changes()))
652
old = transform.trans_id_tree_file_id('id-1')
653
transform.unversion_file(old)
654
self.assertEqual([('id-1', ('old', None), False, (True, False),
655
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
656
(True, True))], list(transform._iter_changes()))
657
transform.new_directory('new', root, 'id-1')
658
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
659
('eert_toor', 'eert_toor'), ('old', 'new'),
660
('file', 'directory'),
661
(True, False))], list(transform._iter_changes()))
665
def test_iter_changes_new(self):
666
self.wt.set_root_id('eert_toor')
667
transform, root = self.get_transform()
668
transform.new_file('old', root, 'blah')
670
transform, root = self.get_transform()
672
old = transform.trans_id_tree_path('old')
673
transform.version_file('id-1', old)
674
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
675
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
676
(False, False))], list(transform._iter_changes()))
680
def test_iter_changes_modifications(self):
681
self.wt.set_root_id('eert_toor')
682
transform, root = self.get_transform()
683
transform.new_file('old', root, 'blah', 'id-1')
684
transform.new_file('new', root, 'blah')
685
transform.new_directory('subdir', root, 'subdir-id')
687
transform, root = self.get_transform()
689
old = transform.trans_id_tree_path('old')
690
subdir = transform.trans_id_tree_file_id('subdir-id')
691
new = transform.trans_id_tree_path('new')
692
self.assertEqual([], list(transform._iter_changes()))
695
transform.delete_contents(old)
696
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
697
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
698
(False, False))], list(transform._iter_changes()))
701
transform.create_file('blah', old)
702
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
703
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
704
(False, False))], list(transform._iter_changes()))
705
transform.cancel_deletion(old)
706
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
707
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
708
(False, False))], list(transform._iter_changes()))
709
transform.cancel_creation(old)
711
# move file_id to a different file
712
self.assertEqual([], list(transform._iter_changes()))
713
transform.unversion_file(old)
714
transform.version_file('id-1', new)
715
transform.adjust_path('old', root, new)
716
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
717
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
718
(False, False))], list(transform._iter_changes()))
719
transform.cancel_versioning(new)
720
transform._removed_id = set()
723
self.assertEqual([], list(transform._iter_changes()))
724
transform.set_executability(True, old)
725
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
726
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
727
(False, True))], list(transform._iter_changes()))
728
transform.set_executability(None, old)
731
self.assertEqual([], list(transform._iter_changes()))
732
transform.adjust_path('new', root, old)
733
transform._new_parent = {}
734
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
735
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
736
(False, False))], list(transform._iter_changes()))
737
transform._new_name = {}
740
self.assertEqual([], list(transform._iter_changes()))
741
transform.adjust_path('new', subdir, old)
742
transform._new_name = {}
743
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
744
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
745
('file', 'file'), (False, False))],
746
list(transform._iter_changes()))
747
transform._new_path = {}
752
def test_iter_changes_modified_bleed(self):
753
self.wt.set_root_id('eert_toor')
754
"""Modified flag should not bleed from one change to another"""
755
# unfortunately, we have no guarantee that file1 (which is modified)
756
# will be applied before file2. And if it's applied after file2, it
757
# obviously can't bleed into file2's change output. But for now, it
759
transform, root = self.get_transform()
760
transform.new_file('file1', root, 'blah', 'id-1')
761
transform.new_file('file2', root, 'blah', 'id-2')
763
transform, root = self.get_transform()
765
transform.delete_contents(transform.trans_id_file_id('id-1'))
766
transform.set_executability(True,
767
transform.trans_id_file_id('id-2'))
768
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
769
('eert_toor', 'eert_toor'), ('file1', u'file1'),
770
('file', None), (False, False)),
771
('id-2', (u'file2', u'file2'), False, (True, True),
772
('eert_toor', 'eert_toor'), ('file2', u'file2'),
773
('file', 'file'), (False, True))],
774
list(transform._iter_changes()))
778
def test_iter_changes_move_missing(self):
779
"""Test moving ids with no files around"""
780
self.wt.set_root_id('toor_eert')
781
# Need two steps because versioning a non-existant file is a conflict.
782
transform, root = self.get_transform()
783
transform.new_directory('floater', root, 'floater-id')
785
transform, root = self.get_transform()
786
transform.delete_contents(transform.trans_id_tree_path('floater'))
788
transform, root = self.get_transform()
789
floater = transform.trans_id_tree_path('floater')
791
transform.adjust_path('flitter', root, floater)
792
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
793
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
794
(None, None), (False, False))], list(transform._iter_changes()))
798
def test_iter_changes_pointless(self):
799
"""Ensure that no-ops are not treated as modifications"""
800
self.wt.set_root_id('eert_toor')
801
transform, root = self.get_transform()
802
transform.new_file('old', root, 'blah', 'id-1')
803
transform.new_directory('subdir', root, 'subdir-id')
805
transform, root = self.get_transform()
807
old = transform.trans_id_tree_path('old')
808
subdir = transform.trans_id_tree_file_id('subdir-id')
809
self.assertEqual([], list(transform._iter_changes()))
810
transform.delete_contents(subdir)
811
transform.create_directory(subdir)
812
transform.set_executability(False, old)
813
transform.unversion_file(old)
814
transform.version_file('id-1', old)
815
transform.adjust_path('old', root, old)
816
self.assertEqual([], list(transform._iter_changes()))
820
def test_rename_count(self):
821
transform, root = self.get_transform()
822
transform.new_file('name1', root, 'contents')
823
self.assertEqual(transform.rename_count, 0)
825
self.assertEqual(transform.rename_count, 1)
826
transform2, root = self.get_transform()
827
transform2.adjust_path('name2', root,
828
transform2.trans_id_tree_path('name1'))
829
self.assertEqual(transform2.rename_count, 0)
831
self.assertEqual(transform2.rename_count, 2)
833
def test_change_parent(self):
834
"""Ensure that after we change a parent, the results are still right.
836
Renames and parent changes on pending transforms can happen as part
837
of conflict resolution, and are explicitly permitted by the
840
This test ensures they work correctly with the rename-avoidance
843
transform, root = self.get_transform()
844
parent1 = transform.new_directory('parent1', root)
845
child1 = transform.new_file('child1', parent1, 'contents')
846
parent2 = transform.new_directory('parent2', root)
847
transform.adjust_path('child1', parent2, child1)
849
self.failIfExists(self.wt.abspath('parent1/child1'))
850
self.failUnlessExists(self.wt.abspath('parent2/child1'))
851
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
852
# no rename for child1 (counting only renames during apply)
853
self.failUnlessEqual(2, transform.rename_count)
855
def test_cancel_parent(self):
856
"""Cancelling a parent doesn't cause deletion of a non-empty directory
858
This is like the test_change_parent, except that we cancel the parent
859
before adjusting the path. The transform must detect that the
860
directory is non-empty, and move children to safe locations.
862
transform, root = self.get_transform()
863
parent1 = transform.new_directory('parent1', root)
864
child1 = transform.new_file('child1', parent1, 'contents')
865
child2 = transform.new_file('child2', parent1, 'contents')
867
transform.cancel_creation(parent1)
869
self.fail('Failed to move child1 before deleting parent1')
870
transform.cancel_creation(child2)
871
transform.create_directory(parent1)
873
transform.cancel_creation(parent1)
874
# If the transform incorrectly believes that child2 is still in
875
# parent1's limbo directory, it will try to rename it and fail
876
# because was already moved by the first cancel_creation.
878
self.fail('Transform still thinks child2 is a child of parent1')
879
parent2 = transform.new_directory('parent2', root)
880
transform.adjust_path('child1', parent2, child1)
882
self.failIfExists(self.wt.abspath('parent1'))
883
self.failUnlessExists(self.wt.abspath('parent2/child1'))
884
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
885
self.failUnlessEqual(2, transform.rename_count)
887
def test_adjust_and_cancel(self):
888
"""Make sure adjust_path keeps track of limbo children properly"""
889
transform, root = self.get_transform()
890
parent1 = transform.new_directory('parent1', root)
891
child1 = transform.new_file('child1', parent1, 'contents')
892
parent2 = transform.new_directory('parent2', root)
893
transform.adjust_path('child1', parent2, child1)
894
transform.cancel_creation(child1)
896
transform.cancel_creation(parent1)
897
# if the transform thinks child1 is still in parent1's limbo
898
# directory, it will attempt to move it and fail.
900
self.fail('Transform still thinks child1 is a child of parent1')
903
def test_noname_contents(self):
904
"""TreeTransform should permit deferring naming files."""
905
transform, root = self.get_transform()
906
parent = transform.trans_id_file_id('parent-id')
908
transform.create_directory(parent)
910
self.fail("Can't handle contents with no name")
913
def test_noname_contents_nested(self):
914
"""TreeTransform should permit deferring naming files."""
915
transform, root = self.get_transform()
916
parent = transform.trans_id_file_id('parent-id')
918
transform.create_directory(parent)
920
self.fail("Can't handle contents with no name")
921
child = transform.new_directory('child', parent)
922
transform.adjust_path('parent', root, parent)
924
self.failUnlessExists(self.wt.abspath('parent/child'))
925
self.assertEqual(1, transform.rename_count)
927
def test_reuse_name(self):
928
"""Avoid reusing the same limbo name for different files"""
929
transform, root = self.get_transform()
930
parent = transform.new_directory('parent', root)
931
child1 = transform.new_directory('child', parent)
933
child2 = transform.new_directory('child', parent)
935
self.fail('Tranform tried to use the same limbo name twice')
936
transform.adjust_path('child2', parent, child2)
938
# limbo/new-1 => parent, limbo/new-3 => parent/child2
939
# child2 is put into top-level limbo because child1 has already
940
# claimed the direct limbo path when child2 is created. There is no
941
# advantage in renaming files once they're in top-level limbo, except
943
self.assertEqual(2, transform.rename_count)
945
def test_reuse_when_first_moved(self):
946
"""Don't avoid direct paths when it is safe to use them"""
947
transform, root = self.get_transform()
948
parent = transform.new_directory('parent', root)
949
child1 = transform.new_directory('child', parent)
950
transform.adjust_path('child1', parent, child1)
951
child2 = transform.new_directory('child', parent)
953
# limbo/new-1 => parent
954
self.assertEqual(1, transform.rename_count)
956
def test_reuse_after_cancel(self):
957
"""Don't avoid direct paths when it is safe to use them"""
958
transform, root = self.get_transform()
959
parent2 = transform.new_directory('parent2', root)
960
child1 = transform.new_directory('child1', parent2)
961
transform.cancel_creation(parent2)
962
transform.create_directory(parent2)
963
child2 = transform.new_directory('child1', parent2)
964
transform.adjust_path('child2', parent2, child1)
966
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
967
self.assertEqual(2, transform.rename_count)
969
def test_finalize_order(self):
970
"""Finalize must be done in child-to-parent order"""
971
transform, root = self.get_transform()
972
parent = transform.new_directory('parent', root)
973
child = transform.new_directory('child', parent)
977
self.fail('Tried to remove parent before child1')
979
def test_cancel_with_cancelled_child_should_succeed(self):
980
transform, root = self.get_transform()
981
parent = transform.new_directory('parent', root)
982
child = transform.new_directory('child', parent)
983
transform.cancel_creation(child)
984
transform.cancel_creation(parent)
987
def test_change_entry(self):
988
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
989
self.callDeprecated([txt], change_entry, None, None, None, None, None,
993
class TransformGroup(object):
994
def __init__(self, dirname, root_id):
997
self.wt = BzrDir.create_standalone_workingtree(dirname)
998
self.wt.set_root_id(root_id)
999
self.b = self.wt.branch
1000
self.tt = TreeTransform(self.wt)
1001
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1004
def conflict_text(tree, merge):
1005
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1006
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1009
class TestTransformMerge(TestCaseInTempDir):
1010
def test_text_merge(self):
1011
root_id = generate_ids.gen_root_id()
1012
base = TransformGroup("base", root_id)
1013
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1014
base.tt.new_file('b', base.root, 'b1', 'b')
1015
base.tt.new_file('c', base.root, 'c', 'c')
1016
base.tt.new_file('d', base.root, 'd', 'd')
1017
base.tt.new_file('e', base.root, 'e', 'e')
1018
base.tt.new_file('f', base.root, 'f', 'f')
1019
base.tt.new_directory('g', base.root, 'g')
1020
base.tt.new_directory('h', base.root, 'h')
1022
other = TransformGroup("other", root_id)
1023
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1024
other.tt.new_file('b', other.root, 'b2', 'b')
1025
other.tt.new_file('c', other.root, 'c2', 'c')
1026
other.tt.new_file('d', other.root, 'd', 'd')
1027
other.tt.new_file('e', other.root, 'e2', 'e')
1028
other.tt.new_file('f', other.root, 'f', 'f')
1029
other.tt.new_file('g', other.root, 'g', 'g')
1030
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1031
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1033
this = TransformGroup("this", root_id)
1034
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1035
this.tt.new_file('b', this.root, 'b', 'b')
1036
this.tt.new_file('c', this.root, 'c', 'c')
1037
this.tt.new_file('d', this.root, 'd2', 'd')
1038
this.tt.new_file('e', this.root, 'e2', 'e')
1039
this.tt.new_file('f', this.root, 'f', 'f')
1040
this.tt.new_file('g', this.root, 'g', 'g')
1041
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1042
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1044
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1046
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1047
# three-way text conflict
1048
self.assertEqual(this.wt.get_file('b').read(),
1049
conflict_text('b', 'b2'))
1051
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1053
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1054
# Ambigious clean merge
1055
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1057
self.assertEqual(this.wt.get_file('f').read(), 'f')
1058
# Correct correct results when THIS == OTHER
1059
self.assertEqual(this.wt.get_file('g').read(), 'g')
1060
# Text conflict when THIS & OTHER are text and BASE is dir
1061
self.assertEqual(this.wt.get_file('h').read(),
1062
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1063
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1065
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1067
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1068
self.assertEqual(this.wt.get_file('i').read(),
1069
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1070
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1072
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1074
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1075
modified = ['a', 'b', 'c', 'h', 'i']
1076
merge_modified = this.wt.merge_modified()
1077
self.assertSubset(merge_modified, modified)
1078
self.assertEqual(len(merge_modified), len(modified))
1079
file(this.wt.id2abspath('a'), 'wb').write('booga')
1081
merge_modified = this.wt.merge_modified()
1082
self.assertSubset(merge_modified, modified)
1083
self.assertEqual(len(merge_modified), len(modified))
1087
def test_file_merge(self):
1088
self.requireFeature(SymlinkFeature)
1089
root_id = generate_ids.gen_root_id()
1090
base = TransformGroup("BASE", root_id)
1091
this = TransformGroup("THIS", root_id)
1092
other = TransformGroup("OTHER", root_id)
1093
for tg in this, base, other:
1094
tg.tt.new_directory('a', tg.root, 'a')
1095
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1096
tg.tt.new_file('c', tg.root, 'c', 'c')
1097
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1098
targets = ((base, 'base-e', 'base-f', None, None),
1099
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1100
(other, 'other-e', None, 'other-g', 'other-h'))
1101
for tg, e_target, f_target, g_target, h_target in targets:
1102
for link, target in (('e', e_target), ('f', f_target),
1103
('g', g_target), ('h', h_target)):
1104
if target is not None:
1105
tg.tt.new_symlink(link, tg.root, target, link)
1107
for tg in this, base, other:
1109
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1110
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1111
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1112
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1113
for suffix in ('THIS', 'BASE', 'OTHER'):
1114
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1115
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1116
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1117
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1118
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1119
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1120
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1121
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1122
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1123
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1124
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1125
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1126
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1127
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1129
def test_filename_merge(self):
1130
root_id = generate_ids.gen_root_id()
1131
base = TransformGroup("BASE", root_id)
1132
this = TransformGroup("THIS", root_id)
1133
other = TransformGroup("OTHER", root_id)
1134
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1135
for t in [base, this, other]]
1136
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1137
for t in [base, this, other]]
1138
base.tt.new_directory('c', base_a, 'c')
1139
this.tt.new_directory('c1', this_a, 'c')
1140
other.tt.new_directory('c', other_b, 'c')
1142
base.tt.new_directory('d', base_a, 'd')
1143
this.tt.new_directory('d1', this_b, 'd')
1144
other.tt.new_directory('d', other_a, 'd')
1146
base.tt.new_directory('e', base_a, 'e')
1147
this.tt.new_directory('e', this_a, 'e')
1148
other.tt.new_directory('e1', other_b, 'e')
1150
base.tt.new_directory('f', base_a, 'f')
1151
this.tt.new_directory('f1', this_b, 'f')
1152
other.tt.new_directory('f1', other_b, 'f')
1154
for tg in [this, base, other]:
1156
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1157
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1158
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1159
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1160
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1162
def test_filename_merge_conflicts(self):
1163
root_id = generate_ids.gen_root_id()
1164
base = TransformGroup("BASE", root_id)
1165
this = TransformGroup("THIS", root_id)
1166
other = TransformGroup("OTHER", root_id)
1167
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1168
for t in [base, this, other]]
1169
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1170
for t in [base, this, other]]
1172
base.tt.new_file('g', base_a, 'g', 'g')
1173
other.tt.new_file('g1', other_b, 'g1', 'g')
1175
base.tt.new_file('h', base_a, 'h', 'h')
1176
this.tt.new_file('h1', this_b, 'h1', 'h')
1178
base.tt.new_file('i', base.root, 'i', 'i')
1179
other.tt.new_directory('i1', this_b, 'i')
1181
for tg in [this, base, other]:
1183
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1185
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1186
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1187
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1188
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1189
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1190
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1191
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1194
class TestBuildTree(tests.TestCaseWithTransport):
1196
def test_build_tree_with_symlinks(self):
1197
self.requireFeature(SymlinkFeature)
1199
a = BzrDir.create_standalone_workingtree('a')
1201
file('a/foo/bar', 'wb').write('contents')
1202
os.symlink('a/foo/bar', 'a/foo/baz')
1203
a.add(['foo', 'foo/bar', 'foo/baz'])
1204
a.commit('initial commit')
1205
b = BzrDir.create_standalone_workingtree('b')
1206
basis = a.basis_tree()
1208
self.addCleanup(basis.unlock)
1209
build_tree(basis, b)
1210
self.assertIs(os.path.isdir('b/foo'), True)
1211
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1212
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1214
def test_build_with_references(self):
1215
tree = self.make_branch_and_tree('source',
1216
format='dirstate-with-subtree')
1217
subtree = self.make_branch_and_tree('source/subtree',
1218
format='dirstate-with-subtree')
1219
tree.add_reference(subtree)
1220
tree.commit('a revision')
1221
tree.branch.create_checkout('target')
1222
self.failUnlessExists('target')
1223
self.failUnlessExists('target/subtree')
1225
def test_file_conflict_handling(self):
1226
"""Ensure that when building trees, conflict handling is done"""
1227
source = self.make_branch_and_tree('source')
1228
target = self.make_branch_and_tree('target')
1229
self.build_tree(['source/file', 'target/file'])
1230
source.add('file', 'new-file')
1231
source.commit('added file')
1232
build_tree(source.basis_tree(), target)
1233
self.assertEqual([DuplicateEntry('Moved existing file to',
1234
'file.moved', 'file', None, 'new-file')],
1236
target2 = self.make_branch_and_tree('target2')
1237
target_file = file('target2/file', 'wb')
1239
source_file = file('source/file', 'rb')
1241
target_file.write(source_file.read())
1246
build_tree(source.basis_tree(), target2)
1247
self.assertEqual([], target2.conflicts())
1249
def test_symlink_conflict_handling(self):
1250
"""Ensure that when building trees, conflict handling is done"""
1251
self.requireFeature(SymlinkFeature)
1252
source = self.make_branch_and_tree('source')
1253
os.symlink('foo', 'source/symlink')
1254
source.add('symlink', 'new-symlink')
1255
source.commit('added file')
1256
target = self.make_branch_and_tree('target')
1257
os.symlink('bar', 'target/symlink')
1258
build_tree(source.basis_tree(), target)
1259
self.assertEqual([DuplicateEntry('Moved existing file to',
1260
'symlink.moved', 'symlink', None, 'new-symlink')],
1262
target = self.make_branch_and_tree('target2')
1263
os.symlink('foo', 'target2/symlink')
1264
build_tree(source.basis_tree(), target)
1265
self.assertEqual([], target.conflicts())
1267
def test_directory_conflict_handling(self):
1268
"""Ensure that when building trees, conflict handling is done"""
1269
source = self.make_branch_and_tree('source')
1270
target = self.make_branch_and_tree('target')
1271
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1272
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1273
source.commit('added file')
1274
build_tree(source.basis_tree(), target)
1275
self.assertEqual([], target.conflicts())
1276
self.failUnlessExists('target/dir1/file')
1278
# Ensure contents are merged
1279
target = self.make_branch_and_tree('target2')
1280
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1281
build_tree(source.basis_tree(), target)
1282
self.assertEqual([], target.conflicts())
1283
self.failUnlessExists('target2/dir1/file2')
1284
self.failUnlessExists('target2/dir1/file')
1286
# Ensure new contents are suppressed for existing branches
1287
target = self.make_branch_and_tree('target3')
1288
self.make_branch('target3/dir1')
1289
self.build_tree(['target3/dir1/file2'])
1290
build_tree(source.basis_tree(), target)
1291
self.failIfExists('target3/dir1/file')
1292
self.failUnlessExists('target3/dir1/file2')
1293
self.failUnlessExists('target3/dir1.diverted/file')
1294
self.assertEqual([DuplicateEntry('Diverted to',
1295
'dir1.diverted', 'dir1', 'new-dir1', None)],
1298
target = self.make_branch_and_tree('target4')
1299
self.build_tree(['target4/dir1/'])
1300
self.make_branch('target4/dir1/file')
1301
build_tree(source.basis_tree(), target)
1302
self.failUnlessExists('target4/dir1/file')
1303
self.assertEqual('directory', file_kind('target4/dir1/file'))
1304
self.failUnlessExists('target4/dir1/file.diverted')
1305
self.assertEqual([DuplicateEntry('Diverted to',
1306
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1309
def test_mixed_conflict_handling(self):
1310
"""Ensure that when building trees, conflict handling is done"""
1311
source = self.make_branch_and_tree('source')
1312
target = self.make_branch_and_tree('target')
1313
self.build_tree(['source/name', 'target/name/'])
1314
source.add('name', 'new-name')
1315
source.commit('added file')
1316
build_tree(source.basis_tree(), target)
1317
self.assertEqual([DuplicateEntry('Moved existing file to',
1318
'name.moved', 'name', None, 'new-name')], target.conflicts())
1320
def test_raises_in_populated(self):
1321
source = self.make_branch_and_tree('source')
1322
self.build_tree(['source/name'])
1324
source.commit('added name')
1325
target = self.make_branch_and_tree('target')
1326
self.build_tree(['target/name'])
1328
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1329
build_tree, source.basis_tree(), target)
1331
def test_build_tree_rename_count(self):
1332
source = self.make_branch_and_tree('source')
1333
self.build_tree(['source/file1', 'source/dir1/'])
1334
source.add(['file1', 'dir1'])
1335
source.commit('add1')
1336
target1 = self.make_branch_and_tree('target1')
1337
transform_result = build_tree(source.basis_tree(), target1)
1338
self.assertEqual(2, transform_result.rename_count)
1340
self.build_tree(['source/dir1/file2'])
1341
source.add(['dir1/file2'])
1342
source.commit('add3')
1343
target2 = self.make_branch_and_tree('target2')
1344
transform_result = build_tree(source.basis_tree(), target2)
1345
# children of non-root directories should not be renamed
1346
self.assertEqual(2, transform_result.rename_count)
1349
class MockTransform(object):
1351
def has_named_child(self, by_parent, parent_id, name):
1352
for child_id in by_parent[parent_id]:
1356
elif name == "name.~%s~" % child_id:
1361
class MockEntry(object):
1363
object.__init__(self)
1366
class TestGetBackupName(TestCase):
1367
def test_get_backup_name(self):
1368
tt = MockTransform()
1369
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1370
self.assertEqual(name, 'name.~1~')
1371
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1372
self.assertEqual(name, 'name.~2~')
1373
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1374
self.assertEqual(name, 'name.~1~')
1375
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1376
self.assertEqual(name, 'name.~1~')
1377
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1378
self.assertEqual(name, 'name.~4~')
1381
class TestFileMover(tests.TestCaseWithTransport):
1383
def test_file_mover(self):
1384
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1385
mover = _FileMover()
1386
mover.rename('a', 'q')
1387
self.failUnlessExists('q')
1388
self.failIfExists('a')
1389
self.failUnlessExists('q/b')
1390
self.failUnlessExists('c')
1391
self.failUnlessExists('c/d')
1393
def test_pre_delete_rollback(self):
1394
self.build_tree(['a/'])
1395
mover = _FileMover()
1396
mover.pre_delete('a', 'q')
1397
self.failUnlessExists('q')
1398
self.failIfExists('a')
1400
self.failIfExists('q')
1401
self.failUnlessExists('a')
1403
def test_apply_deletions(self):
1404
self.build_tree(['a/', 'b/'])
1405
mover = _FileMover()
1406
mover.pre_delete('a', 'q')
1407
mover.pre_delete('b', 'r')
1408
self.failUnlessExists('q')
1409
self.failUnlessExists('r')
1410
self.failIfExists('a')
1411
self.failIfExists('b')
1412
mover.apply_deletions()
1413
self.failIfExists('q')
1414
self.failIfExists('r')
1415
self.failIfExists('a')
1416
self.failIfExists('b')
1418
def test_file_mover_rollback(self):
1419
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1420
mover = _FileMover()
1421
mover.rename('c/d', 'c/f')
1422
mover.rename('c/e', 'c/d')
1424
mover.rename('a', 'c')
1427
self.failUnlessExists('a')
1428
self.failUnlessExists('c/d')
1431
class Bogus(Exception):
1435
class TestTransformRollback(tests.TestCaseWithTransport):
1437
class ExceptionFileMover(_FileMover):
1439
def __init__(self, bad_source=None, bad_target=None):
1440
_FileMover.__init__(self)
1441
self.bad_source = bad_source
1442
self.bad_target = bad_target
1444
def rename(self, source, target):
1445
if (self.bad_source is not None and
1446
source.endswith(self.bad_source)):
1448
elif (self.bad_target is not None and
1449
target.endswith(self.bad_target)):
1452
_FileMover.rename(self, source, target)
1454
def test_rollback_rename(self):
1455
tree = self.make_branch_and_tree('.')
1456
self.build_tree(['a/', 'a/b'])
1457
tt = TreeTransform(tree)
1458
self.addCleanup(tt.finalize)
1459
a_id = tt.trans_id_tree_path('a')
1460
tt.adjust_path('c', tt.root, a_id)
1461
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1462
self.assertRaises(Bogus, tt.apply,
1463
_mover=self.ExceptionFileMover(bad_source='a'))
1464
self.failUnlessExists('a')
1465
self.failUnlessExists('a/b')
1467
self.failUnlessExists('c')
1468
self.failUnlessExists('c/d')
1470
def test_rollback_rename_into_place(self):
1471
tree = self.make_branch_and_tree('.')
1472
self.build_tree(['a/', 'a/b'])
1473
tt = TreeTransform(tree)
1474
self.addCleanup(tt.finalize)
1475
a_id = tt.trans_id_tree_path('a')
1476
tt.adjust_path('c', tt.root, a_id)
1477
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1478
self.assertRaises(Bogus, tt.apply,
1479
_mover=self.ExceptionFileMover(bad_target='c/d'))
1480
self.failUnlessExists('a')
1481
self.failUnlessExists('a/b')
1483
self.failUnlessExists('c')
1484
self.failUnlessExists('c/d')
1486
def test_rollback_deletion(self):
1487
tree = self.make_branch_and_tree('.')
1488
self.build_tree(['a/', 'a/b'])
1489
tt = TreeTransform(tree)
1490
self.addCleanup(tt.finalize)
1491
a_id = tt.trans_id_tree_path('a')
1492
tt.delete_contents(a_id)
1493
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1494
self.assertRaises(Bogus, tt.apply,
1495
_mover=self.ExceptionFileMover(bad_target='d'))
1496
self.failUnlessExists('a')
1497
self.failUnlessExists('a/b')
1499
def test_resolve_no_parent(self):
1500
wt = self.make_branch_and_tree('.')
1501
tt = TreeTransform(wt)
1502
self.addCleanup(tt.finalize)
1503
parent = tt.trans_id_file_id('parent-id')
1504
tt.new_file('file', parent, 'Contents')
1505
resolve_conflicts(tt)