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 (
39
CaseInsensitiveFilesystemFeature,
46
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
47
resolve_conflicts, cook_conflicts,
48
find_interesting, build_tree, get_backup_name,
49
change_entry, _FileMover, resolve_checkout)
52
class TestTreeTransform(tests.TestCaseWithTransport):
55
super(TestTreeTransform, self).setUp()
56
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
59
def get_transform(self):
60
transform = TreeTransform(self.wt)
61
#self.addCleanup(transform.finalize)
62
return transform, transform.root
64
def test_existing_limbo(self):
65
transform, root = self.get_transform()
66
limbo_name = transform._limbodir
67
deletion_path = transform._deletiondir
68
os.mkdir(pathjoin(limbo_name, 'hehe'))
69
self.assertRaises(ImmortalLimbo, transform.apply)
70
self.assertRaises(LockError, self.wt.unlock)
71
self.assertRaises(ExistingLimbo, self.get_transform)
72
self.assertRaises(LockError, self.wt.unlock)
73
os.rmdir(pathjoin(limbo_name, 'hehe'))
75
os.rmdir(deletion_path)
76
transform, root = self.get_transform()
79
def test_existing_pending_deletion(self):
80
transform, root = self.get_transform()
81
deletion_path = self._limbodir = urlutils.local_path_from_url(
82
transform._tree._control_files.controlfilename('pending-deletion'))
83
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
84
self.assertRaises(ImmortalPendingDeletion, transform.apply)
85
self.assertRaises(LockError, self.wt.unlock)
86
self.assertRaises(ExistingPendingDeletion, self.get_transform)
89
transform, root = self.get_transform()
90
self.wt.lock_tree_write()
91
self.addCleanup(self.wt.unlock)
92
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
93
imaginary_id = transform.trans_id_tree_path('imaginary')
94
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
95
self.assertEqual(imaginary_id, imaginary_id2)
96
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
97
self.assertEqual(transform.final_kind(root), 'directory')
98
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
99
trans_id = transform.create_path('name', root)
100
self.assertIs(transform.final_file_id(trans_id), None)
101
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
102
transform.create_file('contents', trans_id)
103
transform.set_executability(True, trans_id)
104
transform.version_file('my_pretties', trans_id)
105
self.assertRaises(DuplicateKey, transform.version_file,
106
'my_pretties', trans_id)
107
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
108
self.assertEqual(transform.final_parent(trans_id), root)
109
self.assertIs(transform.final_parent(root), ROOT_PARENT)
110
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
111
oz_id = transform.create_path('oz', root)
112
transform.create_directory(oz_id)
113
transform.version_file('ozzie', oz_id)
114
trans_id2 = transform.create_path('name2', root)
115
transform.create_file('contents', trans_id2)
116
transform.set_executability(False, trans_id2)
117
transform.version_file('my_pretties2', trans_id2)
118
modified_paths = transform.apply().modified_paths
119
self.assertEqual('contents', self.wt.get_file_byname('name').read())
120
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
121
self.assertIs(self.wt.is_executable('my_pretties'), True)
122
self.assertIs(self.wt.is_executable('my_pretties2'), False)
123
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
124
self.assertEqual(len(modified_paths), 3)
125
tree_mod_paths = [self.wt.id2abspath(f) for f in
126
('ozzie', 'my_pretties', 'my_pretties2')]
127
self.assertSubset(tree_mod_paths, modified_paths)
128
# is it safe to finalize repeatedly?
132
def test_hardlink(self):
133
self.requireFeature(HardlinkFeature)
134
transform, root = self.get_transform()
135
transform.new_file('file1', root, 'contents')
137
target = self.make_branch_and_tree('target')
138
target_transform = TreeTransform(target)
139
trans_id = target_transform.create_path('file1', target_transform.root)
140
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
141
target_transform.apply()
142
self.failUnlessExists('target/file1')
143
source_stat = os.stat(self.wt.abspath('file1'))
144
target_stat = os.stat('target/file1')
145
self.assertEqual(source_stat, target_stat)
147
def test_convenience(self):
148
transform, root = self.get_transform()
149
self.wt.lock_tree_write()
150
self.addCleanup(self.wt.unlock)
151
trans_id = transform.new_file('name', root, 'contents',
153
oz = transform.new_directory('oz', root, 'oz-id')
154
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
155
toto = transform.new_file('toto', dorothy, 'toto-contents',
158
self.assertEqual(len(transform.find_conflicts()), 0)
160
self.assertRaises(ReusingTransform, transform.find_conflicts)
161
self.assertEqual('contents', file(self.wt.abspath('name')).read())
162
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
163
self.assertIs(self.wt.is_executable('my_pretties'), True)
164
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
165
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
166
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
168
self.assertEqual('toto-contents',
169
self.wt.get_file_byname('oz/dorothy/toto').read())
170
self.assertIs(self.wt.is_executable('toto-id'), False)
172
def test_tree_reference(self):
173
transform, root = self.get_transform()
174
tree = transform._tree
175
trans_id = transform.new_directory('reference', root, 'subtree-id')
176
transform.set_tree_reference('subtree-revision', trans_id)
179
self.addCleanup(tree.unlock)
180
self.assertEqual('subtree-revision',
181
tree.inventory['subtree-id'].reference_revision)
183
def test_conflicts(self):
184
transform, root = self.get_transform()
185
trans_id = transform.new_file('name', root, 'contents',
187
self.assertEqual(len(transform.find_conflicts()), 0)
188
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
189
self.assertEqual(transform.find_conflicts(),
190
[('duplicate', trans_id, trans_id2, 'name')])
191
self.assertRaises(MalformedTransform, transform.apply)
192
transform.adjust_path('name', trans_id, trans_id2)
193
self.assertEqual(transform.find_conflicts(),
194
[('non-directory parent', trans_id)])
195
tinman_id = transform.trans_id_tree_path('tinman')
196
transform.adjust_path('name', tinman_id, trans_id2)
197
self.assertEqual(transform.find_conflicts(),
198
[('unversioned parent', tinman_id),
199
('missing parent', tinman_id)])
200
lion_id = transform.create_path('lion', root)
201
self.assertEqual(transform.find_conflicts(),
202
[('unversioned parent', tinman_id),
203
('missing parent', tinman_id)])
204
transform.adjust_path('name', lion_id, trans_id2)
205
self.assertEqual(transform.find_conflicts(),
206
[('unversioned parent', lion_id),
207
('missing parent', lion_id)])
208
transform.version_file("Courage", lion_id)
209
self.assertEqual(transform.find_conflicts(),
210
[('missing parent', lion_id),
211
('versioning no contents', lion_id)])
212
transform.adjust_path('name2', root, trans_id2)
213
self.assertEqual(transform.find_conflicts(),
214
[('versioning no contents', lion_id)])
215
transform.create_file('Contents, okay?', lion_id)
216
transform.adjust_path('name2', trans_id2, trans_id2)
217
self.assertEqual(transform.find_conflicts(),
218
[('parent loop', trans_id2),
219
('non-directory parent', trans_id2)])
220
transform.adjust_path('name2', root, trans_id2)
221
oz_id = transform.new_directory('oz', root)
222
transform.set_executability(True, oz_id)
223
self.assertEqual(transform.find_conflicts(),
224
[('unversioned executability', oz_id)])
225
transform.version_file('oz-id', oz_id)
226
self.assertEqual(transform.find_conflicts(),
227
[('non-file executability', oz_id)])
228
transform.set_executability(None, oz_id)
229
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
231
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
232
self.assertEqual('contents', file(self.wt.abspath('name')).read())
233
transform2, root = self.get_transform()
234
oz_id = transform2.trans_id_tree_file_id('oz-id')
235
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
236
result = transform2.find_conflicts()
237
fp = FinalPaths(transform2)
238
self.assert_('oz/tip' in transform2._tree_path_ids)
239
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
240
self.assertEqual(len(result), 2)
241
self.assertEqual((result[0][0], result[0][1]),
242
('duplicate', newtip))
243
self.assertEqual((result[1][0], result[1][2]),
244
('duplicate id', newtip))
245
transform2.finalize()
246
transform3 = TreeTransform(self.wt)
247
self.addCleanup(transform3.finalize)
248
oz_id = transform3.trans_id_tree_file_id('oz-id')
249
transform3.delete_contents(oz_id)
250
self.assertEqual(transform3.find_conflicts(),
251
[('missing parent', oz_id)])
252
root_id = transform3.root
253
tip_id = transform3.trans_id_tree_file_id('tip-id')
254
transform3.adjust_path('tip', root_id, tip_id)
257
def test_conflict_on_case_insensitive(self):
258
tree = self.make_branch_and_tree('tree')
259
# Don't try this at home, kids!
260
# Force the tree to report that it is case sensitive, for conflict
262
tree.case_sensitive = True
263
transform = TreeTransform(tree)
264
self.addCleanup(transform.finalize)
265
transform.new_file('file', transform.root, 'content')
266
transform.new_file('FiLe', transform.root, 'content')
267
result = transform.find_conflicts()
268
self.assertEqual([], result)
269
# Force the tree to report that it is case insensitive, for conflict
271
tree.case_sensitive = False
272
result = transform.find_conflicts()
273
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
275
def test_conflict_on_case_insensitive_existing(self):
276
tree = self.make_branch_and_tree('tree')
277
self.build_tree(['tree/FiLe'])
278
# Don't try this at home, kids!
279
# Force the tree to report that it is case sensitive, for conflict
281
tree.case_sensitive = True
282
transform = TreeTransform(tree)
283
self.addCleanup(transform.finalize)
284
transform.new_file('file', transform.root, 'content')
285
result = transform.find_conflicts()
286
self.assertEqual([], result)
287
# Force the tree to report that it is case insensitive, for conflict
289
tree.case_sensitive = False
290
result = transform.find_conflicts()
291
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
293
def test_resolve_case_insensitive_conflict(self):
294
tree = self.make_branch_and_tree('tree')
295
# Don't try this at home, kids!
296
# Force the tree to report that it is case insensitive, for conflict
298
tree.case_sensitive = False
299
transform = TreeTransform(tree)
300
self.addCleanup(transform.finalize)
301
transform.new_file('file', transform.root, 'content')
302
transform.new_file('FiLe', transform.root, 'content')
303
resolve_conflicts(transform)
305
self.failUnlessExists('tree/file')
306
self.failUnlessExists('tree/FiLe.moved')
308
def test_resolve_checkout_case_conflict(self):
309
tree = self.make_branch_and_tree('tree')
310
# Don't try this at home, kids!
311
# Force the tree to report that it is case insensitive, for conflict
313
tree.case_sensitive = False
314
transform = TreeTransform(tree)
315
self.addCleanup(transform.finalize)
316
transform.new_file('file', transform.root, 'content')
317
transform.new_file('FiLe', transform.root, 'content')
318
resolve_conflicts(transform,
319
pass_func=lambda t, c: resolve_checkout(t, c, []))
321
self.failUnlessExists('tree/file')
322
self.failUnlessExists('tree/FiLe.moved')
324
def test_apply_case_conflict(self):
325
"""Ensure that a transform with case conflicts can always be applied"""
326
tree = self.make_branch_and_tree('tree')
327
transform = TreeTransform(tree)
328
self.addCleanup(transform.finalize)
329
transform.new_file('file', transform.root, 'content')
330
transform.new_file('FiLe', transform.root, 'content')
331
dir = transform.new_directory('dir', transform.root)
332
transform.new_file('dirfile', dir, 'content')
333
transform.new_file('dirFiLe', dir, 'content')
334
resolve_conflicts(transform)
336
self.failUnlessExists('tree/file')
337
if not os.path.exists('tree/FiLe.moved'):
338
self.failUnlessExists('tree/FiLe')
339
self.failUnlessExists('tree/dir/dirfile')
340
if not os.path.exists('tree/dir/dirFiLe.moved'):
341
self.failUnlessExists('tree/dir/dirFiLe')
343
def test_case_insensitive_limbo(self):
344
tree = self.make_branch_and_tree('tree')
345
# Don't try this at home, kids!
346
# Force the tree to report that it is case insensitive
347
tree.case_sensitive = False
348
transform = TreeTransform(tree)
349
self.addCleanup(transform.finalize)
350
dir = transform.new_directory('dir', transform.root)
351
first = transform.new_file('file', dir, 'content')
352
second = transform.new_file('FiLe', dir, 'content')
353
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
354
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
356
def test_add_del(self):
357
start, root = self.get_transform()
358
start.new_directory('a', root, 'a')
360
transform, root = self.get_transform()
361
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
362
transform.new_directory('a', root, 'a')
365
def test_unversioning(self):
366
create_tree, root = self.get_transform()
367
parent_id = create_tree.new_directory('parent', root, 'parent-id')
368
create_tree.new_file('child', parent_id, 'child', 'child-id')
370
unversion = TreeTransform(self.wt)
371
self.addCleanup(unversion.finalize)
372
parent = unversion.trans_id_tree_path('parent')
373
unversion.unversion_file(parent)
374
self.assertEqual(unversion.find_conflicts(),
375
[('unversioned parent', parent_id)])
376
file_id = unversion.trans_id_tree_file_id('child-id')
377
unversion.unversion_file(file_id)
380
def test_name_invariants(self):
381
create_tree, root = self.get_transform()
383
root = create_tree.root
384
create_tree.new_file('name1', root, 'hello1', 'name1')
385
create_tree.new_file('name2', root, 'hello2', 'name2')
386
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
387
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
388
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
389
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
392
mangle_tree,root = self.get_transform()
393
root = mangle_tree.root
395
name1 = mangle_tree.trans_id_tree_file_id('name1')
396
name2 = mangle_tree.trans_id_tree_file_id('name2')
397
mangle_tree.adjust_path('name2', root, name1)
398
mangle_tree.adjust_path('name1', root, name2)
400
#tests for deleting parent directories
401
ddir = mangle_tree.trans_id_tree_file_id('ddir')
402
mangle_tree.delete_contents(ddir)
403
dfile = mangle_tree.trans_id_tree_file_id('dfile')
404
mangle_tree.delete_versioned(dfile)
405
mangle_tree.unversion_file(dfile)
406
mfile = mangle_tree.trans_id_tree_file_id('mfile')
407
mangle_tree.adjust_path('mfile', root, mfile)
409
#tests for adding parent directories
410
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
411
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
412
mangle_tree.adjust_path('mfile2', newdir, mfile2)
413
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
414
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
415
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
416
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
418
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
419
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
420
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
421
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
422
self.assertEqual(file(mfile2_path).read(), 'later2')
423
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
424
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
425
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
426
self.assertEqual(file(newfile_path).read(), 'hello3')
427
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
428
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
429
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
431
def test_both_rename(self):
432
create_tree,root = self.get_transform()
433
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
434
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
436
mangle_tree,root = self.get_transform()
437
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
438
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
439
mangle_tree.adjust_path('test', root, selftest)
440
mangle_tree.adjust_path('test_too_much', root, selftest)
441
mangle_tree.set_executability(True, blackbox)
444
def test_both_rename2(self):
445
create_tree,root = self.get_transform()
446
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
447
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
448
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
449
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
452
mangle_tree,root = self.get_transform()
453
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
454
tests = mangle_tree.trans_id_tree_file_id('tests-id')
455
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
456
mangle_tree.adjust_path('selftest', bzrlib, tests)
457
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
458
mangle_tree.set_executability(True, test_too_much)
461
def test_both_rename3(self):
462
create_tree,root = self.get_transform()
463
tests = create_tree.new_directory('tests', root, 'tests-id')
464
create_tree.new_file('test_too_much.py', tests, 'hello1',
467
mangle_tree,root = self.get_transform()
468
tests = mangle_tree.trans_id_tree_file_id('tests-id')
469
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
470
mangle_tree.adjust_path('selftest', root, tests)
471
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
472
mangle_tree.set_executability(True, test_too_much)
475
def test_move_dangling_ie(self):
476
create_tree, root = self.get_transform()
478
root = create_tree.root
479
create_tree.new_file('name1', root, 'hello1', 'name1')
481
delete_contents, root = self.get_transform()
482
file = delete_contents.trans_id_tree_file_id('name1')
483
delete_contents.delete_contents(file)
484
delete_contents.apply()
485
move_id, root = self.get_transform()
486
name1 = move_id.trans_id_tree_file_id('name1')
487
newdir = move_id.new_directory('dir', root, 'newdir')
488
move_id.adjust_path('name2', newdir, name1)
491
def test_replace_dangling_ie(self):
492
create_tree, root = self.get_transform()
494
root = create_tree.root
495
create_tree.new_file('name1', root, 'hello1', 'name1')
497
delete_contents = TreeTransform(self.wt)
498
self.addCleanup(delete_contents.finalize)
499
file = delete_contents.trans_id_tree_file_id('name1')
500
delete_contents.delete_contents(file)
501
delete_contents.apply()
502
delete_contents.finalize()
503
replace = TreeTransform(self.wt)
504
self.addCleanup(replace.finalize)
505
name2 = replace.new_file('name2', root, 'hello2', 'name1')
506
conflicts = replace.find_conflicts()
507
name1 = replace.trans_id_tree_file_id('name1')
508
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
509
resolve_conflicts(replace)
512
def test_symlinks(self):
513
self.requireFeature(SymlinkFeature)
514
transform,root = self.get_transform()
515
oz_id = transform.new_directory('oz', root, 'oz-id')
516
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
518
wiz_id = transform.create_path('wizard2', oz_id)
519
transform.create_symlink('behind_curtain', wiz_id)
520
transform.version_file('wiz-id2', wiz_id)
521
transform.set_executability(True, wiz_id)
522
self.assertEqual(transform.find_conflicts(),
523
[('non-file executability', wiz_id)])
524
transform.set_executability(None, wiz_id)
526
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
527
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
528
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
530
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
533
def test_unable_create_symlink(self):
535
wt = self.make_branch_and_tree('.')
536
tt = TreeTransform(wt) # TreeTransform obtains write lock
538
tt.new_symlink('foo', tt.root, 'bar')
542
os_symlink = getattr(os, 'symlink', None)
545
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
547
"Unable to create symlink 'foo' on this platform",
551
os.symlink = os_symlink
553
def get_conflicted(self):
554
create,root = self.get_transform()
555
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
556
oz = create.new_directory('oz', root, 'oz-id')
557
create.new_directory('emeraldcity', oz, 'emerald-id')
559
conflicts,root = self.get_transform()
560
# set up duplicate entry, duplicate id
561
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
563
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
564
oz = conflicts.trans_id_tree_file_id('oz-id')
565
# set up DeletedParent parent conflict
566
conflicts.delete_versioned(oz)
567
emerald = conflicts.trans_id_tree_file_id('emerald-id')
568
# set up MissingParent conflict
569
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
570
conflicts.adjust_path('munchkincity', root, munchkincity)
571
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
573
conflicts.adjust_path('emeraldcity', emerald, emerald)
574
return conflicts, emerald, oz, old_dorothy, new_dorothy
576
def test_conflict_resolution(self):
577
conflicts, emerald, oz, old_dorothy, new_dorothy =\
578
self.get_conflicted()
579
resolve_conflicts(conflicts)
580
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
581
self.assertIs(conflicts.final_file_id(old_dorothy), None)
582
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
583
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
584
self.assertEqual(conflicts.final_parent(emerald), oz)
587
def test_cook_conflicts(self):
588
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
589
raw_conflicts = resolve_conflicts(tt)
590
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
591
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
592
'dorothy', None, 'dorothy-id')
593
self.assertEqual(cooked_conflicts[0], duplicate)
594
duplicate_id = DuplicateID('Unversioned existing file',
595
'dorothy.moved', 'dorothy', None,
597
self.assertEqual(cooked_conflicts[1], duplicate_id)
598
missing_parent = MissingParent('Created directory', 'munchkincity',
600
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
601
self.assertEqual(cooked_conflicts[2], missing_parent)
602
unversioned_parent = UnversionedParent('Versioned directory',
605
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
607
self.assertEqual(cooked_conflicts[3], unversioned_parent)
608
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
609
'oz/emeraldcity', 'emerald-id', 'emerald-id')
610
self.assertEqual(cooked_conflicts[4], deleted_parent)
611
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
612
self.assertEqual(cooked_conflicts[6], parent_loop)
613
self.assertEqual(len(cooked_conflicts), 7)
616
def test_string_conflicts(self):
617
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
618
raw_conflicts = resolve_conflicts(tt)
619
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
621
conflicts_s = [str(c) for c in cooked_conflicts]
622
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
623
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
624
'Moved existing file to '
626
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
627
'Unversioned existing file '
629
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
630
' munchkincity. Created directory.')
631
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
632
' versioned, but has versioned'
633
' children. Versioned directory.')
634
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
635
" is not empty. Not deleting.")
636
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
637
' versioned, but has versioned'
638
' children. Versioned directory.')
639
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
640
' oz/emeraldcity. Cancelled move.')
642
def test_moving_versioned_directories(self):
643
create, root = self.get_transform()
644
kansas = create.new_directory('kansas', root, 'kansas-id')
645
create.new_directory('house', kansas, 'house-id')
646
create.new_directory('oz', root, 'oz-id')
648
cyclone, root = self.get_transform()
649
oz = cyclone.trans_id_tree_file_id('oz-id')
650
house = cyclone.trans_id_tree_file_id('house-id')
651
cyclone.adjust_path('house', oz, house)
654
def test_moving_root(self):
655
create, root = self.get_transform()
656
fun = create.new_directory('fun', root, 'fun-id')
657
create.new_directory('sun', root, 'sun-id')
658
create.new_directory('moon', root, 'moon')
660
transform, root = self.get_transform()
661
transform.adjust_root_path('oldroot', fun)
662
new_root=transform.trans_id_tree_path('')
663
transform.version_file('new-root', new_root)
666
def test_renames(self):
667
create, root = self.get_transform()
668
old = create.new_directory('old-parent', root, 'old-id')
669
intermediate = create.new_directory('intermediate', old, 'im-id')
670
myfile = create.new_file('myfile', intermediate, 'myfile-text',
673
rename, root = self.get_transform()
674
old = rename.trans_id_file_id('old-id')
675
rename.adjust_path('new', root, old)
676
myfile = rename.trans_id_file_id('myfile-id')
677
rename.set_executability(True, myfile)
680
def test_find_interesting(self):
681
create, root = self.get_transform()
683
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
684
create.new_file('uvfile', root, 'othertext')
686
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
687
find_interesting, wt, wt, ['vfile'])
688
self.assertEqual(result, set(['myfile-id']))
690
def test_set_executability_order(self):
691
"""Ensure that executability behaves the same, no matter what order.
693
- create file and set executability simultaneously
694
- create file and set executability afterward
695
- unsetting the executability of a file whose executability has not been
696
declared should throw an exception (this may happen when a
697
merge attempts to create a file with a duplicate ID)
699
transform, root = self.get_transform()
702
self.addCleanup(wt.unlock)
703
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
705
sac = transform.new_file('set_after_creation', root,
706
'Set after creation', 'sac')
707
transform.set_executability(True, sac)
708
uws = transform.new_file('unset_without_set', root, 'Unset badly',
710
self.assertRaises(KeyError, transform.set_executability, None, uws)
712
self.assertTrue(wt.is_executable('soc'))
713
self.assertTrue(wt.is_executable('sac'))
715
def test_preserve_mode(self):
716
"""File mode is preserved when replacing content"""
717
if sys.platform == 'win32':
718
raise TestSkipped('chmod has no effect on win32')
719
transform, root = self.get_transform()
720
transform.new_file('file1', root, 'contents', 'file1-id', True)
722
self.assertTrue(self.wt.is_executable('file1-id'))
723
transform, root = self.get_transform()
724
file1_id = transform.trans_id_tree_file_id('file1-id')
725
transform.delete_contents(file1_id)
726
transform.create_file('contents2', file1_id)
728
self.assertTrue(self.wt.is_executable('file1-id'))
730
def test__set_mode_stats_correctly(self):
731
"""_set_mode stats to determine file mode."""
732
if sys.platform == 'win32':
733
raise TestSkipped('chmod has no effect on win32')
737
def instrumented_stat(path):
738
stat_paths.append(path)
739
return real_stat(path)
741
transform, root = self.get_transform()
743
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
744
file_id='bar-id-1', executable=False)
747
transform, root = self.get_transform()
748
bar1_id = transform.trans_id_tree_path('bar')
749
bar2_id = transform.trans_id_tree_path('bar2')
751
os.stat = instrumented_stat
752
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
757
bar1_abspath = self.wt.abspath('bar')
758
self.assertEqual([bar1_abspath], stat_paths)
760
def test_iter_changes(self):
761
self.wt.set_root_id('eert_toor')
762
transform, root = self.get_transform()
763
transform.new_file('old', root, 'blah', 'id-1', True)
765
transform, root = self.get_transform()
767
self.assertEqual([], list(transform._iter_changes()))
768
old = transform.trans_id_tree_file_id('id-1')
769
transform.unversion_file(old)
770
self.assertEqual([('id-1', ('old', None), False, (True, False),
771
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
772
(True, True))], list(transform._iter_changes()))
773
transform.new_directory('new', root, 'id-1')
774
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
775
('eert_toor', 'eert_toor'), ('old', 'new'),
776
('file', 'directory'),
777
(True, False))], list(transform._iter_changes()))
781
def test_iter_changes_new(self):
782
self.wt.set_root_id('eert_toor')
783
transform, root = self.get_transform()
784
transform.new_file('old', root, 'blah')
786
transform, root = self.get_transform()
788
old = transform.trans_id_tree_path('old')
789
transform.version_file('id-1', old)
790
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
791
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
792
(False, False))], list(transform._iter_changes()))
796
def test_iter_changes_modifications(self):
797
self.wt.set_root_id('eert_toor')
798
transform, root = self.get_transform()
799
transform.new_file('old', root, 'blah', 'id-1')
800
transform.new_file('new', root, 'blah')
801
transform.new_directory('subdir', root, 'subdir-id')
803
transform, root = self.get_transform()
805
old = transform.trans_id_tree_path('old')
806
subdir = transform.trans_id_tree_file_id('subdir-id')
807
new = transform.trans_id_tree_path('new')
808
self.assertEqual([], list(transform._iter_changes()))
811
transform.delete_contents(old)
812
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
813
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
814
(False, False))], list(transform._iter_changes()))
817
transform.create_file('blah', old)
818
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
819
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
820
(False, False))], list(transform._iter_changes()))
821
transform.cancel_deletion(old)
822
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
823
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
824
(False, False))], list(transform._iter_changes()))
825
transform.cancel_creation(old)
827
# move file_id to a different file
828
self.assertEqual([], list(transform._iter_changes()))
829
transform.unversion_file(old)
830
transform.version_file('id-1', new)
831
transform.adjust_path('old', root, new)
832
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
833
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
834
(False, False))], list(transform._iter_changes()))
835
transform.cancel_versioning(new)
836
transform._removed_id = set()
839
self.assertEqual([], list(transform._iter_changes()))
840
transform.set_executability(True, old)
841
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
842
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
843
(False, True))], list(transform._iter_changes()))
844
transform.set_executability(None, old)
847
self.assertEqual([], list(transform._iter_changes()))
848
transform.adjust_path('new', root, old)
849
transform._new_parent = {}
850
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
851
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
852
(False, False))], list(transform._iter_changes()))
853
transform._new_name = {}
856
self.assertEqual([], list(transform._iter_changes()))
857
transform.adjust_path('new', subdir, old)
858
transform._new_name = {}
859
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
860
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
861
('file', 'file'), (False, False))],
862
list(transform._iter_changes()))
863
transform._new_path = {}
868
def test_iter_changes_modified_bleed(self):
869
self.wt.set_root_id('eert_toor')
870
"""Modified flag should not bleed from one change to another"""
871
# unfortunately, we have no guarantee that file1 (which is modified)
872
# will be applied before file2. And if it's applied after file2, it
873
# obviously can't bleed into file2's change output. But for now, it
875
transform, root = self.get_transform()
876
transform.new_file('file1', root, 'blah', 'id-1')
877
transform.new_file('file2', root, 'blah', 'id-2')
879
transform, root = self.get_transform()
881
transform.delete_contents(transform.trans_id_file_id('id-1'))
882
transform.set_executability(True,
883
transform.trans_id_file_id('id-2'))
884
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
885
('eert_toor', 'eert_toor'), ('file1', u'file1'),
886
('file', None), (False, False)),
887
('id-2', (u'file2', u'file2'), False, (True, True),
888
('eert_toor', 'eert_toor'), ('file2', u'file2'),
889
('file', 'file'), (False, True))],
890
list(transform._iter_changes()))
894
def test_iter_changes_move_missing(self):
895
"""Test moving ids with no files around"""
896
self.wt.set_root_id('toor_eert')
897
# Need two steps because versioning a non-existant file is a conflict.
898
transform, root = self.get_transform()
899
transform.new_directory('floater', root, 'floater-id')
901
transform, root = self.get_transform()
902
transform.delete_contents(transform.trans_id_tree_path('floater'))
904
transform, root = self.get_transform()
905
floater = transform.trans_id_tree_path('floater')
907
transform.adjust_path('flitter', root, floater)
908
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
909
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
910
(None, None), (False, False))], list(transform._iter_changes()))
914
def test_iter_changes_pointless(self):
915
"""Ensure that no-ops are not treated as modifications"""
916
self.wt.set_root_id('eert_toor')
917
transform, root = self.get_transform()
918
transform.new_file('old', root, 'blah', 'id-1')
919
transform.new_directory('subdir', root, 'subdir-id')
921
transform, root = self.get_transform()
923
old = transform.trans_id_tree_path('old')
924
subdir = transform.trans_id_tree_file_id('subdir-id')
925
self.assertEqual([], list(transform._iter_changes()))
926
transform.delete_contents(subdir)
927
transform.create_directory(subdir)
928
transform.set_executability(False, old)
929
transform.unversion_file(old)
930
transform.version_file('id-1', old)
931
transform.adjust_path('old', root, old)
932
self.assertEqual([], list(transform._iter_changes()))
936
def test_rename_count(self):
937
transform, root = self.get_transform()
938
transform.new_file('name1', root, 'contents')
939
self.assertEqual(transform.rename_count, 0)
941
self.assertEqual(transform.rename_count, 1)
942
transform2, root = self.get_transform()
943
transform2.adjust_path('name2', root,
944
transform2.trans_id_tree_path('name1'))
945
self.assertEqual(transform2.rename_count, 0)
947
self.assertEqual(transform2.rename_count, 2)
949
def test_change_parent(self):
950
"""Ensure that after we change a parent, the results are still right.
952
Renames and parent changes on pending transforms can happen as part
953
of conflict resolution, and are explicitly permitted by the
956
This test ensures they work correctly with the rename-avoidance
959
transform, root = self.get_transform()
960
parent1 = transform.new_directory('parent1', root)
961
child1 = transform.new_file('child1', parent1, 'contents')
962
parent2 = transform.new_directory('parent2', root)
963
transform.adjust_path('child1', parent2, child1)
965
self.failIfExists(self.wt.abspath('parent1/child1'))
966
self.failUnlessExists(self.wt.abspath('parent2/child1'))
967
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
968
# no rename for child1 (counting only renames during apply)
969
self.failUnlessEqual(2, transform.rename_count)
971
def test_cancel_parent(self):
972
"""Cancelling a parent doesn't cause deletion of a non-empty directory
974
This is like the test_change_parent, except that we cancel the parent
975
before adjusting the path. The transform must detect that the
976
directory is non-empty, and move children to safe locations.
978
transform, root = self.get_transform()
979
parent1 = transform.new_directory('parent1', root)
980
child1 = transform.new_file('child1', parent1, 'contents')
981
child2 = transform.new_file('child2', parent1, 'contents')
983
transform.cancel_creation(parent1)
985
self.fail('Failed to move child1 before deleting parent1')
986
transform.cancel_creation(child2)
987
transform.create_directory(parent1)
989
transform.cancel_creation(parent1)
990
# If the transform incorrectly believes that child2 is still in
991
# parent1's limbo directory, it will try to rename it and fail
992
# because was already moved by the first cancel_creation.
994
self.fail('Transform still thinks child2 is a child of parent1')
995
parent2 = transform.new_directory('parent2', root)
996
transform.adjust_path('child1', parent2, child1)
998
self.failIfExists(self.wt.abspath('parent1'))
999
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1000
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1001
self.failUnlessEqual(2, transform.rename_count)
1003
def test_adjust_and_cancel(self):
1004
"""Make sure adjust_path keeps track of limbo children properly"""
1005
transform, root = self.get_transform()
1006
parent1 = transform.new_directory('parent1', root)
1007
child1 = transform.new_file('child1', parent1, 'contents')
1008
parent2 = transform.new_directory('parent2', root)
1009
transform.adjust_path('child1', parent2, child1)
1010
transform.cancel_creation(child1)
1012
transform.cancel_creation(parent1)
1013
# if the transform thinks child1 is still in parent1's limbo
1014
# directory, it will attempt to move it and fail.
1016
self.fail('Transform still thinks child1 is a child of parent1')
1017
transform.finalize()
1019
def test_noname_contents(self):
1020
"""TreeTransform should permit deferring naming files."""
1021
transform, root = self.get_transform()
1022
parent = transform.trans_id_file_id('parent-id')
1024
transform.create_directory(parent)
1026
self.fail("Can't handle contents with no name")
1027
transform.finalize()
1029
def test_noname_contents_nested(self):
1030
"""TreeTransform should permit deferring naming files."""
1031
transform, root = self.get_transform()
1032
parent = transform.trans_id_file_id('parent-id')
1034
transform.create_directory(parent)
1036
self.fail("Can't handle contents with no name")
1037
child = transform.new_directory('child', parent)
1038
transform.adjust_path('parent', root, parent)
1040
self.failUnlessExists(self.wt.abspath('parent/child'))
1041
self.assertEqual(1, transform.rename_count)
1043
def test_reuse_name(self):
1044
"""Avoid reusing the same limbo name for different files"""
1045
transform, root = self.get_transform()
1046
parent = transform.new_directory('parent', root)
1047
child1 = transform.new_directory('child', parent)
1049
child2 = transform.new_directory('child', parent)
1051
self.fail('Tranform tried to use the same limbo name twice')
1052
transform.adjust_path('child2', parent, child2)
1054
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1055
# child2 is put into top-level limbo because child1 has already
1056
# claimed the direct limbo path when child2 is created. There is no
1057
# advantage in renaming files once they're in top-level limbo, except
1059
self.assertEqual(2, transform.rename_count)
1061
def test_reuse_when_first_moved(self):
1062
"""Don't avoid direct paths when it is safe to use them"""
1063
transform, root = self.get_transform()
1064
parent = transform.new_directory('parent', root)
1065
child1 = transform.new_directory('child', parent)
1066
transform.adjust_path('child1', parent, child1)
1067
child2 = transform.new_directory('child', parent)
1069
# limbo/new-1 => parent
1070
self.assertEqual(1, transform.rename_count)
1072
def test_reuse_after_cancel(self):
1073
"""Don't avoid direct paths when it is safe to use them"""
1074
transform, root = self.get_transform()
1075
parent2 = transform.new_directory('parent2', root)
1076
child1 = transform.new_directory('child1', parent2)
1077
transform.cancel_creation(parent2)
1078
transform.create_directory(parent2)
1079
child2 = transform.new_directory('child1', parent2)
1080
transform.adjust_path('child2', parent2, child1)
1082
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1083
self.assertEqual(2, transform.rename_count)
1085
def test_finalize_order(self):
1086
"""Finalize must be done in child-to-parent order"""
1087
transform, root = self.get_transform()
1088
parent = transform.new_directory('parent', root)
1089
child = transform.new_directory('child', parent)
1091
transform.finalize()
1093
self.fail('Tried to remove parent before child1')
1095
def test_cancel_with_cancelled_child_should_succeed(self):
1096
transform, root = self.get_transform()
1097
parent = transform.new_directory('parent', root)
1098
child = transform.new_directory('child', parent)
1099
transform.cancel_creation(child)
1100
transform.cancel_creation(parent)
1101
transform.finalize()
1103
def test_change_entry(self):
1104
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1105
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1108
def test_case_insensitive_clash(self):
1109
self.requireFeature(CaseInsensitiveFilesystemFeature)
1111
wt = self.make_branch_and_tree('.')
1112
tt = TreeTransform(wt) # TreeTransform obtains write lock
1114
tt.new_file('foo', tt.root, 'bar')
1115
tt.new_file('Foo', tt.root, 'spam')
1116
# Lie to tt that we've already resolved all conflicts.
1117
tt.apply(no_conflicts=True)
1121
err = self.assertRaises(errors.FileExists, tt_helper)
1122
self.assertContainsRe(str(err),
1123
"^File exists: .+/foo")
1125
def test_two_directories_clash(self):
1127
wt = self.make_branch_and_tree('.')
1128
tt = TreeTransform(wt) # TreeTransform obtains write lock
1130
foo_1 = tt.new_directory('foo', tt.root)
1131
tt.new_directory('bar', foo_1)
1132
foo_2 = tt.new_directory('foo', tt.root)
1133
tt.new_directory('baz', foo_2)
1134
# Lie to tt that we've already resolved all conflicts.
1135
tt.apply(no_conflicts=True)
1139
err = self.assertRaises(errors.FileExists, tt_helper)
1140
self.assertContainsRe(str(err),
1141
"^File exists: .+/foo")
1143
def test_two_directories_clash_finalize(self):
1145
wt = self.make_branch_and_tree('.')
1146
tt = TreeTransform(wt) # TreeTransform obtains write lock
1148
foo_1 = tt.new_directory('foo', tt.root)
1149
tt.new_directory('bar', foo_1)
1150
foo_2 = tt.new_directory('foo', tt.root)
1151
tt.new_directory('baz', foo_2)
1152
# Lie to tt that we've already resolved all conflicts.
1153
tt.apply(no_conflicts=True)
1157
err = self.assertRaises(errors.FileExists, tt_helper)
1158
self.assertContainsRe(str(err),
1159
"^File exists: .+/foo")
1162
class TransformGroup(object):
1164
def __init__(self, dirname, root_id):
1167
self.wt = BzrDir.create_standalone_workingtree(dirname)
1168
self.wt.set_root_id(root_id)
1169
self.b = self.wt.branch
1170
self.tt = TreeTransform(self.wt)
1171
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1174
def conflict_text(tree, merge):
1175
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1176
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1179
class TestTransformMerge(TestCaseInTempDir):
1180
def test_text_merge(self):
1181
root_id = generate_ids.gen_root_id()
1182
base = TransformGroup("base", root_id)
1183
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1184
base.tt.new_file('b', base.root, 'b1', 'b')
1185
base.tt.new_file('c', base.root, 'c', 'c')
1186
base.tt.new_file('d', base.root, 'd', 'd')
1187
base.tt.new_file('e', base.root, 'e', 'e')
1188
base.tt.new_file('f', base.root, 'f', 'f')
1189
base.tt.new_directory('g', base.root, 'g')
1190
base.tt.new_directory('h', base.root, 'h')
1192
other = TransformGroup("other", root_id)
1193
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1194
other.tt.new_file('b', other.root, 'b2', 'b')
1195
other.tt.new_file('c', other.root, 'c2', 'c')
1196
other.tt.new_file('d', other.root, 'd', 'd')
1197
other.tt.new_file('e', other.root, 'e2', 'e')
1198
other.tt.new_file('f', other.root, 'f', 'f')
1199
other.tt.new_file('g', other.root, 'g', 'g')
1200
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1201
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1203
this = TransformGroup("this", root_id)
1204
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1205
this.tt.new_file('b', this.root, 'b', 'b')
1206
this.tt.new_file('c', this.root, 'c', 'c')
1207
this.tt.new_file('d', this.root, 'd2', 'd')
1208
this.tt.new_file('e', this.root, 'e2', 'e')
1209
this.tt.new_file('f', this.root, 'f', 'f')
1210
this.tt.new_file('g', this.root, 'g', 'g')
1211
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1212
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1214
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1216
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1217
# three-way text conflict
1218
self.assertEqual(this.wt.get_file('b').read(),
1219
conflict_text('b', 'b2'))
1221
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1223
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1224
# Ambigious clean merge
1225
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1227
self.assertEqual(this.wt.get_file('f').read(), 'f')
1228
# Correct correct results when THIS == OTHER
1229
self.assertEqual(this.wt.get_file('g').read(), 'g')
1230
# Text conflict when THIS & OTHER are text and BASE is dir
1231
self.assertEqual(this.wt.get_file('h').read(),
1232
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1233
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1235
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1237
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1238
self.assertEqual(this.wt.get_file('i').read(),
1239
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1240
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1242
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1244
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1245
modified = ['a', 'b', 'c', 'h', 'i']
1246
merge_modified = this.wt.merge_modified()
1247
self.assertSubset(merge_modified, modified)
1248
self.assertEqual(len(merge_modified), len(modified))
1249
file(this.wt.id2abspath('a'), 'wb').write('booga')
1251
merge_modified = this.wt.merge_modified()
1252
self.assertSubset(merge_modified, modified)
1253
self.assertEqual(len(merge_modified), len(modified))
1257
def test_file_merge(self):
1258
self.requireFeature(SymlinkFeature)
1259
root_id = generate_ids.gen_root_id()
1260
base = TransformGroup("BASE", root_id)
1261
this = TransformGroup("THIS", root_id)
1262
other = TransformGroup("OTHER", root_id)
1263
for tg in this, base, other:
1264
tg.tt.new_directory('a', tg.root, 'a')
1265
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1266
tg.tt.new_file('c', tg.root, 'c', 'c')
1267
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1268
targets = ((base, 'base-e', 'base-f', None, None),
1269
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1270
(other, 'other-e', None, 'other-g', 'other-h'))
1271
for tg, e_target, f_target, g_target, h_target in targets:
1272
for link, target in (('e', e_target), ('f', f_target),
1273
('g', g_target), ('h', h_target)):
1274
if target is not None:
1275
tg.tt.new_symlink(link, tg.root, target, link)
1277
for tg in this, base, other:
1279
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1280
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1281
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1282
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1283
for suffix in ('THIS', 'BASE', 'OTHER'):
1284
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1285
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1286
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1287
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1288
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1289
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1290
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1291
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1292
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1293
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1294
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1295
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1296
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1297
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1299
def test_filename_merge(self):
1300
root_id = generate_ids.gen_root_id()
1301
base = TransformGroup("BASE", root_id)
1302
this = TransformGroup("THIS", root_id)
1303
other = TransformGroup("OTHER", root_id)
1304
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1305
for t in [base, this, other]]
1306
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1307
for t in [base, this, other]]
1308
base.tt.new_directory('c', base_a, 'c')
1309
this.tt.new_directory('c1', this_a, 'c')
1310
other.tt.new_directory('c', other_b, 'c')
1312
base.tt.new_directory('d', base_a, 'd')
1313
this.tt.new_directory('d1', this_b, 'd')
1314
other.tt.new_directory('d', other_a, 'd')
1316
base.tt.new_directory('e', base_a, 'e')
1317
this.tt.new_directory('e', this_a, 'e')
1318
other.tt.new_directory('e1', other_b, 'e')
1320
base.tt.new_directory('f', base_a, 'f')
1321
this.tt.new_directory('f1', this_b, 'f')
1322
other.tt.new_directory('f1', other_b, 'f')
1324
for tg in [this, base, other]:
1326
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1327
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1328
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1329
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1330
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1332
def test_filename_merge_conflicts(self):
1333
root_id = generate_ids.gen_root_id()
1334
base = TransformGroup("BASE", root_id)
1335
this = TransformGroup("THIS", root_id)
1336
other = TransformGroup("OTHER", root_id)
1337
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1338
for t in [base, this, other]]
1339
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1340
for t in [base, this, other]]
1342
base.tt.new_file('g', base_a, 'g', 'g')
1343
other.tt.new_file('g1', other_b, 'g1', 'g')
1345
base.tt.new_file('h', base_a, 'h', 'h')
1346
this.tt.new_file('h1', this_b, 'h1', 'h')
1348
base.tt.new_file('i', base.root, 'i', 'i')
1349
other.tt.new_directory('i1', this_b, 'i')
1351
for tg in [this, base, other]:
1353
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1355
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1356
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1357
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1358
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1359
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1360
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1361
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1364
class TestBuildTree(tests.TestCaseWithTransport):
1366
def test_build_tree_with_symlinks(self):
1367
self.requireFeature(SymlinkFeature)
1369
a = BzrDir.create_standalone_workingtree('a')
1371
file('a/foo/bar', 'wb').write('contents')
1372
os.symlink('a/foo/bar', 'a/foo/baz')
1373
a.add(['foo', 'foo/bar', 'foo/baz'])
1374
a.commit('initial commit')
1375
b = BzrDir.create_standalone_workingtree('b')
1376
basis = a.basis_tree()
1378
self.addCleanup(basis.unlock)
1379
build_tree(basis, b)
1380
self.assertIs(os.path.isdir('b/foo'), True)
1381
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1382
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1384
def test_build_with_references(self):
1385
tree = self.make_branch_and_tree('source',
1386
format='dirstate-with-subtree')
1387
subtree = self.make_branch_and_tree('source/subtree',
1388
format='dirstate-with-subtree')
1389
tree.add_reference(subtree)
1390
tree.commit('a revision')
1391
tree.branch.create_checkout('target')
1392
self.failUnlessExists('target')
1393
self.failUnlessExists('target/subtree')
1395
def test_file_conflict_handling(self):
1396
"""Ensure that when building trees, conflict handling is done"""
1397
source = self.make_branch_and_tree('source')
1398
target = self.make_branch_and_tree('target')
1399
self.build_tree(['source/file', 'target/file'])
1400
source.add('file', 'new-file')
1401
source.commit('added file')
1402
build_tree(source.basis_tree(), target)
1403
self.assertEqual([DuplicateEntry('Moved existing file to',
1404
'file.moved', 'file', None, 'new-file')],
1406
target2 = self.make_branch_and_tree('target2')
1407
target_file = file('target2/file', 'wb')
1409
source_file = file('source/file', 'rb')
1411
target_file.write(source_file.read())
1416
build_tree(source.basis_tree(), target2)
1417
self.assertEqual([], target2.conflicts())
1419
def test_symlink_conflict_handling(self):
1420
"""Ensure that when building trees, conflict handling is done"""
1421
self.requireFeature(SymlinkFeature)
1422
source = self.make_branch_and_tree('source')
1423
os.symlink('foo', 'source/symlink')
1424
source.add('symlink', 'new-symlink')
1425
source.commit('added file')
1426
target = self.make_branch_and_tree('target')
1427
os.symlink('bar', 'target/symlink')
1428
build_tree(source.basis_tree(), target)
1429
self.assertEqual([DuplicateEntry('Moved existing file to',
1430
'symlink.moved', 'symlink', None, 'new-symlink')],
1432
target = self.make_branch_and_tree('target2')
1433
os.symlink('foo', 'target2/symlink')
1434
build_tree(source.basis_tree(), target)
1435
self.assertEqual([], target.conflicts())
1437
def test_directory_conflict_handling(self):
1438
"""Ensure that when building trees, conflict handling is done"""
1439
source = self.make_branch_and_tree('source')
1440
target = self.make_branch_and_tree('target')
1441
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1442
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1443
source.commit('added file')
1444
build_tree(source.basis_tree(), target)
1445
self.assertEqual([], target.conflicts())
1446
self.failUnlessExists('target/dir1/file')
1448
# Ensure contents are merged
1449
target = self.make_branch_and_tree('target2')
1450
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1451
build_tree(source.basis_tree(), target)
1452
self.assertEqual([], target.conflicts())
1453
self.failUnlessExists('target2/dir1/file2')
1454
self.failUnlessExists('target2/dir1/file')
1456
# Ensure new contents are suppressed for existing branches
1457
target = self.make_branch_and_tree('target3')
1458
self.make_branch('target3/dir1')
1459
self.build_tree(['target3/dir1/file2'])
1460
build_tree(source.basis_tree(), target)
1461
self.failIfExists('target3/dir1/file')
1462
self.failUnlessExists('target3/dir1/file2')
1463
self.failUnlessExists('target3/dir1.diverted/file')
1464
self.assertEqual([DuplicateEntry('Diverted to',
1465
'dir1.diverted', 'dir1', 'new-dir1', None)],
1468
target = self.make_branch_and_tree('target4')
1469
self.build_tree(['target4/dir1/'])
1470
self.make_branch('target4/dir1/file')
1471
build_tree(source.basis_tree(), target)
1472
self.failUnlessExists('target4/dir1/file')
1473
self.assertEqual('directory', file_kind('target4/dir1/file'))
1474
self.failUnlessExists('target4/dir1/file.diverted')
1475
self.assertEqual([DuplicateEntry('Diverted to',
1476
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1479
def test_mixed_conflict_handling(self):
1480
"""Ensure that when building trees, conflict handling is done"""
1481
source = self.make_branch_and_tree('source')
1482
target = self.make_branch_and_tree('target')
1483
self.build_tree(['source/name', 'target/name/'])
1484
source.add('name', 'new-name')
1485
source.commit('added file')
1486
build_tree(source.basis_tree(), target)
1487
self.assertEqual([DuplicateEntry('Moved existing file to',
1488
'name.moved', 'name', None, 'new-name')], target.conflicts())
1490
def test_raises_in_populated(self):
1491
source = self.make_branch_and_tree('source')
1492
self.build_tree(['source/name'])
1494
source.commit('added name')
1495
target = self.make_branch_and_tree('target')
1496
self.build_tree(['target/name'])
1498
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1499
build_tree, source.basis_tree(), target)
1501
def test_build_tree_rename_count(self):
1502
source = self.make_branch_and_tree('source')
1503
self.build_tree(['source/file1', 'source/dir1/'])
1504
source.add(['file1', 'dir1'])
1505
source.commit('add1')
1506
target1 = self.make_branch_and_tree('target1')
1507
transform_result = build_tree(source.basis_tree(), target1)
1508
self.assertEqual(2, transform_result.rename_count)
1510
self.build_tree(['source/dir1/file2'])
1511
source.add(['dir1/file2'])
1512
source.commit('add3')
1513
target2 = self.make_branch_and_tree('target2')
1514
transform_result = build_tree(source.basis_tree(), target2)
1515
# children of non-root directories should not be renamed
1516
self.assertEqual(2, transform_result.rename_count)
1518
def create_ab_tree(self):
1519
"""Create a committed test tree with two files"""
1520
source = self.make_branch_and_tree('source')
1521
self.build_tree_contents([('source/file1', 'A')])
1522
self.build_tree_contents([('source/file2', 'B')])
1523
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1524
source.commit('commit files')
1526
self.addCleanup(source.unlock)
1529
def test_build_tree_accelerator_tree(self):
1530
source = self.create_ab_tree()
1531
self.build_tree_contents([('source/file2', 'C')])
1533
real_source_get_file = source.get_file
1534
def get_file(file_id, path=None):
1535
calls.append(file_id)
1536
return real_source_get_file(file_id, path)
1537
source.get_file = get_file
1538
target = self.make_branch_and_tree('target')
1539
revision_tree = source.basis_tree()
1540
revision_tree.lock_read()
1541
self.addCleanup(revision_tree.unlock)
1542
build_tree(revision_tree, target, source)
1543
self.assertEqual(['file1-id'], calls)
1545
self.addCleanup(target.unlock)
1546
self.assertEqual([], list(target._iter_changes(revision_tree)))
1548
def test_build_tree_accelerator_tree_missing_file(self):
1549
source = self.create_ab_tree()
1550
os.unlink('source/file1')
1551
source.remove(['file2'])
1552
target = self.make_branch_and_tree('target')
1553
revision_tree = source.basis_tree()
1554
revision_tree.lock_read()
1555
self.addCleanup(revision_tree.unlock)
1556
build_tree(revision_tree, target, source)
1558
self.addCleanup(target.unlock)
1559
self.assertEqual([], list(target._iter_changes(revision_tree)))
1561
def test_build_tree_accelerator_wrong_kind(self):
1562
source = self.make_branch_and_tree('source')
1563
self.build_tree_contents([('source/file1', '')])
1564
self.build_tree_contents([('source/file2', '')])
1565
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1566
source.commit('commit files')
1567
os.unlink('source/file2')
1568
self.build_tree_contents([('source/file2/', 'C')])
1569
os.unlink('source/file1')
1570
os.symlink('file2', 'source/file1')
1572
real_source_get_file = source.get_file
1573
def get_file(file_id, path=None):
1574
calls.append(file_id)
1575
return real_source_get_file(file_id, path)
1576
source.get_file = get_file
1577
target = self.make_branch_and_tree('target')
1578
revision_tree = source.basis_tree()
1579
revision_tree.lock_read()
1580
self.addCleanup(revision_tree.unlock)
1581
build_tree(revision_tree, target, source)
1582
self.assertEqual([], calls)
1584
self.addCleanup(target.unlock)
1585
self.assertEqual([], list(target._iter_changes(revision_tree)))
1587
def test_build_tree_hardlink(self):
1588
self.requireFeature(HardlinkFeature)
1589
source = self.create_ab_tree()
1590
target = self.make_branch_and_tree('target')
1591
revision_tree = source.basis_tree()
1592
revision_tree.lock_read()
1593
self.addCleanup(revision_tree.unlock)
1594
build_tree(revision_tree, target, source, hardlink=True)
1596
self.addCleanup(target.unlock)
1597
self.assertEqual([], list(target._iter_changes(revision_tree)))
1598
source_stat = os.stat('source/file1')
1599
target_stat = os.stat('target/file1')
1600
self.assertEqual(source_stat, target_stat)
1602
# Explicitly disallowing hardlinks should prevent them.
1603
target2 = self.make_branch_and_tree('target2')
1604
build_tree(revision_tree, target2, source, hardlink=False)
1606
self.addCleanup(target2.unlock)
1607
self.assertEqual([], list(target2._iter_changes(revision_tree)))
1608
source_stat = os.stat('source/file1')
1609
target2_stat = os.stat('target2/file1')
1610
self.assertNotEqual(source_stat, target2_stat)
1612
def test_build_tree_hardlinks_preserve_execute(self):
1613
self.requireFeature(HardlinkFeature)
1614
source = self.create_ab_tree()
1615
tt = TreeTransform(source)
1616
trans_id = tt.trans_id_tree_file_id('file1-id')
1617
tt.set_executability(True, trans_id)
1619
self.assertTrue(source.is_executable('file1-id'))
1620
target = self.make_branch_and_tree('target')
1621
revision_tree = source.basis_tree()
1622
revision_tree.lock_read()
1623
self.addCleanup(revision_tree.unlock)
1624
build_tree(revision_tree, target, source, hardlink=True)
1626
self.addCleanup(target.unlock)
1627
self.assertEqual([], list(target._iter_changes(revision_tree)))
1628
self.assertTrue(source.is_executable('file1-id'))
1631
class MockTransform(object):
1633
def has_named_child(self, by_parent, parent_id, name):
1634
for child_id in by_parent[parent_id]:
1638
elif name == "name.~%s~" % child_id:
1643
class MockEntry(object):
1645
object.__init__(self)
1649
class TestGetBackupName(TestCase):
1650
def test_get_backup_name(self):
1651
tt = MockTransform()
1652
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1653
self.assertEqual(name, 'name.~1~')
1654
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1655
self.assertEqual(name, 'name.~2~')
1656
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1657
self.assertEqual(name, 'name.~1~')
1658
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1659
self.assertEqual(name, 'name.~1~')
1660
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1661
self.assertEqual(name, 'name.~4~')
1664
class TestFileMover(tests.TestCaseWithTransport):
1666
def test_file_mover(self):
1667
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1668
mover = _FileMover()
1669
mover.rename('a', 'q')
1670
self.failUnlessExists('q')
1671
self.failIfExists('a')
1672
self.failUnlessExists('q/b')
1673
self.failUnlessExists('c')
1674
self.failUnlessExists('c/d')
1676
def test_pre_delete_rollback(self):
1677
self.build_tree(['a/'])
1678
mover = _FileMover()
1679
mover.pre_delete('a', 'q')
1680
self.failUnlessExists('q')
1681
self.failIfExists('a')
1683
self.failIfExists('q')
1684
self.failUnlessExists('a')
1686
def test_apply_deletions(self):
1687
self.build_tree(['a/', 'b/'])
1688
mover = _FileMover()
1689
mover.pre_delete('a', 'q')
1690
mover.pre_delete('b', 'r')
1691
self.failUnlessExists('q')
1692
self.failUnlessExists('r')
1693
self.failIfExists('a')
1694
self.failIfExists('b')
1695
mover.apply_deletions()
1696
self.failIfExists('q')
1697
self.failIfExists('r')
1698
self.failIfExists('a')
1699
self.failIfExists('b')
1701
def test_file_mover_rollback(self):
1702
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1703
mover = _FileMover()
1704
mover.rename('c/d', 'c/f')
1705
mover.rename('c/e', 'c/d')
1707
mover.rename('a', 'c')
1708
except errors.FileExists, e:
1710
self.failUnlessExists('a')
1711
self.failUnlessExists('c/d')
1714
class Bogus(Exception):
1718
class TestTransformRollback(tests.TestCaseWithTransport):
1720
class ExceptionFileMover(_FileMover):
1722
def __init__(self, bad_source=None, bad_target=None):
1723
_FileMover.__init__(self)
1724
self.bad_source = bad_source
1725
self.bad_target = bad_target
1727
def rename(self, source, target):
1728
if (self.bad_source is not None and
1729
source.endswith(self.bad_source)):
1731
elif (self.bad_target is not None and
1732
target.endswith(self.bad_target)):
1735
_FileMover.rename(self, source, target)
1737
def test_rollback_rename(self):
1738
tree = self.make_branch_and_tree('.')
1739
self.build_tree(['a/', 'a/b'])
1740
tt = TreeTransform(tree)
1741
self.addCleanup(tt.finalize)
1742
a_id = tt.trans_id_tree_path('a')
1743
tt.adjust_path('c', tt.root, a_id)
1744
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1745
self.assertRaises(Bogus, tt.apply,
1746
_mover=self.ExceptionFileMover(bad_source='a'))
1747
self.failUnlessExists('a')
1748
self.failUnlessExists('a/b')
1750
self.failUnlessExists('c')
1751
self.failUnlessExists('c/d')
1753
def test_rollback_rename_into_place(self):
1754
tree = self.make_branch_and_tree('.')
1755
self.build_tree(['a/', 'a/b'])
1756
tt = TreeTransform(tree)
1757
self.addCleanup(tt.finalize)
1758
a_id = tt.trans_id_tree_path('a')
1759
tt.adjust_path('c', tt.root, a_id)
1760
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1761
self.assertRaises(Bogus, tt.apply,
1762
_mover=self.ExceptionFileMover(bad_target='c/d'))
1763
self.failUnlessExists('a')
1764
self.failUnlessExists('a/b')
1766
self.failUnlessExists('c')
1767
self.failUnlessExists('c/d')
1769
def test_rollback_deletion(self):
1770
tree = self.make_branch_and_tree('.')
1771
self.build_tree(['a/', 'a/b'])
1772
tt = TreeTransform(tree)
1773
self.addCleanup(tt.finalize)
1774
a_id = tt.trans_id_tree_path('a')
1775
tt.delete_contents(a_id)
1776
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1777
self.assertRaises(Bogus, tt.apply,
1778
_mover=self.ExceptionFileMover(bad_target='d'))
1779
self.failUnlessExists('a')
1780
self.failUnlessExists('a/b')
1782
def test_resolve_no_parent(self):
1783
wt = self.make_branch_and_tree('.')
1784
tt = TreeTransform(wt)
1785
self.addCleanup(tt.finalize)
1786
parent = tt.trans_id_file_id('parent-id')
1787
tt.new_file('file', parent, 'Contents')
1788
resolve_conflicts(tt)