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)
763
self.addCleanup(self.wt.unlock)
764
self.assertTrue(self.wt.is_executable('file1-id'))
765
transform, root = self.get_transform()
766
file1_id = transform.trans_id_tree_file_id('file1-id')
767
transform.delete_contents(file1_id)
768
transform.create_file('contents2', file1_id)
770
self.assertTrue(self.wt.is_executable('file1-id'))
772
def test__set_mode_stats_correctly(self):
773
"""_set_mode stats to determine file mode."""
774
if sys.platform == 'win32':
775
raise TestSkipped('chmod has no effect on win32')
779
def instrumented_stat(path):
780
stat_paths.append(path)
781
return real_stat(path)
783
transform, root = self.get_transform()
785
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
786
file_id='bar-id-1', executable=False)
789
transform, root = self.get_transform()
790
bar1_id = transform.trans_id_tree_path('bar')
791
bar2_id = transform.trans_id_tree_path('bar2')
793
os.stat = instrumented_stat
794
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
799
bar1_abspath = self.wt.abspath('bar')
800
self.assertEqual([bar1_abspath], stat_paths)
802
def test_iter_changes(self):
803
self.wt.set_root_id('eert_toor')
804
transform, root = self.get_transform()
805
transform.new_file('old', root, 'blah', 'id-1', True)
807
transform, root = self.get_transform()
809
self.assertEqual([], list(transform._iter_changes()))
810
old = transform.trans_id_tree_file_id('id-1')
811
transform.unversion_file(old)
812
self.assertEqual([('id-1', ('old', None), False, (True, False),
813
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
814
(True, True))], list(transform._iter_changes()))
815
transform.new_directory('new', root, 'id-1')
816
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
817
('eert_toor', 'eert_toor'), ('old', 'new'),
818
('file', 'directory'),
819
(True, False))], list(transform._iter_changes()))
823
def test_iter_changes_new(self):
824
self.wt.set_root_id('eert_toor')
825
transform, root = self.get_transform()
826
transform.new_file('old', root, 'blah')
828
transform, root = self.get_transform()
830
old = transform.trans_id_tree_path('old')
831
transform.version_file('id-1', old)
832
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
833
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
834
(False, False))], list(transform._iter_changes()))
838
def test_iter_changes_modifications(self):
839
self.wt.set_root_id('eert_toor')
840
transform, root = self.get_transform()
841
transform.new_file('old', root, 'blah', 'id-1')
842
transform.new_file('new', root, 'blah')
843
transform.new_directory('subdir', root, 'subdir-id')
845
transform, root = self.get_transform()
847
old = transform.trans_id_tree_path('old')
848
subdir = transform.trans_id_tree_file_id('subdir-id')
849
new = transform.trans_id_tree_path('new')
850
self.assertEqual([], list(transform._iter_changes()))
853
transform.delete_contents(old)
854
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
855
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
856
(False, False))], list(transform._iter_changes()))
859
transform.create_file('blah', old)
860
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
861
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
862
(False, False))], list(transform._iter_changes()))
863
transform.cancel_deletion(old)
864
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
865
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
866
(False, False))], list(transform._iter_changes()))
867
transform.cancel_creation(old)
869
# move file_id to a different file
870
self.assertEqual([], list(transform._iter_changes()))
871
transform.unversion_file(old)
872
transform.version_file('id-1', new)
873
transform.adjust_path('old', root, new)
874
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
875
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
876
(False, False))], list(transform._iter_changes()))
877
transform.cancel_versioning(new)
878
transform._removed_id = set()
881
self.assertEqual([], list(transform._iter_changes()))
882
transform.set_executability(True, old)
883
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
884
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
885
(False, True))], list(transform._iter_changes()))
886
transform.set_executability(None, old)
889
self.assertEqual([], list(transform._iter_changes()))
890
transform.adjust_path('new', root, old)
891
transform._new_parent = {}
892
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
893
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
894
(False, False))], list(transform._iter_changes()))
895
transform._new_name = {}
898
self.assertEqual([], list(transform._iter_changes()))
899
transform.adjust_path('new', subdir, old)
900
transform._new_name = {}
901
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
902
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
903
('file', 'file'), (False, False))],
904
list(transform._iter_changes()))
905
transform._new_path = {}
910
def test_iter_changes_modified_bleed(self):
911
self.wt.set_root_id('eert_toor')
912
"""Modified flag should not bleed from one change to another"""
913
# unfortunately, we have no guarantee that file1 (which is modified)
914
# will be applied before file2. And if it's applied after file2, it
915
# obviously can't bleed into file2's change output. But for now, it
917
transform, root = self.get_transform()
918
transform.new_file('file1', root, 'blah', 'id-1')
919
transform.new_file('file2', root, 'blah', 'id-2')
921
transform, root = self.get_transform()
923
transform.delete_contents(transform.trans_id_file_id('id-1'))
924
transform.set_executability(True,
925
transform.trans_id_file_id('id-2'))
926
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
927
('eert_toor', 'eert_toor'), ('file1', u'file1'),
928
('file', None), (False, False)),
929
('id-2', (u'file2', u'file2'), False, (True, True),
930
('eert_toor', 'eert_toor'), ('file2', u'file2'),
931
('file', 'file'), (False, True))],
932
list(transform._iter_changes()))
936
def test_iter_changes_move_missing(self):
937
"""Test moving ids with no files around"""
938
self.wt.set_root_id('toor_eert')
939
# Need two steps because versioning a non-existant file is a conflict.
940
transform, root = self.get_transform()
941
transform.new_directory('floater', root, 'floater-id')
943
transform, root = self.get_transform()
944
transform.delete_contents(transform.trans_id_tree_path('floater'))
946
transform, root = self.get_transform()
947
floater = transform.trans_id_tree_path('floater')
949
transform.adjust_path('flitter', root, floater)
950
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
951
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
952
(None, None), (False, False))], list(transform._iter_changes()))
956
def test_iter_changes_pointless(self):
957
"""Ensure that no-ops are not treated as modifications"""
958
self.wt.set_root_id('eert_toor')
959
transform, root = self.get_transform()
960
transform.new_file('old', root, 'blah', 'id-1')
961
transform.new_directory('subdir', root, 'subdir-id')
963
transform, root = self.get_transform()
965
old = transform.trans_id_tree_path('old')
966
subdir = transform.trans_id_tree_file_id('subdir-id')
967
self.assertEqual([], list(transform._iter_changes()))
968
transform.delete_contents(subdir)
969
transform.create_directory(subdir)
970
transform.set_executability(False, old)
971
transform.unversion_file(old)
972
transform.version_file('id-1', old)
973
transform.adjust_path('old', root, old)
974
self.assertEqual([], list(transform._iter_changes()))
978
def test_rename_count(self):
979
transform, root = self.get_transform()
980
transform.new_file('name1', root, 'contents')
981
self.assertEqual(transform.rename_count, 0)
983
self.assertEqual(transform.rename_count, 1)
984
transform2, root = self.get_transform()
985
transform2.adjust_path('name2', root,
986
transform2.trans_id_tree_path('name1'))
987
self.assertEqual(transform2.rename_count, 0)
989
self.assertEqual(transform2.rename_count, 2)
991
def test_change_parent(self):
992
"""Ensure that after we change a parent, the results are still right.
994
Renames and parent changes on pending transforms can happen as part
995
of conflict resolution, and are explicitly permitted by the
998
This test ensures they work correctly with the rename-avoidance
1001
transform, root = self.get_transform()
1002
parent1 = transform.new_directory('parent1', root)
1003
child1 = transform.new_file('child1', parent1, 'contents')
1004
parent2 = transform.new_directory('parent2', root)
1005
transform.adjust_path('child1', parent2, child1)
1007
self.failIfExists(self.wt.abspath('parent1/child1'))
1008
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1009
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1010
# no rename for child1 (counting only renames during apply)
1011
self.failUnlessEqual(2, transform.rename_count)
1013
def test_cancel_parent(self):
1014
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1016
This is like the test_change_parent, except that we cancel the parent
1017
before adjusting the path. The transform must detect that the
1018
directory is non-empty, and move children to safe locations.
1020
transform, root = self.get_transform()
1021
parent1 = transform.new_directory('parent1', root)
1022
child1 = transform.new_file('child1', parent1, 'contents')
1023
child2 = transform.new_file('child2', parent1, 'contents')
1025
transform.cancel_creation(parent1)
1027
self.fail('Failed to move child1 before deleting parent1')
1028
transform.cancel_creation(child2)
1029
transform.create_directory(parent1)
1031
transform.cancel_creation(parent1)
1032
# If the transform incorrectly believes that child2 is still in
1033
# parent1's limbo directory, it will try to rename it and fail
1034
# because was already moved by the first cancel_creation.
1036
self.fail('Transform still thinks child2 is a child of parent1')
1037
parent2 = transform.new_directory('parent2', root)
1038
transform.adjust_path('child1', parent2, child1)
1040
self.failIfExists(self.wt.abspath('parent1'))
1041
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1042
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1043
self.failUnlessEqual(2, transform.rename_count)
1045
def test_adjust_and_cancel(self):
1046
"""Make sure adjust_path keeps track of limbo children properly"""
1047
transform, root = self.get_transform()
1048
parent1 = transform.new_directory('parent1', root)
1049
child1 = transform.new_file('child1', parent1, 'contents')
1050
parent2 = transform.new_directory('parent2', root)
1051
transform.adjust_path('child1', parent2, child1)
1052
transform.cancel_creation(child1)
1054
transform.cancel_creation(parent1)
1055
# if the transform thinks child1 is still in parent1's limbo
1056
# directory, it will attempt to move it and fail.
1058
self.fail('Transform still thinks child1 is a child of parent1')
1059
transform.finalize()
1061
def test_noname_contents(self):
1062
"""TreeTransform should permit deferring naming files."""
1063
transform, root = self.get_transform()
1064
parent = transform.trans_id_file_id('parent-id')
1066
transform.create_directory(parent)
1068
self.fail("Can't handle contents with no name")
1069
transform.finalize()
1071
def test_noname_contents_nested(self):
1072
"""TreeTransform should permit deferring naming files."""
1073
transform, root = self.get_transform()
1074
parent = transform.trans_id_file_id('parent-id')
1076
transform.create_directory(parent)
1078
self.fail("Can't handle contents with no name")
1079
child = transform.new_directory('child', parent)
1080
transform.adjust_path('parent', root, parent)
1082
self.failUnlessExists(self.wt.abspath('parent/child'))
1083
self.assertEqual(1, transform.rename_count)
1085
def test_reuse_name(self):
1086
"""Avoid reusing the same limbo name for different files"""
1087
transform, root = self.get_transform()
1088
parent = transform.new_directory('parent', root)
1089
child1 = transform.new_directory('child', parent)
1091
child2 = transform.new_directory('child', parent)
1093
self.fail('Tranform tried to use the same limbo name twice')
1094
transform.adjust_path('child2', parent, child2)
1096
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1097
# child2 is put into top-level limbo because child1 has already
1098
# claimed the direct limbo path when child2 is created. There is no
1099
# advantage in renaming files once they're in top-level limbo, except
1101
self.assertEqual(2, transform.rename_count)
1103
def test_reuse_when_first_moved(self):
1104
"""Don't avoid direct paths when it is safe to use them"""
1105
transform, root = self.get_transform()
1106
parent = transform.new_directory('parent', root)
1107
child1 = transform.new_directory('child', parent)
1108
transform.adjust_path('child1', parent, child1)
1109
child2 = transform.new_directory('child', parent)
1111
# limbo/new-1 => parent
1112
self.assertEqual(1, transform.rename_count)
1114
def test_reuse_after_cancel(self):
1115
"""Don't avoid direct paths when it is safe to use them"""
1116
transform, root = self.get_transform()
1117
parent2 = transform.new_directory('parent2', root)
1118
child1 = transform.new_directory('child1', parent2)
1119
transform.cancel_creation(parent2)
1120
transform.create_directory(parent2)
1121
child2 = transform.new_directory('child1', parent2)
1122
transform.adjust_path('child2', parent2, child1)
1124
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1125
self.assertEqual(2, transform.rename_count)
1127
def test_finalize_order(self):
1128
"""Finalize must be done in child-to-parent order"""
1129
transform, root = self.get_transform()
1130
parent = transform.new_directory('parent', root)
1131
child = transform.new_directory('child', parent)
1133
transform.finalize()
1135
self.fail('Tried to remove parent before child1')
1137
def test_cancel_with_cancelled_child_should_succeed(self):
1138
transform, root = self.get_transform()
1139
parent = transform.new_directory('parent', root)
1140
child = transform.new_directory('child', parent)
1141
transform.cancel_creation(child)
1142
transform.cancel_creation(parent)
1143
transform.finalize()
1145
def test_change_entry(self):
1146
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1147
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1150
def test_case_insensitive_clash(self):
1151
self.requireFeature(CaseInsensitiveFilesystemFeature)
1153
wt = self.make_branch_and_tree('.')
1154
tt = TreeTransform(wt) # TreeTransform obtains write lock
1156
tt.new_file('foo', tt.root, 'bar')
1157
tt.new_file('Foo', tt.root, 'spam')
1158
# Lie to tt that we've already resolved all conflicts.
1159
tt.apply(no_conflicts=True)
1163
err = self.assertRaises(errors.FileExists, tt_helper)
1164
self.assertContainsRe(str(err),
1165
"^File exists: .+/foo")
1167
def test_two_directories_clash(self):
1169
wt = self.make_branch_and_tree('.')
1170
tt = TreeTransform(wt) # TreeTransform obtains write lock
1172
foo_1 = tt.new_directory('foo', tt.root)
1173
tt.new_directory('bar', foo_1)
1174
foo_2 = tt.new_directory('foo', tt.root)
1175
tt.new_directory('baz', foo_2)
1176
# Lie to tt that we've already resolved all conflicts.
1177
tt.apply(no_conflicts=True)
1181
err = self.assertRaises(errors.FileExists, tt_helper)
1182
self.assertContainsRe(str(err),
1183
"^File exists: .+/foo")
1185
def test_two_directories_clash_finalize(self):
1187
wt = self.make_branch_and_tree('.')
1188
tt = TreeTransform(wt) # TreeTransform obtains write lock
1190
foo_1 = tt.new_directory('foo', tt.root)
1191
tt.new_directory('bar', foo_1)
1192
foo_2 = tt.new_directory('foo', tt.root)
1193
tt.new_directory('baz', foo_2)
1194
# Lie to tt that we've already resolved all conflicts.
1195
tt.apply(no_conflicts=True)
1199
err = self.assertRaises(errors.FileExists, tt_helper)
1200
self.assertContainsRe(str(err),
1201
"^File exists: .+/foo")
1204
class TransformGroup(object):
1206
def __init__(self, dirname, root_id):
1209
self.wt = BzrDir.create_standalone_workingtree(dirname)
1210
self.wt.set_root_id(root_id)
1211
self.b = self.wt.branch
1212
self.tt = TreeTransform(self.wt)
1213
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1216
def conflict_text(tree, merge):
1217
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1218
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1221
class TestTransformMerge(TestCaseInTempDir):
1222
def test_text_merge(self):
1223
root_id = generate_ids.gen_root_id()
1224
base = TransformGroup("base", root_id)
1225
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1226
base.tt.new_file('b', base.root, 'b1', 'b')
1227
base.tt.new_file('c', base.root, 'c', 'c')
1228
base.tt.new_file('d', base.root, 'd', 'd')
1229
base.tt.new_file('e', base.root, 'e', 'e')
1230
base.tt.new_file('f', base.root, 'f', 'f')
1231
base.tt.new_directory('g', base.root, 'g')
1232
base.tt.new_directory('h', base.root, 'h')
1234
other = TransformGroup("other", root_id)
1235
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1236
other.tt.new_file('b', other.root, 'b2', 'b')
1237
other.tt.new_file('c', other.root, 'c2', 'c')
1238
other.tt.new_file('d', other.root, 'd', 'd')
1239
other.tt.new_file('e', other.root, 'e2', 'e')
1240
other.tt.new_file('f', other.root, 'f', 'f')
1241
other.tt.new_file('g', other.root, 'g', 'g')
1242
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1243
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1245
this = TransformGroup("this", root_id)
1246
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1247
this.tt.new_file('b', this.root, 'b', 'b')
1248
this.tt.new_file('c', this.root, 'c', 'c')
1249
this.tt.new_file('d', this.root, 'd2', 'd')
1250
this.tt.new_file('e', this.root, 'e2', 'e')
1251
this.tt.new_file('f', this.root, 'f', 'f')
1252
this.tt.new_file('g', this.root, 'g', 'g')
1253
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1254
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1256
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1258
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1259
# three-way text conflict
1260
self.assertEqual(this.wt.get_file('b').read(),
1261
conflict_text('b', 'b2'))
1263
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1265
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1266
# Ambigious clean merge
1267
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1269
self.assertEqual(this.wt.get_file('f').read(), 'f')
1270
# Correct correct results when THIS == OTHER
1271
self.assertEqual(this.wt.get_file('g').read(), 'g')
1272
# Text conflict when THIS & OTHER are text and BASE is dir
1273
self.assertEqual(this.wt.get_file('h').read(),
1274
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1275
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1277
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1279
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1280
self.assertEqual(this.wt.get_file('i').read(),
1281
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1282
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1284
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1286
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1287
modified = ['a', 'b', 'c', 'h', 'i']
1288
merge_modified = this.wt.merge_modified()
1289
self.assertSubset(merge_modified, modified)
1290
self.assertEqual(len(merge_modified), len(modified))
1291
file(this.wt.id2abspath('a'), 'wb').write('booga')
1293
merge_modified = this.wt.merge_modified()
1294
self.assertSubset(merge_modified, modified)
1295
self.assertEqual(len(merge_modified), len(modified))
1299
def test_file_merge(self):
1300
self.requireFeature(SymlinkFeature)
1301
root_id = generate_ids.gen_root_id()
1302
base = TransformGroup("BASE", root_id)
1303
this = TransformGroup("THIS", root_id)
1304
other = TransformGroup("OTHER", root_id)
1305
for tg in this, base, other:
1306
tg.tt.new_directory('a', tg.root, 'a')
1307
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1308
tg.tt.new_file('c', tg.root, 'c', 'c')
1309
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1310
targets = ((base, 'base-e', 'base-f', None, None),
1311
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1312
(other, 'other-e', None, 'other-g', 'other-h'))
1313
for tg, e_target, f_target, g_target, h_target in targets:
1314
for link, target in (('e', e_target), ('f', f_target),
1315
('g', g_target), ('h', h_target)):
1316
if target is not None:
1317
tg.tt.new_symlink(link, tg.root, target, link)
1319
for tg in this, base, other:
1321
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1322
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1323
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1324
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1325
for suffix in ('THIS', 'BASE', 'OTHER'):
1326
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1327
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1328
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1329
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1330
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1331
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1332
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1333
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1334
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1335
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1336
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1337
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1338
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1339
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1341
def test_filename_merge(self):
1342
root_id = generate_ids.gen_root_id()
1343
base = TransformGroup("BASE", root_id)
1344
this = TransformGroup("THIS", root_id)
1345
other = TransformGroup("OTHER", root_id)
1346
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1347
for t in [base, this, other]]
1348
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1349
for t in [base, this, other]]
1350
base.tt.new_directory('c', base_a, 'c')
1351
this.tt.new_directory('c1', this_a, 'c')
1352
other.tt.new_directory('c', other_b, 'c')
1354
base.tt.new_directory('d', base_a, 'd')
1355
this.tt.new_directory('d1', this_b, 'd')
1356
other.tt.new_directory('d', other_a, 'd')
1358
base.tt.new_directory('e', base_a, 'e')
1359
this.tt.new_directory('e', this_a, 'e')
1360
other.tt.new_directory('e1', other_b, 'e')
1362
base.tt.new_directory('f', base_a, 'f')
1363
this.tt.new_directory('f1', this_b, 'f')
1364
other.tt.new_directory('f1', other_b, 'f')
1366
for tg in [this, base, other]:
1368
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1369
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1370
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1371
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1372
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1374
def test_filename_merge_conflicts(self):
1375
root_id = generate_ids.gen_root_id()
1376
base = TransformGroup("BASE", root_id)
1377
this = TransformGroup("THIS", root_id)
1378
other = TransformGroup("OTHER", root_id)
1379
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1380
for t in [base, this, other]]
1381
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1382
for t in [base, this, other]]
1384
base.tt.new_file('g', base_a, 'g', 'g')
1385
other.tt.new_file('g1', other_b, 'g1', 'g')
1387
base.tt.new_file('h', base_a, 'h', 'h')
1388
this.tt.new_file('h1', this_b, 'h1', 'h')
1390
base.tt.new_file('i', base.root, 'i', 'i')
1391
other.tt.new_directory('i1', this_b, 'i')
1393
for tg in [this, base, other]:
1395
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1397
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1398
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1399
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1400
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1401
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1402
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1403
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1406
class TestBuildTree(tests.TestCaseWithTransport):
1408
def test_build_tree_with_symlinks(self):
1409
self.requireFeature(SymlinkFeature)
1411
a = BzrDir.create_standalone_workingtree('a')
1413
file('a/foo/bar', 'wb').write('contents')
1414
os.symlink('a/foo/bar', 'a/foo/baz')
1415
a.add(['foo', 'foo/bar', 'foo/baz'])
1416
a.commit('initial commit')
1417
b = BzrDir.create_standalone_workingtree('b')
1418
basis = a.basis_tree()
1420
self.addCleanup(basis.unlock)
1421
build_tree(basis, b)
1422
self.assertIs(os.path.isdir('b/foo'), True)
1423
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1424
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1426
def test_build_with_references(self):
1427
tree = self.make_branch_and_tree('source',
1428
format='dirstate-with-subtree')
1429
subtree = self.make_branch_and_tree('source/subtree',
1430
format='dirstate-with-subtree')
1431
tree.add_reference(subtree)
1432
tree.commit('a revision')
1433
tree.branch.create_checkout('target')
1434
self.failUnlessExists('target')
1435
self.failUnlessExists('target/subtree')
1437
def test_file_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/file', 'target/file'])
1442
source.add('file', 'new-file')
1443
source.commit('added file')
1444
build_tree(source.basis_tree(), target)
1445
self.assertEqual([DuplicateEntry('Moved existing file to',
1446
'file.moved', 'file', None, 'new-file')],
1448
target2 = self.make_branch_and_tree('target2')
1449
target_file = file('target2/file', 'wb')
1451
source_file = file('source/file', 'rb')
1453
target_file.write(source_file.read())
1458
build_tree(source.basis_tree(), target2)
1459
self.assertEqual([], target2.conflicts())
1461
def test_symlink_conflict_handling(self):
1462
"""Ensure that when building trees, conflict handling is done"""
1463
self.requireFeature(SymlinkFeature)
1464
source = self.make_branch_and_tree('source')
1465
os.symlink('foo', 'source/symlink')
1466
source.add('symlink', 'new-symlink')
1467
source.commit('added file')
1468
target = self.make_branch_and_tree('target')
1469
os.symlink('bar', 'target/symlink')
1470
build_tree(source.basis_tree(), target)
1471
self.assertEqual([DuplicateEntry('Moved existing file to',
1472
'symlink.moved', 'symlink', None, 'new-symlink')],
1474
target = self.make_branch_and_tree('target2')
1475
os.symlink('foo', 'target2/symlink')
1476
build_tree(source.basis_tree(), target)
1477
self.assertEqual([], target.conflicts())
1479
def test_directory_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/dir1/', 'source/dir1/file', 'target/dir1/'])
1484
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1485
source.commit('added file')
1486
build_tree(source.basis_tree(), target)
1487
self.assertEqual([], target.conflicts())
1488
self.failUnlessExists('target/dir1/file')
1490
# Ensure contents are merged
1491
target = self.make_branch_and_tree('target2')
1492
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1493
build_tree(source.basis_tree(), target)
1494
self.assertEqual([], target.conflicts())
1495
self.failUnlessExists('target2/dir1/file2')
1496
self.failUnlessExists('target2/dir1/file')
1498
# Ensure new contents are suppressed for existing branches
1499
target = self.make_branch_and_tree('target3')
1500
self.make_branch('target3/dir1')
1501
self.build_tree(['target3/dir1/file2'])
1502
build_tree(source.basis_tree(), target)
1503
self.failIfExists('target3/dir1/file')
1504
self.failUnlessExists('target3/dir1/file2')
1505
self.failUnlessExists('target3/dir1.diverted/file')
1506
self.assertEqual([DuplicateEntry('Diverted to',
1507
'dir1.diverted', 'dir1', 'new-dir1', None)],
1510
target = self.make_branch_and_tree('target4')
1511
self.build_tree(['target4/dir1/'])
1512
self.make_branch('target4/dir1/file')
1513
build_tree(source.basis_tree(), target)
1514
self.failUnlessExists('target4/dir1/file')
1515
self.assertEqual('directory', file_kind('target4/dir1/file'))
1516
self.failUnlessExists('target4/dir1/file.diverted')
1517
self.assertEqual([DuplicateEntry('Diverted to',
1518
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1521
def test_mixed_conflict_handling(self):
1522
"""Ensure that when building trees, conflict handling is done"""
1523
source = self.make_branch_and_tree('source')
1524
target = self.make_branch_and_tree('target')
1525
self.build_tree(['source/name', 'target/name/'])
1526
source.add('name', 'new-name')
1527
source.commit('added file')
1528
build_tree(source.basis_tree(), target)
1529
self.assertEqual([DuplicateEntry('Moved existing file to',
1530
'name.moved', 'name', None, 'new-name')], target.conflicts())
1532
def test_raises_in_populated(self):
1533
source = self.make_branch_and_tree('source')
1534
self.build_tree(['source/name'])
1536
source.commit('added name')
1537
target = self.make_branch_and_tree('target')
1538
self.build_tree(['target/name'])
1540
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1541
build_tree, source.basis_tree(), target)
1543
def test_build_tree_rename_count(self):
1544
source = self.make_branch_and_tree('source')
1545
self.build_tree(['source/file1', 'source/dir1/'])
1546
source.add(['file1', 'dir1'])
1547
source.commit('add1')
1548
target1 = self.make_branch_and_tree('target1')
1549
transform_result = build_tree(source.basis_tree(), target1)
1550
self.assertEqual(2, transform_result.rename_count)
1552
self.build_tree(['source/dir1/file2'])
1553
source.add(['dir1/file2'])
1554
source.commit('add3')
1555
target2 = self.make_branch_and_tree('target2')
1556
transform_result = build_tree(source.basis_tree(), target2)
1557
# children of non-root directories should not be renamed
1558
self.assertEqual(2, transform_result.rename_count)
1560
def test_build_tree_accelerator_tree(self):
1561
source = self.make_branch_and_tree('source')
1562
self.build_tree_contents([('source/file1', 'A')])
1563
self.build_tree_contents([('source/file2', 'B')])
1564
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1565
source.commit('commit files')
1566
self.build_tree_contents([('source/file2', 'C')])
1568
real_source_get_file = source.get_file
1569
def get_file(file_id, path=None):
1570
calls.append(file_id)
1571
return real_source_get_file(file_id, path)
1572
source.get_file = get_file
1574
self.addCleanup(source.unlock)
1575
target = self.make_branch_and_tree('target')
1576
revision_tree = source.basis_tree()
1577
revision_tree.lock_read()
1578
self.addCleanup(revision_tree.unlock)
1579
build_tree(revision_tree, target, source)
1580
self.assertEqual(['file1-id'], calls)
1582
self.addCleanup(target.unlock)
1583
self.assertEqual([], list(target._iter_changes(revision_tree)))
1585
def test_build_tree_accelerator_tree_missing_file(self):
1586
source = self.make_branch_and_tree('source')
1587
self.build_tree_contents([('source/file1', 'A')])
1588
self.build_tree_contents([('source/file2', 'B')])
1589
source.add(['file1', 'file2'])
1590
source.commit('commit files')
1591
os.unlink('source/file1')
1592
source.remove(['file2'])
1593
target = self.make_branch_and_tree('target')
1594
revision_tree = source.basis_tree()
1595
revision_tree.lock_read()
1596
self.addCleanup(revision_tree.unlock)
1597
build_tree(revision_tree, target, source)
1599
self.addCleanup(target.unlock)
1600
self.assertEqual([], list(target._iter_changes(revision_tree)))
1602
def test_build_tree_accelerator_wrong_kind(self):
1603
self.requireFeature(SymlinkFeature)
1604
source = self.make_branch_and_tree('source')
1605
self.build_tree_contents([('source/file1', '')])
1606
self.build_tree_contents([('source/file2', '')])
1607
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1608
source.commit('commit files')
1609
os.unlink('source/file2')
1610
self.build_tree_contents([('source/file2/', 'C')])
1611
os.unlink('source/file1')
1612
os.symlink('file2', 'source/file1')
1614
real_source_get_file = source.get_file
1615
def get_file(file_id, path=None):
1616
calls.append(file_id)
1617
return real_source_get_file(file_id, path)
1618
source.get_file = get_file
1620
self.addCleanup(source.unlock)
1621
target = self.make_branch_and_tree('target')
1622
revision_tree = source.basis_tree()
1623
revision_tree.lock_read()
1624
self.addCleanup(revision_tree.unlock)
1625
build_tree(revision_tree, target, source)
1626
self.assertEqual([], calls)
1628
self.addCleanup(target.unlock)
1629
self.assertEqual([], list(target._iter_changes(revision_tree)))
1631
def test_build_tree_accelerator_tree_moved(self):
1632
source = self.make_branch_and_tree('source')
1633
self.build_tree_contents([('source/file1', 'A')])
1634
source.add(['file1'], ['file1-id'])
1635
source.commit('commit files')
1636
source.rename_one('file1', 'file2')
1638
self.addCleanup(source.unlock)
1639
target = self.make_branch_and_tree('target')
1640
revision_tree = source.basis_tree()
1641
revision_tree.lock_read()
1642
self.addCleanup(revision_tree.unlock)
1643
build_tree(revision_tree, target, source)
1645
self.addCleanup(target.unlock)
1646
self.assertEqual([], list(target._iter_changes(revision_tree)))
1649
class MockTransform(object):
1651
def has_named_child(self, by_parent, parent_id, name):
1652
for child_id in by_parent[parent_id]:
1656
elif name == "name.~%s~" % child_id:
1661
class MockEntry(object):
1663
object.__init__(self)
1667
class TestGetBackupName(TestCase):
1668
def test_get_backup_name(self):
1669
tt = MockTransform()
1670
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1671
self.assertEqual(name, 'name.~1~')
1672
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1673
self.assertEqual(name, 'name.~2~')
1674
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1675
self.assertEqual(name, 'name.~1~')
1676
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1677
self.assertEqual(name, 'name.~1~')
1678
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1679
self.assertEqual(name, 'name.~4~')
1682
class TestFileMover(tests.TestCaseWithTransport):
1684
def test_file_mover(self):
1685
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1686
mover = _FileMover()
1687
mover.rename('a', 'q')
1688
self.failUnlessExists('q')
1689
self.failIfExists('a')
1690
self.failUnlessExists('q/b')
1691
self.failUnlessExists('c')
1692
self.failUnlessExists('c/d')
1694
def test_pre_delete_rollback(self):
1695
self.build_tree(['a/'])
1696
mover = _FileMover()
1697
mover.pre_delete('a', 'q')
1698
self.failUnlessExists('q')
1699
self.failIfExists('a')
1701
self.failIfExists('q')
1702
self.failUnlessExists('a')
1704
def test_apply_deletions(self):
1705
self.build_tree(['a/', 'b/'])
1706
mover = _FileMover()
1707
mover.pre_delete('a', 'q')
1708
mover.pre_delete('b', 'r')
1709
self.failUnlessExists('q')
1710
self.failUnlessExists('r')
1711
self.failIfExists('a')
1712
self.failIfExists('b')
1713
mover.apply_deletions()
1714
self.failIfExists('q')
1715
self.failIfExists('r')
1716
self.failIfExists('a')
1717
self.failIfExists('b')
1719
def test_file_mover_rollback(self):
1720
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1721
mover = _FileMover()
1722
mover.rename('c/d', 'c/f')
1723
mover.rename('c/e', 'c/d')
1725
mover.rename('a', 'c')
1726
except errors.FileExists, e:
1728
self.failUnlessExists('a')
1729
self.failUnlessExists('c/d')
1732
class Bogus(Exception):
1736
class TestTransformRollback(tests.TestCaseWithTransport):
1738
class ExceptionFileMover(_FileMover):
1740
def __init__(self, bad_source=None, bad_target=None):
1741
_FileMover.__init__(self)
1742
self.bad_source = bad_source
1743
self.bad_target = bad_target
1745
def rename(self, source, target):
1746
if (self.bad_source is not None and
1747
source.endswith(self.bad_source)):
1749
elif (self.bad_target is not None and
1750
target.endswith(self.bad_target)):
1753
_FileMover.rename(self, source, target)
1755
def test_rollback_rename(self):
1756
tree = self.make_branch_and_tree('.')
1757
self.build_tree(['a/', 'a/b'])
1758
tt = TreeTransform(tree)
1759
self.addCleanup(tt.finalize)
1760
a_id = tt.trans_id_tree_path('a')
1761
tt.adjust_path('c', tt.root, a_id)
1762
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1763
self.assertRaises(Bogus, tt.apply,
1764
_mover=self.ExceptionFileMover(bad_source='a'))
1765
self.failUnlessExists('a')
1766
self.failUnlessExists('a/b')
1768
self.failUnlessExists('c')
1769
self.failUnlessExists('c/d')
1771
def test_rollback_rename_into_place(self):
1772
tree = self.make_branch_and_tree('.')
1773
self.build_tree(['a/', 'a/b'])
1774
tt = TreeTransform(tree)
1775
self.addCleanup(tt.finalize)
1776
a_id = tt.trans_id_tree_path('a')
1777
tt.adjust_path('c', tt.root, a_id)
1778
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1779
self.assertRaises(Bogus, tt.apply,
1780
_mover=self.ExceptionFileMover(bad_target='c/d'))
1781
self.failUnlessExists('a')
1782
self.failUnlessExists('a/b')
1784
self.failUnlessExists('c')
1785
self.failUnlessExists('c/d')
1787
def test_rollback_deletion(self):
1788
tree = self.make_branch_and_tree('.')
1789
self.build_tree(['a/', 'a/b'])
1790
tt = TreeTransform(tree)
1791
self.addCleanup(tt.finalize)
1792
a_id = tt.trans_id_tree_path('a')
1793
tt.delete_contents(a_id)
1794
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1795
self.assertRaises(Bogus, tt.apply,
1796
_mover=self.ExceptionFileMover(bad_target='d'))
1797
self.failUnlessExists('a')
1798
self.failUnlessExists('a/b')
1800
def test_resolve_no_parent(self):
1801
wt = self.make_branch_and_tree('.')
1802
tt = TreeTransform(wt)
1803
self.addCleanup(tt.finalize)
1804
parent = tt.trans_id_file_id('parent-id')
1805
tt.new_file('file', parent, 'Contents')
1806
resolve_conflicts(tt)