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,
32
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
33
ReusingTransform, CantMoveRoot,
34
PathsNotVersionedError, ExistingLimbo,
35
ExistingPendingDeletion, ImmortalLimbo,
36
ImmortalPendingDeletion, LockError)
37
from bzrlib.osutils import file_kind, pathjoin
38
from bzrlib.merge import Merge3Merger
39
from bzrlib.tests import (
40
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_convenience(self):
133
transform, root = self.get_transform()
134
self.wt.lock_tree_write()
135
self.addCleanup(self.wt.unlock)
136
trans_id = transform.new_file('name', root, 'contents',
138
oz = transform.new_directory('oz', root, 'oz-id')
139
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
140
toto = transform.new_file('toto', dorothy, 'toto-contents',
143
self.assertEqual(len(transform.find_conflicts()), 0)
145
self.assertRaises(ReusingTransform, transform.find_conflicts)
146
self.assertEqual('contents', file(self.wt.abspath('name')).read())
147
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
148
self.assertIs(self.wt.is_executable('my_pretties'), True)
149
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
150
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
151
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
153
self.assertEqual('toto-contents',
154
self.wt.get_file_byname('oz/dorothy/toto').read())
155
self.assertIs(self.wt.is_executable('toto-id'), False)
157
def test_tree_reference(self):
158
transform, root = self.get_transform()
159
tree = transform._tree
160
trans_id = transform.new_directory('reference', root, 'subtree-id')
161
transform.set_tree_reference('subtree-revision', trans_id)
164
self.addCleanup(tree.unlock)
165
self.assertEqual('subtree-revision',
166
tree.inventory['subtree-id'].reference_revision)
168
def test_conflicts(self):
169
transform, root = self.get_transform()
170
trans_id = transform.new_file('name', root, 'contents',
172
self.assertEqual(len(transform.find_conflicts()), 0)
173
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
174
self.assertEqual(transform.find_conflicts(),
175
[('duplicate', trans_id, trans_id2, 'name')])
176
self.assertRaises(MalformedTransform, transform.apply)
177
transform.adjust_path('name', trans_id, trans_id2)
178
self.assertEqual(transform.find_conflicts(),
179
[('non-directory parent', trans_id)])
180
tinman_id = transform.trans_id_tree_path('tinman')
181
transform.adjust_path('name', tinman_id, trans_id2)
182
self.assertEqual(transform.find_conflicts(),
183
[('unversioned parent', tinman_id),
184
('missing parent', tinman_id)])
185
lion_id = transform.create_path('lion', root)
186
self.assertEqual(transform.find_conflicts(),
187
[('unversioned parent', tinman_id),
188
('missing parent', tinman_id)])
189
transform.adjust_path('name', lion_id, trans_id2)
190
self.assertEqual(transform.find_conflicts(),
191
[('unversioned parent', lion_id),
192
('missing parent', lion_id)])
193
transform.version_file("Courage", lion_id)
194
self.assertEqual(transform.find_conflicts(),
195
[('missing parent', lion_id),
196
('versioning no contents', lion_id)])
197
transform.adjust_path('name2', root, trans_id2)
198
self.assertEqual(transform.find_conflicts(),
199
[('versioning no contents', lion_id)])
200
transform.create_file('Contents, okay?', lion_id)
201
transform.adjust_path('name2', trans_id2, trans_id2)
202
self.assertEqual(transform.find_conflicts(),
203
[('parent loop', trans_id2),
204
('non-directory parent', trans_id2)])
205
transform.adjust_path('name2', root, trans_id2)
206
oz_id = transform.new_directory('oz', root)
207
transform.set_executability(True, oz_id)
208
self.assertEqual(transform.find_conflicts(),
209
[('unversioned executability', oz_id)])
210
transform.version_file('oz-id', oz_id)
211
self.assertEqual(transform.find_conflicts(),
212
[('non-file executability', oz_id)])
213
transform.set_executability(None, oz_id)
214
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
216
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
217
self.assertEqual('contents', file(self.wt.abspath('name')).read())
218
transform2, root = self.get_transform()
219
oz_id = transform2.trans_id_tree_file_id('oz-id')
220
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
221
result = transform2.find_conflicts()
222
fp = FinalPaths(transform2)
223
self.assert_('oz/tip' in transform2._tree_path_ids)
224
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
225
self.assertEqual(len(result), 2)
226
self.assertEqual((result[0][0], result[0][1]),
227
('duplicate', newtip))
228
self.assertEqual((result[1][0], result[1][2]),
229
('duplicate id', newtip))
230
transform2.finalize()
231
transform3 = TreeTransform(self.wt)
232
self.addCleanup(transform3.finalize)
233
oz_id = transform3.trans_id_tree_file_id('oz-id')
234
transform3.delete_contents(oz_id)
235
self.assertEqual(transform3.find_conflicts(),
236
[('missing parent', oz_id)])
237
root_id = transform3.root
238
tip_id = transform3.trans_id_tree_file_id('tip-id')
239
transform3.adjust_path('tip', root_id, tip_id)
242
def test_conflict_on_case_insensitive(self):
243
tree = self.make_branch_and_tree('tree')
244
# Don't try this at home, kids!
245
# Force the tree to report that it is case sensitive, for conflict
247
tree.case_sensitive = True
248
transform = TreeTransform(tree)
249
self.addCleanup(transform.finalize)
250
transform.new_file('file', transform.root, 'content')
251
transform.new_file('FiLe', transform.root, 'content')
252
result = transform.find_conflicts()
253
self.assertEqual([], result)
254
# Force the tree to report that it is case insensitive, for conflict
256
tree.case_sensitive = False
257
result = transform.find_conflicts()
258
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
260
def test_conflict_on_case_insensitive_existing(self):
261
tree = self.make_branch_and_tree('tree')
262
self.build_tree(['tree/FiLe'])
263
# Don't try this at home, kids!
264
# Force the tree to report that it is case sensitive, for conflict
266
tree.case_sensitive = True
267
transform = TreeTransform(tree)
268
self.addCleanup(transform.finalize)
269
transform.new_file('file', transform.root, 'content')
270
result = transform.find_conflicts()
271
self.assertEqual([], result)
272
# Force the tree to report that it is case insensitive, for conflict
274
tree.case_sensitive = False
275
result = transform.find_conflicts()
276
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
278
def test_resolve_case_insensitive_conflict(self):
279
tree = self.make_branch_and_tree('tree')
280
# Don't try this at home, kids!
281
# Force the tree to report that it is case insensitive, for conflict
283
tree.case_sensitive = False
284
transform = TreeTransform(tree)
285
self.addCleanup(transform.finalize)
286
transform.new_file('file', transform.root, 'content')
287
transform.new_file('FiLe', transform.root, 'content')
288
resolve_conflicts(transform)
290
self.failUnlessExists('tree/file')
291
self.failUnlessExists('tree/FiLe.moved')
293
def test_resolve_checkout_case_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,
304
pass_func=lambda t, c: resolve_checkout(t, c, []))
306
self.failUnlessExists('tree/file')
307
self.failUnlessExists('tree/FiLe.moved')
309
def test_apply_case_conflict(self):
310
"""Ensure that a transform with case conflicts can always be applied"""
311
tree = self.make_branch_and_tree('tree')
312
transform = TreeTransform(tree)
313
self.addCleanup(transform.finalize)
314
transform.new_file('file', transform.root, 'content')
315
transform.new_file('FiLe', transform.root, 'content')
316
dir = transform.new_directory('dir', transform.root)
317
transform.new_file('dirfile', dir, 'content')
318
transform.new_file('dirFiLe', dir, 'content')
319
resolve_conflicts(transform)
321
self.failUnlessExists('tree/file')
322
if not os.path.exists('tree/FiLe.moved'):
323
self.failUnlessExists('tree/FiLe')
324
self.failUnlessExists('tree/dir/dirfile')
325
if not os.path.exists('tree/dir/dirFiLe.moved'):
326
self.failUnlessExists('tree/dir/dirFiLe')
328
def test_case_insensitive_limbo(self):
329
tree = self.make_branch_and_tree('tree')
330
# Don't try this at home, kids!
331
# Force the tree to report that it is case insensitive
332
tree.case_sensitive = False
333
transform = TreeTransform(tree)
334
self.addCleanup(transform.finalize)
335
dir = transform.new_directory('dir', transform.root)
336
first = transform.new_file('file', dir, 'content')
337
second = transform.new_file('FiLe', dir, 'content')
338
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
339
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
341
def test_add_del(self):
342
start, root = self.get_transform()
343
start.new_directory('a', root, 'a')
345
transform, root = self.get_transform()
346
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
347
transform.new_directory('a', root, 'a')
350
def test_unversioning(self):
351
create_tree, root = self.get_transform()
352
parent_id = create_tree.new_directory('parent', root, 'parent-id')
353
create_tree.new_file('child', parent_id, 'child', 'child-id')
355
unversion = TreeTransform(self.wt)
356
self.addCleanup(unversion.finalize)
357
parent = unversion.trans_id_tree_path('parent')
358
unversion.unversion_file(parent)
359
self.assertEqual(unversion.find_conflicts(),
360
[('unversioned parent', parent_id)])
361
file_id = unversion.trans_id_tree_file_id('child-id')
362
unversion.unversion_file(file_id)
365
def test_name_invariants(self):
366
create_tree, root = self.get_transform()
368
root = create_tree.root
369
create_tree.new_file('name1', root, 'hello1', 'name1')
370
create_tree.new_file('name2', root, 'hello2', 'name2')
371
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
372
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
373
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
374
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
377
mangle_tree,root = self.get_transform()
378
root = mangle_tree.root
380
name1 = mangle_tree.trans_id_tree_file_id('name1')
381
name2 = mangle_tree.trans_id_tree_file_id('name2')
382
mangle_tree.adjust_path('name2', root, name1)
383
mangle_tree.adjust_path('name1', root, name2)
385
#tests for deleting parent directories
386
ddir = mangle_tree.trans_id_tree_file_id('ddir')
387
mangle_tree.delete_contents(ddir)
388
dfile = mangle_tree.trans_id_tree_file_id('dfile')
389
mangle_tree.delete_versioned(dfile)
390
mangle_tree.unversion_file(dfile)
391
mfile = mangle_tree.trans_id_tree_file_id('mfile')
392
mangle_tree.adjust_path('mfile', root, mfile)
394
#tests for adding parent directories
395
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
396
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
397
mangle_tree.adjust_path('mfile2', newdir, mfile2)
398
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
399
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
400
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
401
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
403
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
404
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
405
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
406
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
407
self.assertEqual(file(mfile2_path).read(), 'later2')
408
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
409
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
410
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
411
self.assertEqual(file(newfile_path).read(), 'hello3')
412
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
413
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
414
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
416
def test_both_rename(self):
417
create_tree,root = self.get_transform()
418
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
419
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
421
mangle_tree,root = self.get_transform()
422
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
423
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
424
mangle_tree.adjust_path('test', root, selftest)
425
mangle_tree.adjust_path('test_too_much', root, selftest)
426
mangle_tree.set_executability(True, blackbox)
429
def test_both_rename2(self):
430
create_tree,root = self.get_transform()
431
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
432
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
433
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
434
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
437
mangle_tree,root = self.get_transform()
438
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
439
tests = mangle_tree.trans_id_tree_file_id('tests-id')
440
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
441
mangle_tree.adjust_path('selftest', bzrlib, tests)
442
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
443
mangle_tree.set_executability(True, test_too_much)
446
def test_both_rename3(self):
447
create_tree,root = self.get_transform()
448
tests = create_tree.new_directory('tests', root, 'tests-id')
449
create_tree.new_file('test_too_much.py', tests, 'hello1',
452
mangle_tree,root = self.get_transform()
453
tests = mangle_tree.trans_id_tree_file_id('tests-id')
454
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
455
mangle_tree.adjust_path('selftest', root, tests)
456
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
457
mangle_tree.set_executability(True, test_too_much)
460
def test_move_dangling_ie(self):
461
create_tree, root = self.get_transform()
463
root = create_tree.root
464
create_tree.new_file('name1', root, 'hello1', 'name1')
466
delete_contents, root = self.get_transform()
467
file = delete_contents.trans_id_tree_file_id('name1')
468
delete_contents.delete_contents(file)
469
delete_contents.apply()
470
move_id, root = self.get_transform()
471
name1 = move_id.trans_id_tree_file_id('name1')
472
newdir = move_id.new_directory('dir', root, 'newdir')
473
move_id.adjust_path('name2', newdir, name1)
476
def test_replace_dangling_ie(self):
477
create_tree, root = self.get_transform()
479
root = create_tree.root
480
create_tree.new_file('name1', root, 'hello1', 'name1')
482
delete_contents = TreeTransform(self.wt)
483
self.addCleanup(delete_contents.finalize)
484
file = delete_contents.trans_id_tree_file_id('name1')
485
delete_contents.delete_contents(file)
486
delete_contents.apply()
487
delete_contents.finalize()
488
replace = TreeTransform(self.wt)
489
self.addCleanup(replace.finalize)
490
name2 = replace.new_file('name2', root, 'hello2', 'name1')
491
conflicts = replace.find_conflicts()
492
name1 = replace.trans_id_tree_file_id('name1')
493
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
494
resolve_conflicts(replace)
497
def test_symlinks(self):
498
self.requireFeature(SymlinkFeature)
499
transform,root = self.get_transform()
500
oz_id = transform.new_directory('oz', root, 'oz-id')
501
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
503
wiz_id = transform.create_path('wizard2', oz_id)
504
transform.create_symlink('behind_curtain', wiz_id)
505
transform.version_file('wiz-id2', wiz_id)
506
transform.set_executability(True, wiz_id)
507
self.assertEqual(transform.find_conflicts(),
508
[('non-file executability', wiz_id)])
509
transform.set_executability(None, wiz_id)
511
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
512
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
513
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
515
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
518
def test_unable_create_symlink(self):
520
wt = self.make_branch_and_tree('.')
521
tt = TreeTransform(wt) # TreeTransform obtains write lock
523
tt.new_symlink('foo', tt.root, 'bar')
527
os_symlink = getattr(os, 'symlink', None)
530
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
532
"Unable to create symlink 'foo' on this platform",
536
os.symlink = os_symlink
538
def get_conflicted(self):
539
create,root = self.get_transform()
540
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
541
oz = create.new_directory('oz', root, 'oz-id')
542
create.new_directory('emeraldcity', oz, 'emerald-id')
544
conflicts,root = self.get_transform()
545
# set up duplicate entry, duplicate id
546
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
548
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
549
oz = conflicts.trans_id_tree_file_id('oz-id')
550
# set up DeletedParent parent conflict
551
conflicts.delete_versioned(oz)
552
emerald = conflicts.trans_id_tree_file_id('emerald-id')
553
# set up MissingParent conflict
554
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
555
conflicts.adjust_path('munchkincity', root, munchkincity)
556
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
558
conflicts.adjust_path('emeraldcity', emerald, emerald)
559
return conflicts, emerald, oz, old_dorothy, new_dorothy
561
def test_conflict_resolution(self):
562
conflicts, emerald, oz, old_dorothy, new_dorothy =\
563
self.get_conflicted()
564
resolve_conflicts(conflicts)
565
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
566
self.assertIs(conflicts.final_file_id(old_dorothy), None)
567
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
568
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
569
self.assertEqual(conflicts.final_parent(emerald), oz)
572
def test_cook_conflicts(self):
573
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
574
raw_conflicts = resolve_conflicts(tt)
575
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
576
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
577
'dorothy', None, 'dorothy-id')
578
self.assertEqual(cooked_conflicts[0], duplicate)
579
duplicate_id = DuplicateID('Unversioned existing file',
580
'dorothy.moved', 'dorothy', None,
582
self.assertEqual(cooked_conflicts[1], duplicate_id)
583
missing_parent = MissingParent('Created directory', 'munchkincity',
585
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
586
self.assertEqual(cooked_conflicts[2], missing_parent)
587
unversioned_parent = UnversionedParent('Versioned directory',
590
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
592
self.assertEqual(cooked_conflicts[3], unversioned_parent)
593
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
594
'oz/emeraldcity', 'emerald-id', 'emerald-id')
595
self.assertEqual(cooked_conflicts[4], deleted_parent)
596
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
597
self.assertEqual(cooked_conflicts[6], parent_loop)
598
self.assertEqual(len(cooked_conflicts), 7)
601
def test_string_conflicts(self):
602
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
603
raw_conflicts = resolve_conflicts(tt)
604
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
606
conflicts_s = [str(c) for c in cooked_conflicts]
607
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
608
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
609
'Moved existing file to '
611
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
612
'Unversioned existing file '
614
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
615
' munchkincity. Created directory.')
616
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
617
' versioned, but has versioned'
618
' children. Versioned directory.')
619
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
620
" is not empty. Not deleting.")
621
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
622
' versioned, but has versioned'
623
' children. Versioned directory.')
624
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
625
' oz/emeraldcity. Cancelled move.')
627
def prepare_wrong_parent_kind(self):
628
tt, root = self.get_transform()
629
tt.new_file('parent', root, 'contents', 'parent-id')
631
tt, root = self.get_transform()
632
parent_id = tt.trans_id_file_id('parent-id')
633
tt.new_file('child,', parent_id, 'contents2', 'file-id')
636
def test_find_conflicts_wrong_parent_kind(self):
637
tt = self.prepare_wrong_parent_kind()
640
def test_resolve_conflicts_wrong_existing_parent_kind(self):
641
tt = self.prepare_wrong_parent_kind()
642
raw_conflicts = resolve_conflicts(tt)
643
self.assertEqual(set([('non-directory parent', 'Created directory',
644
'new-3')]), raw_conflicts)
645
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
646
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
647
'parent-id')], cooked_conflicts)
649
self.assertEqual(None, self.wt.path2id('parent'))
650
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
652
def test_resolve_conflicts_wrong_new_parent_kind(self):
653
tt, root = self.get_transform()
654
parent_id = tt.new_directory('parent', root, 'parent-id')
655
tt.new_file('child,', parent_id, 'contents2', 'file-id')
657
tt, root = self.get_transform()
658
parent_id = tt.trans_id_file_id('parent-id')
659
tt.delete_contents(parent_id)
660
tt.create_file('contents', parent_id)
661
raw_conflicts = resolve_conflicts(tt)
662
self.assertEqual(set([('non-directory parent', 'Created directory',
663
'new-3')]), raw_conflicts)
665
self.assertEqual(None, self.wt.path2id('parent'))
666
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
668
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
669
tt, root = self.get_transform()
670
parent_id = tt.new_directory('parent', root)
671
tt.new_file('child,', parent_id, 'contents2')
673
tt, root = self.get_transform()
674
parent_id = tt.trans_id_tree_path('parent')
675
tt.delete_contents(parent_id)
676
tt.create_file('contents', parent_id)
677
resolve_conflicts(tt)
679
self.assertIs(None, self.wt.path2id('parent'))
680
self.assertIs(None, self.wt.path2id('parent.new'))
682
def test_moving_versioned_directories(self):
683
create, root = self.get_transform()
684
kansas = create.new_directory('kansas', root, 'kansas-id')
685
create.new_directory('house', kansas, 'house-id')
686
create.new_directory('oz', root, 'oz-id')
688
cyclone, root = self.get_transform()
689
oz = cyclone.trans_id_tree_file_id('oz-id')
690
house = cyclone.trans_id_tree_file_id('house-id')
691
cyclone.adjust_path('house', oz, house)
694
def test_moving_root(self):
695
create, root = self.get_transform()
696
fun = create.new_directory('fun', root, 'fun-id')
697
create.new_directory('sun', root, 'sun-id')
698
create.new_directory('moon', root, 'moon')
700
transform, root = self.get_transform()
701
transform.adjust_root_path('oldroot', fun)
702
new_root=transform.trans_id_tree_path('')
703
transform.version_file('new-root', new_root)
706
def test_renames(self):
707
create, root = self.get_transform()
708
old = create.new_directory('old-parent', root, 'old-id')
709
intermediate = create.new_directory('intermediate', old, 'im-id')
710
myfile = create.new_file('myfile', intermediate, 'myfile-text',
713
rename, root = self.get_transform()
714
old = rename.trans_id_file_id('old-id')
715
rename.adjust_path('new', root, old)
716
myfile = rename.trans_id_file_id('myfile-id')
717
rename.set_executability(True, myfile)
720
def test_find_interesting(self):
721
create, root = self.get_transform()
723
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
724
create.new_file('uvfile', root, 'othertext')
726
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
727
find_interesting, wt, wt, ['vfile'])
728
self.assertEqual(result, set(['myfile-id']))
730
def test_set_executability_order(self):
731
"""Ensure that executability behaves the same, no matter what order.
733
- create file and set executability simultaneously
734
- create file and set executability afterward
735
- unsetting the executability of a file whose executability has not been
736
declared should throw an exception (this may happen when a
737
merge attempts to create a file with a duplicate ID)
739
transform, root = self.get_transform()
742
self.addCleanup(wt.unlock)
743
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
745
sac = transform.new_file('set_after_creation', root,
746
'Set after creation', 'sac')
747
transform.set_executability(True, sac)
748
uws = transform.new_file('unset_without_set', root, 'Unset badly',
750
self.assertRaises(KeyError, transform.set_executability, None, uws)
752
self.assertTrue(wt.is_executable('soc'))
753
self.assertTrue(wt.is_executable('sac'))
755
def test_preserve_mode(self):
756
"""File mode is preserved when replacing content"""
757
if sys.platform == 'win32':
758
raise TestSkipped('chmod has no effect on win32')
759
transform, root = self.get_transform()
760
transform.new_file('file1', root, 'contents', 'file1-id', True)
762
self.assertTrue(self.wt.is_executable('file1-id'))
763
transform, root = self.get_transform()
764
file1_id = transform.trans_id_tree_file_id('file1-id')
765
transform.delete_contents(file1_id)
766
transform.create_file('contents2', file1_id)
768
self.assertTrue(self.wt.is_executable('file1-id'))
770
def test__set_mode_stats_correctly(self):
771
"""_set_mode stats to determine file mode."""
772
if sys.platform == 'win32':
773
raise TestSkipped('chmod has no effect on win32')
777
def instrumented_stat(path):
778
stat_paths.append(path)
779
return real_stat(path)
781
transform, root = self.get_transform()
783
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
784
file_id='bar-id-1', executable=False)
787
transform, root = self.get_transform()
788
bar1_id = transform.trans_id_tree_path('bar')
789
bar2_id = transform.trans_id_tree_path('bar2')
791
os.stat = instrumented_stat
792
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
797
bar1_abspath = self.wt.abspath('bar')
798
self.assertEqual([bar1_abspath], stat_paths)
800
def test_iter_changes(self):
801
self.wt.set_root_id('eert_toor')
802
transform, root = self.get_transform()
803
transform.new_file('old', root, 'blah', 'id-1', True)
805
transform, root = self.get_transform()
807
self.assertEqual([], list(transform._iter_changes()))
808
old = transform.trans_id_tree_file_id('id-1')
809
transform.unversion_file(old)
810
self.assertEqual([('id-1', ('old', None), False, (True, False),
811
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
812
(True, True))], list(transform._iter_changes()))
813
transform.new_directory('new', root, 'id-1')
814
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
815
('eert_toor', 'eert_toor'), ('old', 'new'),
816
('file', 'directory'),
817
(True, False))], list(transform._iter_changes()))
821
def test_iter_changes_new(self):
822
self.wt.set_root_id('eert_toor')
823
transform, root = self.get_transform()
824
transform.new_file('old', root, 'blah')
826
transform, root = self.get_transform()
828
old = transform.trans_id_tree_path('old')
829
transform.version_file('id-1', old)
830
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
831
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
832
(False, False))], list(transform._iter_changes()))
836
def test_iter_changes_modifications(self):
837
self.wt.set_root_id('eert_toor')
838
transform, root = self.get_transform()
839
transform.new_file('old', root, 'blah', 'id-1')
840
transform.new_file('new', root, 'blah')
841
transform.new_directory('subdir', root, 'subdir-id')
843
transform, root = self.get_transform()
845
old = transform.trans_id_tree_path('old')
846
subdir = transform.trans_id_tree_file_id('subdir-id')
847
new = transform.trans_id_tree_path('new')
848
self.assertEqual([], list(transform._iter_changes()))
851
transform.delete_contents(old)
852
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
853
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
854
(False, False))], list(transform._iter_changes()))
857
transform.create_file('blah', old)
858
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
859
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
860
(False, False))], list(transform._iter_changes()))
861
transform.cancel_deletion(old)
862
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
863
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
864
(False, False))], list(transform._iter_changes()))
865
transform.cancel_creation(old)
867
# move file_id to a different file
868
self.assertEqual([], list(transform._iter_changes()))
869
transform.unversion_file(old)
870
transform.version_file('id-1', new)
871
transform.adjust_path('old', root, new)
872
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
873
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
874
(False, False))], list(transform._iter_changes()))
875
transform.cancel_versioning(new)
876
transform._removed_id = set()
879
self.assertEqual([], list(transform._iter_changes()))
880
transform.set_executability(True, old)
881
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
882
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
883
(False, True))], list(transform._iter_changes()))
884
transform.set_executability(None, old)
887
self.assertEqual([], list(transform._iter_changes()))
888
transform.adjust_path('new', root, old)
889
transform._new_parent = {}
890
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
891
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
892
(False, False))], list(transform._iter_changes()))
893
transform._new_name = {}
896
self.assertEqual([], list(transform._iter_changes()))
897
transform.adjust_path('new', subdir, old)
898
transform._new_name = {}
899
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
900
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
901
('file', 'file'), (False, False))],
902
list(transform._iter_changes()))
903
transform._new_path = {}
908
def test_iter_changes_modified_bleed(self):
909
self.wt.set_root_id('eert_toor')
910
"""Modified flag should not bleed from one change to another"""
911
# unfortunately, we have no guarantee that file1 (which is modified)
912
# will be applied before file2. And if it's applied after file2, it
913
# obviously can't bleed into file2's change output. But for now, it
915
transform, root = self.get_transform()
916
transform.new_file('file1', root, 'blah', 'id-1')
917
transform.new_file('file2', root, 'blah', 'id-2')
919
transform, root = self.get_transform()
921
transform.delete_contents(transform.trans_id_file_id('id-1'))
922
transform.set_executability(True,
923
transform.trans_id_file_id('id-2'))
924
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
925
('eert_toor', 'eert_toor'), ('file1', u'file1'),
926
('file', None), (False, False)),
927
('id-2', (u'file2', u'file2'), False, (True, True),
928
('eert_toor', 'eert_toor'), ('file2', u'file2'),
929
('file', 'file'), (False, True))],
930
list(transform._iter_changes()))
934
def test_iter_changes_move_missing(self):
935
"""Test moving ids with no files around"""
936
self.wt.set_root_id('toor_eert')
937
# Need two steps because versioning a non-existant file is a conflict.
938
transform, root = self.get_transform()
939
transform.new_directory('floater', root, 'floater-id')
941
transform, root = self.get_transform()
942
transform.delete_contents(transform.trans_id_tree_path('floater'))
944
transform, root = self.get_transform()
945
floater = transform.trans_id_tree_path('floater')
947
transform.adjust_path('flitter', root, floater)
948
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
949
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
950
(None, None), (False, False))], list(transform._iter_changes()))
954
def test_iter_changes_pointless(self):
955
"""Ensure that no-ops are not treated as modifications"""
956
self.wt.set_root_id('eert_toor')
957
transform, root = self.get_transform()
958
transform.new_file('old', root, 'blah', 'id-1')
959
transform.new_directory('subdir', root, 'subdir-id')
961
transform, root = self.get_transform()
963
old = transform.trans_id_tree_path('old')
964
subdir = transform.trans_id_tree_file_id('subdir-id')
965
self.assertEqual([], list(transform._iter_changes()))
966
transform.delete_contents(subdir)
967
transform.create_directory(subdir)
968
transform.set_executability(False, old)
969
transform.unversion_file(old)
970
transform.version_file('id-1', old)
971
transform.adjust_path('old', root, old)
972
self.assertEqual([], list(transform._iter_changes()))
976
def test_rename_count(self):
977
transform, root = self.get_transform()
978
transform.new_file('name1', root, 'contents')
979
self.assertEqual(transform.rename_count, 0)
981
self.assertEqual(transform.rename_count, 1)
982
transform2, root = self.get_transform()
983
transform2.adjust_path('name2', root,
984
transform2.trans_id_tree_path('name1'))
985
self.assertEqual(transform2.rename_count, 0)
987
self.assertEqual(transform2.rename_count, 2)
989
def test_change_parent(self):
990
"""Ensure that after we change a parent, the results are still right.
992
Renames and parent changes on pending transforms can happen as part
993
of conflict resolution, and are explicitly permitted by the
996
This test ensures they work correctly with the rename-avoidance
999
transform, root = self.get_transform()
1000
parent1 = transform.new_directory('parent1', root)
1001
child1 = transform.new_file('child1', parent1, 'contents')
1002
parent2 = transform.new_directory('parent2', root)
1003
transform.adjust_path('child1', parent2, child1)
1005
self.failIfExists(self.wt.abspath('parent1/child1'))
1006
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1007
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1008
# no rename for child1 (counting only renames during apply)
1009
self.failUnlessEqual(2, transform.rename_count)
1011
def test_cancel_parent(self):
1012
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1014
This is like the test_change_parent, except that we cancel the parent
1015
before adjusting the path. The transform must detect that the
1016
directory is non-empty, and move children to safe locations.
1018
transform, root = self.get_transform()
1019
parent1 = transform.new_directory('parent1', root)
1020
child1 = transform.new_file('child1', parent1, 'contents')
1021
child2 = transform.new_file('child2', parent1, 'contents')
1023
transform.cancel_creation(parent1)
1025
self.fail('Failed to move child1 before deleting parent1')
1026
transform.cancel_creation(child2)
1027
transform.create_directory(parent1)
1029
transform.cancel_creation(parent1)
1030
# If the transform incorrectly believes that child2 is still in
1031
# parent1's limbo directory, it will try to rename it and fail
1032
# because was already moved by the first cancel_creation.
1034
self.fail('Transform still thinks child2 is a child of parent1')
1035
parent2 = transform.new_directory('parent2', root)
1036
transform.adjust_path('child1', parent2, child1)
1038
self.failIfExists(self.wt.abspath('parent1'))
1039
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1040
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1041
self.failUnlessEqual(2, transform.rename_count)
1043
def test_adjust_and_cancel(self):
1044
"""Make sure adjust_path keeps track of limbo children properly"""
1045
transform, root = self.get_transform()
1046
parent1 = transform.new_directory('parent1', root)
1047
child1 = transform.new_file('child1', parent1, 'contents')
1048
parent2 = transform.new_directory('parent2', root)
1049
transform.adjust_path('child1', parent2, child1)
1050
transform.cancel_creation(child1)
1052
transform.cancel_creation(parent1)
1053
# if the transform thinks child1 is still in parent1's limbo
1054
# directory, it will attempt to move it and fail.
1056
self.fail('Transform still thinks child1 is a child of parent1')
1057
transform.finalize()
1059
def test_noname_contents(self):
1060
"""TreeTransform should permit deferring naming files."""
1061
transform, root = self.get_transform()
1062
parent = transform.trans_id_file_id('parent-id')
1064
transform.create_directory(parent)
1066
self.fail("Can't handle contents with no name")
1067
transform.finalize()
1069
def test_noname_contents_nested(self):
1070
"""TreeTransform should permit deferring naming files."""
1071
transform, root = self.get_transform()
1072
parent = transform.trans_id_file_id('parent-id')
1074
transform.create_directory(parent)
1076
self.fail("Can't handle contents with no name")
1077
child = transform.new_directory('child', parent)
1078
transform.adjust_path('parent', root, parent)
1080
self.failUnlessExists(self.wt.abspath('parent/child'))
1081
self.assertEqual(1, transform.rename_count)
1083
def test_reuse_name(self):
1084
"""Avoid reusing the same limbo name for different files"""
1085
transform, root = self.get_transform()
1086
parent = transform.new_directory('parent', root)
1087
child1 = transform.new_directory('child', parent)
1089
child2 = transform.new_directory('child', parent)
1091
self.fail('Tranform tried to use the same limbo name twice')
1092
transform.adjust_path('child2', parent, child2)
1094
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1095
# child2 is put into top-level limbo because child1 has already
1096
# claimed the direct limbo path when child2 is created. There is no
1097
# advantage in renaming files once they're in top-level limbo, except
1099
self.assertEqual(2, transform.rename_count)
1101
def test_reuse_when_first_moved(self):
1102
"""Don't avoid direct paths when it is safe to use them"""
1103
transform, root = self.get_transform()
1104
parent = transform.new_directory('parent', root)
1105
child1 = transform.new_directory('child', parent)
1106
transform.adjust_path('child1', parent, child1)
1107
child2 = transform.new_directory('child', parent)
1109
# limbo/new-1 => parent
1110
self.assertEqual(1, transform.rename_count)
1112
def test_reuse_after_cancel(self):
1113
"""Don't avoid direct paths when it is safe to use them"""
1114
transform, root = self.get_transform()
1115
parent2 = transform.new_directory('parent2', root)
1116
child1 = transform.new_directory('child1', parent2)
1117
transform.cancel_creation(parent2)
1118
transform.create_directory(parent2)
1119
child2 = transform.new_directory('child1', parent2)
1120
transform.adjust_path('child2', parent2, child1)
1122
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1123
self.assertEqual(2, transform.rename_count)
1125
def test_finalize_order(self):
1126
"""Finalize must be done in child-to-parent order"""
1127
transform, root = self.get_transform()
1128
parent = transform.new_directory('parent', root)
1129
child = transform.new_directory('child', parent)
1131
transform.finalize()
1133
self.fail('Tried to remove parent before child1')
1135
def test_cancel_with_cancelled_child_should_succeed(self):
1136
transform, root = self.get_transform()
1137
parent = transform.new_directory('parent', root)
1138
child = transform.new_directory('child', parent)
1139
transform.cancel_creation(child)
1140
transform.cancel_creation(parent)
1141
transform.finalize()
1143
def test_change_entry(self):
1144
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1145
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1148
def test_case_insensitive_clash(self):
1149
self.requireFeature(CaseInsensitiveFilesystemFeature)
1151
wt = self.make_branch_and_tree('.')
1152
tt = TreeTransform(wt) # TreeTransform obtains write lock
1154
tt.new_file('foo', tt.root, 'bar')
1155
tt.new_file('Foo', tt.root, 'spam')
1156
# Lie to tt that we've already resolved all conflicts.
1157
tt.apply(no_conflicts=True)
1161
err = self.assertRaises(errors.FileExists, tt_helper)
1162
self.assertContainsRe(str(err),
1163
"^File exists: .+/foo")
1165
def test_two_directories_clash(self):
1167
wt = self.make_branch_and_tree('.')
1168
tt = TreeTransform(wt) # TreeTransform obtains write lock
1170
foo_1 = tt.new_directory('foo', tt.root)
1171
tt.new_directory('bar', foo_1)
1172
foo_2 = tt.new_directory('foo', tt.root)
1173
tt.new_directory('baz', foo_2)
1174
# Lie to tt that we've already resolved all conflicts.
1175
tt.apply(no_conflicts=True)
1179
err = self.assertRaises(errors.FileExists, tt_helper)
1180
self.assertContainsRe(str(err),
1181
"^File exists: .+/foo")
1183
def test_two_directories_clash_finalize(self):
1185
wt = self.make_branch_and_tree('.')
1186
tt = TreeTransform(wt) # TreeTransform obtains write lock
1188
foo_1 = tt.new_directory('foo', tt.root)
1189
tt.new_directory('bar', foo_1)
1190
foo_2 = tt.new_directory('foo', tt.root)
1191
tt.new_directory('baz', foo_2)
1192
# Lie to tt that we've already resolved all conflicts.
1193
tt.apply(no_conflicts=True)
1197
err = self.assertRaises(errors.FileExists, tt_helper)
1198
self.assertContainsRe(str(err),
1199
"^File exists: .+/foo")
1202
class TransformGroup(object):
1204
def __init__(self, dirname, root_id):
1207
self.wt = BzrDir.create_standalone_workingtree(dirname)
1208
self.wt.set_root_id(root_id)
1209
self.b = self.wt.branch
1210
self.tt = TreeTransform(self.wt)
1211
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1214
def conflict_text(tree, merge):
1215
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1216
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1219
class TestTransformMerge(TestCaseInTempDir):
1220
def test_text_merge(self):
1221
root_id = generate_ids.gen_root_id()
1222
base = TransformGroup("base", root_id)
1223
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1224
base.tt.new_file('b', base.root, 'b1', 'b')
1225
base.tt.new_file('c', base.root, 'c', 'c')
1226
base.tt.new_file('d', base.root, 'd', 'd')
1227
base.tt.new_file('e', base.root, 'e', 'e')
1228
base.tt.new_file('f', base.root, 'f', 'f')
1229
base.tt.new_directory('g', base.root, 'g')
1230
base.tt.new_directory('h', base.root, 'h')
1232
other = TransformGroup("other", root_id)
1233
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1234
other.tt.new_file('b', other.root, 'b2', 'b')
1235
other.tt.new_file('c', other.root, 'c2', 'c')
1236
other.tt.new_file('d', other.root, 'd', 'd')
1237
other.tt.new_file('e', other.root, 'e2', 'e')
1238
other.tt.new_file('f', other.root, 'f', 'f')
1239
other.tt.new_file('g', other.root, 'g', 'g')
1240
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1241
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1243
this = TransformGroup("this", root_id)
1244
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1245
this.tt.new_file('b', this.root, 'b', 'b')
1246
this.tt.new_file('c', this.root, 'c', 'c')
1247
this.tt.new_file('d', this.root, 'd2', 'd')
1248
this.tt.new_file('e', this.root, 'e2', 'e')
1249
this.tt.new_file('f', this.root, 'f', 'f')
1250
this.tt.new_file('g', this.root, 'g', 'g')
1251
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1252
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1254
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1256
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1257
# three-way text conflict
1258
self.assertEqual(this.wt.get_file('b').read(),
1259
conflict_text('b', 'b2'))
1261
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1263
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1264
# Ambigious clean merge
1265
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1267
self.assertEqual(this.wt.get_file('f').read(), 'f')
1268
# Correct correct results when THIS == OTHER
1269
self.assertEqual(this.wt.get_file('g').read(), 'g')
1270
# Text conflict when THIS & OTHER are text and BASE is dir
1271
self.assertEqual(this.wt.get_file('h').read(),
1272
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1273
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1275
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1277
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1278
self.assertEqual(this.wt.get_file('i').read(),
1279
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1280
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1282
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1284
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1285
modified = ['a', 'b', 'c', 'h', 'i']
1286
merge_modified = this.wt.merge_modified()
1287
self.assertSubset(merge_modified, modified)
1288
self.assertEqual(len(merge_modified), len(modified))
1289
file(this.wt.id2abspath('a'), 'wb').write('booga')
1291
merge_modified = this.wt.merge_modified()
1292
self.assertSubset(merge_modified, modified)
1293
self.assertEqual(len(merge_modified), len(modified))
1297
def test_file_merge(self):
1298
self.requireFeature(SymlinkFeature)
1299
root_id = generate_ids.gen_root_id()
1300
base = TransformGroup("BASE", root_id)
1301
this = TransformGroup("THIS", root_id)
1302
other = TransformGroup("OTHER", root_id)
1303
for tg in this, base, other:
1304
tg.tt.new_directory('a', tg.root, 'a')
1305
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1306
tg.tt.new_file('c', tg.root, 'c', 'c')
1307
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1308
targets = ((base, 'base-e', 'base-f', None, None),
1309
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1310
(other, 'other-e', None, 'other-g', 'other-h'))
1311
for tg, e_target, f_target, g_target, h_target in targets:
1312
for link, target in (('e', e_target), ('f', f_target),
1313
('g', g_target), ('h', h_target)):
1314
if target is not None:
1315
tg.tt.new_symlink(link, tg.root, target, link)
1317
for tg in this, base, other:
1319
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1320
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1321
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1322
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1323
for suffix in ('THIS', 'BASE', 'OTHER'):
1324
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1325
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1326
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1327
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1328
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1329
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1330
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1331
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1332
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1333
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1334
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1335
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1336
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1337
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1339
def test_filename_merge(self):
1340
root_id = generate_ids.gen_root_id()
1341
base = TransformGroup("BASE", root_id)
1342
this = TransformGroup("THIS", root_id)
1343
other = TransformGroup("OTHER", root_id)
1344
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1345
for t in [base, this, other]]
1346
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1347
for t in [base, this, other]]
1348
base.tt.new_directory('c', base_a, 'c')
1349
this.tt.new_directory('c1', this_a, 'c')
1350
other.tt.new_directory('c', other_b, 'c')
1352
base.tt.new_directory('d', base_a, 'd')
1353
this.tt.new_directory('d1', this_b, 'd')
1354
other.tt.new_directory('d', other_a, 'd')
1356
base.tt.new_directory('e', base_a, 'e')
1357
this.tt.new_directory('e', this_a, 'e')
1358
other.tt.new_directory('e1', other_b, 'e')
1360
base.tt.new_directory('f', base_a, 'f')
1361
this.tt.new_directory('f1', this_b, 'f')
1362
other.tt.new_directory('f1', other_b, 'f')
1364
for tg in [this, base, other]:
1366
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1367
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1368
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1369
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1370
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1372
def test_filename_merge_conflicts(self):
1373
root_id = generate_ids.gen_root_id()
1374
base = TransformGroup("BASE", root_id)
1375
this = TransformGroup("THIS", root_id)
1376
other = TransformGroup("OTHER", root_id)
1377
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1378
for t in [base, this, other]]
1379
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1380
for t in [base, this, other]]
1382
base.tt.new_file('g', base_a, 'g', 'g')
1383
other.tt.new_file('g1', other_b, 'g1', 'g')
1385
base.tt.new_file('h', base_a, 'h', 'h')
1386
this.tt.new_file('h1', this_b, 'h1', 'h')
1388
base.tt.new_file('i', base.root, 'i', 'i')
1389
other.tt.new_directory('i1', this_b, 'i')
1391
for tg in [this, base, other]:
1393
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1395
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1396
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1397
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1398
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1399
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1400
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1401
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1404
class TestBuildTree(tests.TestCaseWithTransport):
1406
def test_build_tree_with_symlinks(self):
1407
self.requireFeature(SymlinkFeature)
1409
a = BzrDir.create_standalone_workingtree('a')
1411
file('a/foo/bar', 'wb').write('contents')
1412
os.symlink('a/foo/bar', 'a/foo/baz')
1413
a.add(['foo', 'foo/bar', 'foo/baz'])
1414
a.commit('initial commit')
1415
b = BzrDir.create_standalone_workingtree('b')
1416
basis = a.basis_tree()
1418
self.addCleanup(basis.unlock)
1419
build_tree(basis, b)
1420
self.assertIs(os.path.isdir('b/foo'), True)
1421
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1422
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1424
def test_build_with_references(self):
1425
tree = self.make_branch_and_tree('source',
1426
format='dirstate-with-subtree')
1427
subtree = self.make_branch_and_tree('source/subtree',
1428
format='dirstate-with-subtree')
1429
tree.add_reference(subtree)
1430
tree.commit('a revision')
1431
tree.branch.create_checkout('target')
1432
self.failUnlessExists('target')
1433
self.failUnlessExists('target/subtree')
1435
def test_file_conflict_handling(self):
1436
"""Ensure that when building trees, conflict handling is done"""
1437
source = self.make_branch_and_tree('source')
1438
target = self.make_branch_and_tree('target')
1439
self.build_tree(['source/file', 'target/file'])
1440
source.add('file', 'new-file')
1441
source.commit('added file')
1442
build_tree(source.basis_tree(), target)
1443
self.assertEqual([DuplicateEntry('Moved existing file to',
1444
'file.moved', 'file', None, 'new-file')],
1446
target2 = self.make_branch_and_tree('target2')
1447
target_file = file('target2/file', 'wb')
1449
source_file = file('source/file', 'rb')
1451
target_file.write(source_file.read())
1456
build_tree(source.basis_tree(), target2)
1457
self.assertEqual([], target2.conflicts())
1459
def test_symlink_conflict_handling(self):
1460
"""Ensure that when building trees, conflict handling is done"""
1461
self.requireFeature(SymlinkFeature)
1462
source = self.make_branch_and_tree('source')
1463
os.symlink('foo', 'source/symlink')
1464
source.add('symlink', 'new-symlink')
1465
source.commit('added file')
1466
target = self.make_branch_and_tree('target')
1467
os.symlink('bar', 'target/symlink')
1468
build_tree(source.basis_tree(), target)
1469
self.assertEqual([DuplicateEntry('Moved existing file to',
1470
'symlink.moved', 'symlink', None, 'new-symlink')],
1472
target = self.make_branch_and_tree('target2')
1473
os.symlink('foo', 'target2/symlink')
1474
build_tree(source.basis_tree(), target)
1475
self.assertEqual([], target.conflicts())
1477
def test_directory_conflict_handling(self):
1478
"""Ensure that when building trees, conflict handling is done"""
1479
source = self.make_branch_and_tree('source')
1480
target = self.make_branch_and_tree('target')
1481
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1482
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1483
source.commit('added file')
1484
build_tree(source.basis_tree(), target)
1485
self.assertEqual([], target.conflicts())
1486
self.failUnlessExists('target/dir1/file')
1488
# Ensure contents are merged
1489
target = self.make_branch_and_tree('target2')
1490
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1491
build_tree(source.basis_tree(), target)
1492
self.assertEqual([], target.conflicts())
1493
self.failUnlessExists('target2/dir1/file2')
1494
self.failUnlessExists('target2/dir1/file')
1496
# Ensure new contents are suppressed for existing branches
1497
target = self.make_branch_and_tree('target3')
1498
self.make_branch('target3/dir1')
1499
self.build_tree(['target3/dir1/file2'])
1500
build_tree(source.basis_tree(), target)
1501
self.failIfExists('target3/dir1/file')
1502
self.failUnlessExists('target3/dir1/file2')
1503
self.failUnlessExists('target3/dir1.diverted/file')
1504
self.assertEqual([DuplicateEntry('Diverted to',
1505
'dir1.diverted', 'dir1', 'new-dir1', None)],
1508
target = self.make_branch_and_tree('target4')
1509
self.build_tree(['target4/dir1/'])
1510
self.make_branch('target4/dir1/file')
1511
build_tree(source.basis_tree(), target)
1512
self.failUnlessExists('target4/dir1/file')
1513
self.assertEqual('directory', file_kind('target4/dir1/file'))
1514
self.failUnlessExists('target4/dir1/file.diverted')
1515
self.assertEqual([DuplicateEntry('Diverted to',
1516
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1519
def test_mixed_conflict_handling(self):
1520
"""Ensure that when building trees, conflict handling is done"""
1521
source = self.make_branch_and_tree('source')
1522
target = self.make_branch_and_tree('target')
1523
self.build_tree(['source/name', 'target/name/'])
1524
source.add('name', 'new-name')
1525
source.commit('added file')
1526
build_tree(source.basis_tree(), target)
1527
self.assertEqual([DuplicateEntry('Moved existing file to',
1528
'name.moved', 'name', None, 'new-name')], target.conflicts())
1530
def test_raises_in_populated(self):
1531
source = self.make_branch_and_tree('source')
1532
self.build_tree(['source/name'])
1534
source.commit('added name')
1535
target = self.make_branch_and_tree('target')
1536
self.build_tree(['target/name'])
1538
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1539
build_tree, source.basis_tree(), target)
1541
def test_build_tree_rename_count(self):
1542
source = self.make_branch_and_tree('source')
1543
self.build_tree(['source/file1', 'source/dir1/'])
1544
source.add(['file1', 'dir1'])
1545
source.commit('add1')
1546
target1 = self.make_branch_and_tree('target1')
1547
transform_result = build_tree(source.basis_tree(), target1)
1548
self.assertEqual(2, transform_result.rename_count)
1550
self.build_tree(['source/dir1/file2'])
1551
source.add(['dir1/file2'])
1552
source.commit('add3')
1553
target2 = self.make_branch_and_tree('target2')
1554
transform_result = build_tree(source.basis_tree(), target2)
1555
# children of non-root directories should not be renamed
1556
self.assertEqual(2, transform_result.rename_count)
1558
def test_build_tree_accelerator_tree(self):
1559
source = self.make_branch_and_tree('source')
1560
self.build_tree_contents([('source/file1', 'A')])
1561
self.build_tree_contents([('source/file2', 'B')])
1562
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1563
source.commit('commit files')
1564
self.build_tree_contents([('source/file2', 'C')])
1566
real_source_get_file = source.get_file
1567
def get_file(file_id, path=None):
1568
calls.append(file_id)
1569
return real_source_get_file(file_id, path)
1570
source.get_file = get_file
1572
self.addCleanup(source.unlock)
1573
target = self.make_branch_and_tree('target')
1574
revision_tree = source.basis_tree()
1575
revision_tree.lock_read()
1576
self.addCleanup(revision_tree.unlock)
1577
build_tree(revision_tree, target, source)
1578
self.assertEqual(['file1-id'], calls)
1580
self.addCleanup(target.unlock)
1581
self.assertEqual([], list(target._iter_changes(revision_tree)))
1583
def test_build_tree_accelerator_tree_missing_file(self):
1584
source = self.make_branch_and_tree('source')
1585
self.build_tree_contents([('source/file1', 'A')])
1586
self.build_tree_contents([('source/file2', 'B')])
1587
source.add(['file1', 'file2'])
1588
source.commit('commit files')
1589
os.unlink('source/file1')
1590
source.remove(['file2'])
1591
target = self.make_branch_and_tree('target')
1592
revision_tree = source.basis_tree()
1593
revision_tree.lock_read()
1594
self.addCleanup(revision_tree.unlock)
1595
build_tree(revision_tree, target, source)
1597
self.addCleanup(target.unlock)
1598
self.assertEqual([], list(target._iter_changes(revision_tree)))
1600
def test_build_tree_accelerator_wrong_kind(self):
1601
source = self.make_branch_and_tree('source')
1602
self.build_tree_contents([('source/file1', '')])
1603
self.build_tree_contents([('source/file2', '')])
1604
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1605
source.commit('commit files')
1606
os.unlink('source/file2')
1607
self.build_tree_contents([('source/file2/', 'C')])
1608
os.unlink('source/file1')
1609
os.symlink('file2', 'source/file1')
1611
real_source_get_file = source.get_file
1612
def get_file(file_id, path=None):
1613
calls.append(file_id)
1614
return real_source_get_file(file_id, path)
1615
source.get_file = get_file
1617
self.addCleanup(source.unlock)
1618
target = self.make_branch_and_tree('target')
1619
revision_tree = source.basis_tree()
1620
revision_tree.lock_read()
1621
self.addCleanup(revision_tree.unlock)
1622
build_tree(revision_tree, target, source)
1623
self.assertEqual([], calls)
1625
self.addCleanup(target.unlock)
1626
self.assertEqual([], list(target._iter_changes(revision_tree)))
1628
def test_build_tree_accelerator_tree_moved(self):
1629
source = self.make_branch_and_tree('source')
1630
self.build_tree_contents([('source/file1', 'A')])
1631
source.add(['file1'], ['file1-id'])
1632
source.commit('commit files')
1633
source.rename_one('file1', 'file2')
1635
self.addCleanup(source.unlock)
1636
target = self.make_branch_and_tree('target')
1637
revision_tree = source.basis_tree()
1638
revision_tree.lock_read()
1639
self.addCleanup(revision_tree.unlock)
1640
build_tree(revision_tree, target, source)
1642
self.addCleanup(target.unlock)
1643
self.assertEqual([], list(target._iter_changes(revision_tree)))
1646
class MockTransform(object):
1648
def has_named_child(self, by_parent, parent_id, name):
1649
for child_id in by_parent[parent_id]:
1653
elif name == "name.~%s~" % child_id:
1658
class MockEntry(object):
1660
object.__init__(self)
1664
class TestGetBackupName(TestCase):
1665
def test_get_backup_name(self):
1666
tt = MockTransform()
1667
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1668
self.assertEqual(name, 'name.~1~')
1669
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1670
self.assertEqual(name, 'name.~2~')
1671
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1672
self.assertEqual(name, 'name.~1~')
1673
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1674
self.assertEqual(name, 'name.~1~')
1675
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1676
self.assertEqual(name, 'name.~4~')
1679
class TestFileMover(tests.TestCaseWithTransport):
1681
def test_file_mover(self):
1682
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1683
mover = _FileMover()
1684
mover.rename('a', 'q')
1685
self.failUnlessExists('q')
1686
self.failIfExists('a')
1687
self.failUnlessExists('q/b')
1688
self.failUnlessExists('c')
1689
self.failUnlessExists('c/d')
1691
def test_pre_delete_rollback(self):
1692
self.build_tree(['a/'])
1693
mover = _FileMover()
1694
mover.pre_delete('a', 'q')
1695
self.failUnlessExists('q')
1696
self.failIfExists('a')
1698
self.failIfExists('q')
1699
self.failUnlessExists('a')
1701
def test_apply_deletions(self):
1702
self.build_tree(['a/', 'b/'])
1703
mover = _FileMover()
1704
mover.pre_delete('a', 'q')
1705
mover.pre_delete('b', 'r')
1706
self.failUnlessExists('q')
1707
self.failUnlessExists('r')
1708
self.failIfExists('a')
1709
self.failIfExists('b')
1710
mover.apply_deletions()
1711
self.failIfExists('q')
1712
self.failIfExists('r')
1713
self.failIfExists('a')
1714
self.failIfExists('b')
1716
def test_file_mover_rollback(self):
1717
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1718
mover = _FileMover()
1719
mover.rename('c/d', 'c/f')
1720
mover.rename('c/e', 'c/d')
1722
mover.rename('a', 'c')
1723
except errors.FileExists, e:
1725
self.failUnlessExists('a')
1726
self.failUnlessExists('c/d')
1729
class Bogus(Exception):
1733
class TestTransformRollback(tests.TestCaseWithTransport):
1735
class ExceptionFileMover(_FileMover):
1737
def __init__(self, bad_source=None, bad_target=None):
1738
_FileMover.__init__(self)
1739
self.bad_source = bad_source
1740
self.bad_target = bad_target
1742
def rename(self, source, target):
1743
if (self.bad_source is not None and
1744
source.endswith(self.bad_source)):
1746
elif (self.bad_target is not None and
1747
target.endswith(self.bad_target)):
1750
_FileMover.rename(self, source, target)
1752
def test_rollback_rename(self):
1753
tree = self.make_branch_and_tree('.')
1754
self.build_tree(['a/', 'a/b'])
1755
tt = TreeTransform(tree)
1756
self.addCleanup(tt.finalize)
1757
a_id = tt.trans_id_tree_path('a')
1758
tt.adjust_path('c', tt.root, a_id)
1759
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1760
self.assertRaises(Bogus, tt.apply,
1761
_mover=self.ExceptionFileMover(bad_source='a'))
1762
self.failUnlessExists('a')
1763
self.failUnlessExists('a/b')
1765
self.failUnlessExists('c')
1766
self.failUnlessExists('c/d')
1768
def test_rollback_rename_into_place(self):
1769
tree = self.make_branch_and_tree('.')
1770
self.build_tree(['a/', 'a/b'])
1771
tt = TreeTransform(tree)
1772
self.addCleanup(tt.finalize)
1773
a_id = tt.trans_id_tree_path('a')
1774
tt.adjust_path('c', tt.root, a_id)
1775
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1776
self.assertRaises(Bogus, tt.apply,
1777
_mover=self.ExceptionFileMover(bad_target='c/d'))
1778
self.failUnlessExists('a')
1779
self.failUnlessExists('a/b')
1781
self.failUnlessExists('c')
1782
self.failUnlessExists('c/d')
1784
def test_rollback_deletion(self):
1785
tree = self.make_branch_and_tree('.')
1786
self.build_tree(['a/', 'a/b'])
1787
tt = TreeTransform(tree)
1788
self.addCleanup(tt.finalize)
1789
a_id = tt.trans_id_tree_path('a')
1790
tt.delete_contents(a_id)
1791
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1792
self.assertRaises(Bogus, tt.apply,
1793
_mover=self.ExceptionFileMover(bad_target='d'))
1794
self.failUnlessExists('a')
1795
self.failUnlessExists('a/b')
1797
def test_resolve_no_parent(self):
1798
wt = self.make_branch_and_tree('.')
1799
tt = TreeTransform(wt)
1800
self.addCleanup(tt.finalize)
1801
parent = tt.trans_id_file_id('parent-id')
1802
tt.new_file('file', parent, 'Contents')
1803
resolve_conflicts(tt)