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
19
from StringIO import StringIO
27
revision as _mod_revision,
32
from bzrlib.bzrdir import BzrDir
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
UnversionedParent, ParentLoop, DeletingParent,
36
from bzrlib.diff import show_diff_trees
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
39
PathsNotVersionedError, ExistingLimbo,
40
ExistingPendingDeletion, ImmortalLimbo,
41
ImmortalPendingDeletion, LockError)
42
from bzrlib.osutils import file_kind, pathjoin
43
from bzrlib.merge import Merge3Merger
44
from bzrlib.tests import (
45
CaseInsensitiveFilesystemFeature,
51
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
52
resolve_conflicts, cook_conflicts,
53
find_interesting, build_tree, get_backup_name,
54
change_entry, _FileMover, resolve_checkout,
57
class TestTreeTransform(tests.TestCaseWithTransport):
60
super(TestTreeTransform, self).setUp()
61
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
64
def get_transform(self):
65
transform = TreeTransform(self.wt)
66
#self.addCleanup(transform.finalize)
67
return transform, transform.root
69
def test_existing_limbo(self):
70
transform, root = self.get_transform()
71
limbo_name = transform._limbodir
72
deletion_path = transform._deletiondir
73
os.mkdir(pathjoin(limbo_name, 'hehe'))
74
self.assertRaises(ImmortalLimbo, transform.apply)
75
self.assertRaises(LockError, self.wt.unlock)
76
self.assertRaises(ExistingLimbo, self.get_transform)
77
self.assertRaises(LockError, self.wt.unlock)
78
os.rmdir(pathjoin(limbo_name, 'hehe'))
80
os.rmdir(deletion_path)
81
transform, root = self.get_transform()
84
def test_existing_pending_deletion(self):
85
transform, root = self.get_transform()
86
deletion_path = self._limbodir = urlutils.local_path_from_url(
87
transform._tree._control_files.controlfilename('pending-deletion'))
88
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
89
self.assertRaises(ImmortalPendingDeletion, transform.apply)
90
self.assertRaises(LockError, self.wt.unlock)
91
self.assertRaises(ExistingPendingDeletion, self.get_transform)
94
transform, root = self.get_transform()
95
self.wt.lock_tree_write()
96
self.addCleanup(self.wt.unlock)
97
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
98
imaginary_id = transform.trans_id_tree_path('imaginary')
99
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
100
self.assertEqual(imaginary_id, imaginary_id2)
101
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
102
self.assertEqual(transform.final_kind(root), 'directory')
103
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
104
trans_id = transform.create_path('name', root)
105
self.assertIs(transform.final_file_id(trans_id), None)
106
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
107
transform.create_file('contents', trans_id)
108
transform.set_executability(True, trans_id)
109
transform.version_file('my_pretties', trans_id)
110
self.assertRaises(DuplicateKey, transform.version_file,
111
'my_pretties', trans_id)
112
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
113
self.assertEqual(transform.final_parent(trans_id), root)
114
self.assertIs(transform.final_parent(root), ROOT_PARENT)
115
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
116
oz_id = transform.create_path('oz', root)
117
transform.create_directory(oz_id)
118
transform.version_file('ozzie', oz_id)
119
trans_id2 = transform.create_path('name2', root)
120
transform.create_file('contents', trans_id2)
121
transform.set_executability(False, trans_id2)
122
transform.version_file('my_pretties2', trans_id2)
123
modified_paths = transform.apply().modified_paths
124
self.assertEqual('contents', self.wt.get_file_byname('name').read())
125
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
126
self.assertIs(self.wt.is_executable('my_pretties'), True)
127
self.assertIs(self.wt.is_executable('my_pretties2'), False)
128
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
129
self.assertEqual(len(modified_paths), 3)
130
tree_mod_paths = [self.wt.id2abspath(f) for f in
131
('ozzie', 'my_pretties', 'my_pretties2')]
132
self.assertSubset(tree_mod_paths, modified_paths)
133
# is it safe to finalize repeatedly?
137
def test_convenience(self):
138
transform, root = self.get_transform()
139
self.wt.lock_tree_write()
140
self.addCleanup(self.wt.unlock)
141
trans_id = transform.new_file('name', root, 'contents',
143
oz = transform.new_directory('oz', root, 'oz-id')
144
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
145
toto = transform.new_file('toto', dorothy, 'toto-contents',
148
self.assertEqual(len(transform.find_conflicts()), 0)
150
self.assertRaises(ReusingTransform, transform.find_conflicts)
151
self.assertEqual('contents', file(self.wt.abspath('name')).read())
152
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
153
self.assertIs(self.wt.is_executable('my_pretties'), True)
154
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
155
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
156
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
158
self.assertEqual('toto-contents',
159
self.wt.get_file_byname('oz/dorothy/toto').read())
160
self.assertIs(self.wt.is_executable('toto-id'), False)
162
def test_tree_reference(self):
163
transform, root = self.get_transform()
164
tree = transform._tree
165
trans_id = transform.new_directory('reference', root, 'subtree-id')
166
transform.set_tree_reference('subtree-revision', trans_id)
169
self.addCleanup(tree.unlock)
170
self.assertEqual('subtree-revision',
171
tree.inventory['subtree-id'].reference_revision)
173
def test_conflicts(self):
174
transform, root = self.get_transform()
175
trans_id = transform.new_file('name', root, 'contents',
177
self.assertEqual(len(transform.find_conflicts()), 0)
178
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
179
self.assertEqual(transform.find_conflicts(),
180
[('duplicate', trans_id, trans_id2, 'name')])
181
self.assertRaises(MalformedTransform, transform.apply)
182
transform.adjust_path('name', trans_id, trans_id2)
183
self.assertEqual(transform.find_conflicts(),
184
[('non-directory parent', trans_id)])
185
tinman_id = transform.trans_id_tree_path('tinman')
186
transform.adjust_path('name', tinman_id, trans_id2)
187
self.assertEqual(transform.find_conflicts(),
188
[('unversioned parent', tinman_id),
189
('missing parent', tinman_id)])
190
lion_id = transform.create_path('lion', root)
191
self.assertEqual(transform.find_conflicts(),
192
[('unversioned parent', tinman_id),
193
('missing parent', tinman_id)])
194
transform.adjust_path('name', lion_id, trans_id2)
195
self.assertEqual(transform.find_conflicts(),
196
[('unversioned parent', lion_id),
197
('missing parent', lion_id)])
198
transform.version_file("Courage", lion_id)
199
self.assertEqual(transform.find_conflicts(),
200
[('missing parent', lion_id),
201
('versioning no contents', lion_id)])
202
transform.adjust_path('name2', root, trans_id2)
203
self.assertEqual(transform.find_conflicts(),
204
[('versioning no contents', lion_id)])
205
transform.create_file('Contents, okay?', lion_id)
206
transform.adjust_path('name2', trans_id2, trans_id2)
207
self.assertEqual(transform.find_conflicts(),
208
[('parent loop', trans_id2),
209
('non-directory parent', trans_id2)])
210
transform.adjust_path('name2', root, trans_id2)
211
oz_id = transform.new_directory('oz', root)
212
transform.set_executability(True, oz_id)
213
self.assertEqual(transform.find_conflicts(),
214
[('unversioned executability', oz_id)])
215
transform.version_file('oz-id', oz_id)
216
self.assertEqual(transform.find_conflicts(),
217
[('non-file executability', oz_id)])
218
transform.set_executability(None, oz_id)
219
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
221
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
222
self.assertEqual('contents', file(self.wt.abspath('name')).read())
223
transform2, root = self.get_transform()
224
oz_id = transform2.trans_id_tree_file_id('oz-id')
225
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
226
result = transform2.find_conflicts()
227
fp = FinalPaths(transform2)
228
self.assert_('oz/tip' in transform2._tree_path_ids)
229
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
230
self.assertEqual(len(result), 2)
231
self.assertEqual((result[0][0], result[0][1]),
232
('duplicate', newtip))
233
self.assertEqual((result[1][0], result[1][2]),
234
('duplicate id', newtip))
235
transform2.finalize()
236
transform3 = TreeTransform(self.wt)
237
self.addCleanup(transform3.finalize)
238
oz_id = transform3.trans_id_tree_file_id('oz-id')
239
transform3.delete_contents(oz_id)
240
self.assertEqual(transform3.find_conflicts(),
241
[('missing parent', oz_id)])
242
root_id = transform3.root
243
tip_id = transform3.trans_id_tree_file_id('tip-id')
244
transform3.adjust_path('tip', root_id, tip_id)
247
def test_conflict_on_case_insensitive(self):
248
tree = self.make_branch_and_tree('tree')
249
# Don't try this at home, kids!
250
# Force the tree to report that it is case sensitive, for conflict
252
tree.case_sensitive = True
253
transform = TreeTransform(tree)
254
self.addCleanup(transform.finalize)
255
transform.new_file('file', transform.root, 'content')
256
transform.new_file('FiLe', transform.root, 'content')
257
result = transform.find_conflicts()
258
self.assertEqual([], result)
260
# Force the tree to report that it is case insensitive, for conflict
262
tree.case_sensitive = False
263
transform = TreeTransform(tree)
264
self.addCleanup(transform.finalize)
265
transform.new_file('file', transform.root, 'content')
266
transform.new_file('FiLe', transform.root, 'content')
267
result = transform.find_conflicts()
268
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
270
def test_conflict_on_case_insensitive_existing(self):
271
tree = self.make_branch_and_tree('tree')
272
self.build_tree(['tree/FiLe'])
273
# Don't try this at home, kids!
274
# Force the tree to report that it is case sensitive, for conflict
276
tree.case_sensitive = True
277
transform = TreeTransform(tree)
278
self.addCleanup(transform.finalize)
279
transform.new_file('file', transform.root, 'content')
280
result = transform.find_conflicts()
281
self.assertEqual([], result)
283
# Force the tree to report that it is case insensitive, for conflict
285
tree.case_sensitive = False
286
transform = TreeTransform(tree)
287
self.addCleanup(transform.finalize)
288
transform.new_file('file', transform.root, 'content')
289
result = transform.find_conflicts()
290
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
292
def test_resolve_case_insensitive_conflict(self):
293
tree = self.make_branch_and_tree('tree')
294
# Don't try this at home, kids!
295
# Force the tree to report that it is case insensitive, for conflict
297
tree.case_sensitive = False
298
transform = TreeTransform(tree)
299
self.addCleanup(transform.finalize)
300
transform.new_file('file', transform.root, 'content')
301
transform.new_file('FiLe', transform.root, 'content')
302
resolve_conflicts(transform)
304
self.failUnlessExists('tree/file')
305
self.failUnlessExists('tree/FiLe.moved')
307
def test_resolve_checkout_case_conflict(self):
308
tree = self.make_branch_and_tree('tree')
309
# Don't try this at home, kids!
310
# Force the tree to report that it is case insensitive, for conflict
312
tree.case_sensitive = False
313
transform = TreeTransform(tree)
314
self.addCleanup(transform.finalize)
315
transform.new_file('file', transform.root, 'content')
316
transform.new_file('FiLe', transform.root, 'content')
317
resolve_conflicts(transform,
318
pass_func=lambda t, c: resolve_checkout(t, c, []))
320
self.failUnlessExists('tree/file')
321
self.failUnlessExists('tree/FiLe.moved')
323
def test_apply_case_conflict(self):
324
"""Ensure that a transform with case conflicts can always be applied"""
325
tree = self.make_branch_and_tree('tree')
326
transform = TreeTransform(tree)
327
self.addCleanup(transform.finalize)
328
transform.new_file('file', transform.root, 'content')
329
transform.new_file('FiLe', transform.root, 'content')
330
dir = transform.new_directory('dir', transform.root)
331
transform.new_file('dirfile', dir, 'content')
332
transform.new_file('dirFiLe', dir, 'content')
333
resolve_conflicts(transform)
335
self.failUnlessExists('tree/file')
336
if not os.path.exists('tree/FiLe.moved'):
337
self.failUnlessExists('tree/FiLe')
338
self.failUnlessExists('tree/dir/dirfile')
339
if not os.path.exists('tree/dir/dirFiLe.moved'):
340
self.failUnlessExists('tree/dir/dirFiLe')
342
def test_case_insensitive_limbo(self):
343
tree = self.make_branch_and_tree('tree')
344
# Don't try this at home, kids!
345
# Force the tree to report that it is case insensitive
346
tree.case_sensitive = False
347
transform = TreeTransform(tree)
348
self.addCleanup(transform.finalize)
349
dir = transform.new_directory('dir', transform.root)
350
first = transform.new_file('file', dir, 'content')
351
second = transform.new_file('FiLe', dir, 'content')
352
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
353
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
355
def test_add_del(self):
356
start, root = self.get_transform()
357
start.new_directory('a', root, 'a')
359
transform, root = self.get_transform()
360
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
361
transform.new_directory('a', root, 'a')
364
def test_unversioning(self):
365
create_tree, root = self.get_transform()
366
parent_id = create_tree.new_directory('parent', root, 'parent-id')
367
create_tree.new_file('child', parent_id, 'child', 'child-id')
369
unversion = TreeTransform(self.wt)
370
self.addCleanup(unversion.finalize)
371
parent = unversion.trans_id_tree_path('parent')
372
unversion.unversion_file(parent)
373
self.assertEqual(unversion.find_conflicts(),
374
[('unversioned parent', parent_id)])
375
file_id = unversion.trans_id_tree_file_id('child-id')
376
unversion.unversion_file(file_id)
379
def test_name_invariants(self):
380
create_tree, root = self.get_transform()
382
root = create_tree.root
383
create_tree.new_file('name1', root, 'hello1', 'name1')
384
create_tree.new_file('name2', root, 'hello2', 'name2')
385
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
386
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
387
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
388
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
391
mangle_tree,root = self.get_transform()
392
root = mangle_tree.root
394
name1 = mangle_tree.trans_id_tree_file_id('name1')
395
name2 = mangle_tree.trans_id_tree_file_id('name2')
396
mangle_tree.adjust_path('name2', root, name1)
397
mangle_tree.adjust_path('name1', root, name2)
399
#tests for deleting parent directories
400
ddir = mangle_tree.trans_id_tree_file_id('ddir')
401
mangle_tree.delete_contents(ddir)
402
dfile = mangle_tree.trans_id_tree_file_id('dfile')
403
mangle_tree.delete_versioned(dfile)
404
mangle_tree.unversion_file(dfile)
405
mfile = mangle_tree.trans_id_tree_file_id('mfile')
406
mangle_tree.adjust_path('mfile', root, mfile)
408
#tests for adding parent directories
409
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
410
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
411
mangle_tree.adjust_path('mfile2', newdir, mfile2)
412
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
413
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
414
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
415
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
417
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
418
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
419
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
420
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
421
self.assertEqual(file(mfile2_path).read(), 'later2')
422
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
423
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
424
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
425
self.assertEqual(file(newfile_path).read(), 'hello3')
426
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
427
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
428
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
430
def test_both_rename(self):
431
create_tree,root = self.get_transform()
432
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
433
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
435
mangle_tree,root = self.get_transform()
436
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
437
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
438
mangle_tree.adjust_path('test', root, selftest)
439
mangle_tree.adjust_path('test_too_much', root, selftest)
440
mangle_tree.set_executability(True, blackbox)
443
def test_both_rename2(self):
444
create_tree,root = self.get_transform()
445
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
446
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
447
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
448
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
451
mangle_tree,root = self.get_transform()
452
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
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', bzrlib, tests)
456
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
457
mangle_tree.set_executability(True, test_too_much)
460
def test_both_rename3(self):
461
create_tree,root = self.get_transform()
462
tests = create_tree.new_directory('tests', root, 'tests-id')
463
create_tree.new_file('test_too_much.py', tests, 'hello1',
466
mangle_tree,root = self.get_transform()
467
tests = mangle_tree.trans_id_tree_file_id('tests-id')
468
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
469
mangle_tree.adjust_path('selftest', root, tests)
470
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
471
mangle_tree.set_executability(True, test_too_much)
474
def test_move_dangling_ie(self):
475
create_tree, root = self.get_transform()
477
root = create_tree.root
478
create_tree.new_file('name1', root, 'hello1', 'name1')
480
delete_contents, root = self.get_transform()
481
file = delete_contents.trans_id_tree_file_id('name1')
482
delete_contents.delete_contents(file)
483
delete_contents.apply()
484
move_id, root = self.get_transform()
485
name1 = move_id.trans_id_tree_file_id('name1')
486
newdir = move_id.new_directory('dir', root, 'newdir')
487
move_id.adjust_path('name2', newdir, name1)
490
def test_replace_dangling_ie(self):
491
create_tree, root = self.get_transform()
493
root = create_tree.root
494
create_tree.new_file('name1', root, 'hello1', 'name1')
496
delete_contents = TreeTransform(self.wt)
497
self.addCleanup(delete_contents.finalize)
498
file = delete_contents.trans_id_tree_file_id('name1')
499
delete_contents.delete_contents(file)
500
delete_contents.apply()
501
delete_contents.finalize()
502
replace = TreeTransform(self.wt)
503
self.addCleanup(replace.finalize)
504
name2 = replace.new_file('name2', root, 'hello2', 'name1')
505
conflicts = replace.find_conflicts()
506
name1 = replace.trans_id_tree_file_id('name1')
507
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
508
resolve_conflicts(replace)
511
def test_symlinks(self):
512
self.requireFeature(SymlinkFeature)
513
transform,root = self.get_transform()
514
oz_id = transform.new_directory('oz', root, 'oz-id')
515
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
517
wiz_id = transform.create_path('wizard2', oz_id)
518
transform.create_symlink('behind_curtain', wiz_id)
519
transform.version_file('wiz-id2', wiz_id)
520
transform.set_executability(True, wiz_id)
521
self.assertEqual(transform.find_conflicts(),
522
[('non-file executability', wiz_id)])
523
transform.set_executability(None, wiz_id)
525
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
526
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
527
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
529
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
532
def test_unable_create_symlink(self):
534
wt = self.make_branch_and_tree('.')
535
tt = TreeTransform(wt) # TreeTransform obtains write lock
537
tt.new_symlink('foo', tt.root, 'bar')
541
os_symlink = getattr(os, 'symlink', None)
544
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
546
"Unable to create symlink 'foo' on this platform",
550
os.symlink = os_symlink
552
def get_conflicted(self):
553
create,root = self.get_transform()
554
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
555
oz = create.new_directory('oz', root, 'oz-id')
556
create.new_directory('emeraldcity', oz, 'emerald-id')
558
conflicts,root = self.get_transform()
559
# set up duplicate entry, duplicate id
560
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
562
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
563
oz = conflicts.trans_id_tree_file_id('oz-id')
564
# set up DeletedParent parent conflict
565
conflicts.delete_versioned(oz)
566
emerald = conflicts.trans_id_tree_file_id('emerald-id')
567
# set up MissingParent conflict
568
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
569
conflicts.adjust_path('munchkincity', root, munchkincity)
570
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
572
conflicts.adjust_path('emeraldcity', emerald, emerald)
573
return conflicts, emerald, oz, old_dorothy, new_dorothy
575
def test_conflict_resolution(self):
576
conflicts, emerald, oz, old_dorothy, new_dorothy =\
577
self.get_conflicted()
578
resolve_conflicts(conflicts)
579
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
580
self.assertIs(conflicts.final_file_id(old_dorothy), None)
581
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
582
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
583
self.assertEqual(conflicts.final_parent(emerald), oz)
586
def test_cook_conflicts(self):
587
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
588
raw_conflicts = resolve_conflicts(tt)
589
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
590
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
591
'dorothy', None, 'dorothy-id')
592
self.assertEqual(cooked_conflicts[0], duplicate)
593
duplicate_id = DuplicateID('Unversioned existing file',
594
'dorothy.moved', 'dorothy', None,
596
self.assertEqual(cooked_conflicts[1], duplicate_id)
597
missing_parent = MissingParent('Created directory', 'munchkincity',
599
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
600
self.assertEqual(cooked_conflicts[2], missing_parent)
601
unversioned_parent = UnversionedParent('Versioned directory',
604
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
606
self.assertEqual(cooked_conflicts[3], unversioned_parent)
607
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
608
'oz/emeraldcity', 'emerald-id', 'emerald-id')
609
self.assertEqual(cooked_conflicts[4], deleted_parent)
610
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
611
self.assertEqual(cooked_conflicts[6], parent_loop)
612
self.assertEqual(len(cooked_conflicts), 7)
615
def test_string_conflicts(self):
616
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
617
raw_conflicts = resolve_conflicts(tt)
618
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
620
conflicts_s = [str(c) for c in cooked_conflicts]
621
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
622
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
623
'Moved existing file to '
625
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
626
'Unversioned existing file '
628
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
629
' munchkincity. Created directory.')
630
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
631
' versioned, but has versioned'
632
' children. Versioned directory.')
633
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
634
" is not empty. Not deleting.")
635
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
636
' versioned, but has versioned'
637
' children. Versioned directory.')
638
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
639
' oz/emeraldcity. Cancelled move.')
641
def prepare_wrong_parent_kind(self):
642
tt, root = self.get_transform()
643
tt.new_file('parent', root, 'contents', 'parent-id')
645
tt, root = self.get_transform()
646
parent_id = tt.trans_id_file_id('parent-id')
647
tt.new_file('child,', parent_id, 'contents2', 'file-id')
650
def test_find_conflicts_wrong_parent_kind(self):
651
tt = self.prepare_wrong_parent_kind()
654
def test_resolve_conflicts_wrong_existing_parent_kind(self):
655
tt = self.prepare_wrong_parent_kind()
656
raw_conflicts = resolve_conflicts(tt)
657
self.assertEqual(set([('non-directory parent', 'Created directory',
658
'new-3')]), raw_conflicts)
659
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
660
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
661
'parent-id')], cooked_conflicts)
663
self.assertEqual(None, self.wt.path2id('parent'))
664
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
666
def test_resolve_conflicts_wrong_new_parent_kind(self):
667
tt, root = self.get_transform()
668
parent_id = tt.new_directory('parent', root, 'parent-id')
669
tt.new_file('child,', parent_id, 'contents2', 'file-id')
671
tt, root = self.get_transform()
672
parent_id = tt.trans_id_file_id('parent-id')
673
tt.delete_contents(parent_id)
674
tt.create_file('contents', parent_id)
675
raw_conflicts = resolve_conflicts(tt)
676
self.assertEqual(set([('non-directory parent', 'Created directory',
677
'new-3')]), raw_conflicts)
679
self.assertEqual(None, self.wt.path2id('parent'))
680
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
682
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
683
tt, root = self.get_transform()
684
parent_id = tt.new_directory('parent', root)
685
tt.new_file('child,', parent_id, 'contents2')
687
tt, root = self.get_transform()
688
parent_id = tt.trans_id_tree_path('parent')
689
tt.delete_contents(parent_id)
690
tt.create_file('contents', parent_id)
691
resolve_conflicts(tt)
693
self.assertIs(None, self.wt.path2id('parent'))
694
self.assertIs(None, self.wt.path2id('parent.new'))
696
def test_moving_versioned_directories(self):
697
create, root = self.get_transform()
698
kansas = create.new_directory('kansas', root, 'kansas-id')
699
create.new_directory('house', kansas, 'house-id')
700
create.new_directory('oz', root, 'oz-id')
702
cyclone, root = self.get_transform()
703
oz = cyclone.trans_id_tree_file_id('oz-id')
704
house = cyclone.trans_id_tree_file_id('house-id')
705
cyclone.adjust_path('house', oz, house)
708
def test_moving_root(self):
709
create, root = self.get_transform()
710
fun = create.new_directory('fun', root, 'fun-id')
711
create.new_directory('sun', root, 'sun-id')
712
create.new_directory('moon', root, 'moon')
714
transform, root = self.get_transform()
715
transform.adjust_root_path('oldroot', fun)
716
new_root=transform.trans_id_tree_path('')
717
transform.version_file('new-root', new_root)
720
def test_renames(self):
721
create, root = self.get_transform()
722
old = create.new_directory('old-parent', root, 'old-id')
723
intermediate = create.new_directory('intermediate', old, 'im-id')
724
myfile = create.new_file('myfile', intermediate, 'myfile-text',
727
rename, root = self.get_transform()
728
old = rename.trans_id_file_id('old-id')
729
rename.adjust_path('new', root, old)
730
myfile = rename.trans_id_file_id('myfile-id')
731
rename.set_executability(True, myfile)
734
def test_find_interesting(self):
735
create, root = self.get_transform()
737
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
738
create.new_file('uvfile', root, 'othertext')
740
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
741
find_interesting, wt, wt, ['vfile'])
742
self.assertEqual(result, set(['myfile-id']))
744
def test_set_executability_order(self):
745
"""Ensure that executability behaves the same, no matter what order.
747
- create file and set executability simultaneously
748
- create file and set executability afterward
749
- unsetting the executability of a file whose executability has not been
750
declared should throw an exception (this may happen when a
751
merge attempts to create a file with a duplicate ID)
753
transform, root = self.get_transform()
756
self.addCleanup(wt.unlock)
757
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
759
sac = transform.new_file('set_after_creation', root,
760
'Set after creation', 'sac')
761
transform.set_executability(True, sac)
762
uws = transform.new_file('unset_without_set', root, 'Unset badly',
764
self.assertRaises(KeyError, transform.set_executability, None, uws)
766
self.assertTrue(wt.is_executable('soc'))
767
self.assertTrue(wt.is_executable('sac'))
769
def test_preserve_mode(self):
770
"""File mode is preserved when replacing content"""
771
if sys.platform == 'win32':
772
raise TestSkipped('chmod has no effect on win32')
773
transform, root = self.get_transform()
774
transform.new_file('file1', root, 'contents', 'file1-id', True)
777
self.addCleanup(self.wt.unlock)
778
self.assertTrue(self.wt.is_executable('file1-id'))
779
transform, root = self.get_transform()
780
file1_id = transform.trans_id_tree_file_id('file1-id')
781
transform.delete_contents(file1_id)
782
transform.create_file('contents2', file1_id)
784
self.assertTrue(self.wt.is_executable('file1-id'))
786
def test__set_mode_stats_correctly(self):
787
"""_set_mode stats to determine file mode."""
788
if sys.platform == 'win32':
789
raise TestSkipped('chmod has no effect on win32')
793
def instrumented_stat(path):
794
stat_paths.append(path)
795
return real_stat(path)
797
transform, root = self.get_transform()
799
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
800
file_id='bar-id-1', executable=False)
803
transform, root = self.get_transform()
804
bar1_id = transform.trans_id_tree_path('bar')
805
bar2_id = transform.trans_id_tree_path('bar2')
807
os.stat = instrumented_stat
808
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
813
bar1_abspath = self.wt.abspath('bar')
814
self.assertEqual([bar1_abspath], stat_paths)
816
def test_iter_changes(self):
817
self.wt.set_root_id('eert_toor')
818
transform, root = self.get_transform()
819
transform.new_file('old', root, 'blah', 'id-1', True)
821
transform, root = self.get_transform()
823
self.assertEqual([], list(transform._iter_changes()))
824
old = transform.trans_id_tree_file_id('id-1')
825
transform.unversion_file(old)
826
self.assertEqual([('id-1', ('old', None), False, (True, False),
827
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
828
(True, True))], list(transform._iter_changes()))
829
transform.new_directory('new', root, 'id-1')
830
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
831
('eert_toor', 'eert_toor'), ('old', 'new'),
832
('file', 'directory'),
833
(True, False))], list(transform._iter_changes()))
837
def test_iter_changes_new(self):
838
self.wt.set_root_id('eert_toor')
839
transform, root = self.get_transform()
840
transform.new_file('old', root, 'blah')
842
transform, root = self.get_transform()
844
old = transform.trans_id_tree_path('old')
845
transform.version_file('id-1', old)
846
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
847
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
848
(False, False))], list(transform._iter_changes()))
852
def test_iter_changes_modifications(self):
853
self.wt.set_root_id('eert_toor')
854
transform, root = self.get_transform()
855
transform.new_file('old', root, 'blah', 'id-1')
856
transform.new_file('new', root, 'blah')
857
transform.new_directory('subdir', root, 'subdir-id')
859
transform, root = self.get_transform()
861
old = transform.trans_id_tree_path('old')
862
subdir = transform.trans_id_tree_file_id('subdir-id')
863
new = transform.trans_id_tree_path('new')
864
self.assertEqual([], list(transform._iter_changes()))
867
transform.delete_contents(old)
868
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
869
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
870
(False, False))], list(transform._iter_changes()))
873
transform.create_file('blah', old)
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_deletion(old)
878
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
879
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
880
(False, False))], list(transform._iter_changes()))
881
transform.cancel_creation(old)
883
# move file_id to a different file
884
self.assertEqual([], list(transform._iter_changes()))
885
transform.unversion_file(old)
886
transform.version_file('id-1', new)
887
transform.adjust_path('old', root, new)
888
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
889
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
890
(False, False))], list(transform._iter_changes()))
891
transform.cancel_versioning(new)
892
transform._removed_id = set()
895
self.assertEqual([], list(transform._iter_changes()))
896
transform.set_executability(True, old)
897
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
898
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
899
(False, True))], list(transform._iter_changes()))
900
transform.set_executability(None, old)
903
self.assertEqual([], list(transform._iter_changes()))
904
transform.adjust_path('new', root, old)
905
transform._new_parent = {}
906
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
907
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
908
(False, False))], list(transform._iter_changes()))
909
transform._new_name = {}
912
self.assertEqual([], list(transform._iter_changes()))
913
transform.adjust_path('new', subdir, old)
914
transform._new_name = {}
915
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
916
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
917
('file', 'file'), (False, False))],
918
list(transform._iter_changes()))
919
transform._new_path = {}
924
def test_iter_changes_modified_bleed(self):
925
self.wt.set_root_id('eert_toor')
926
"""Modified flag should not bleed from one change to another"""
927
# unfortunately, we have no guarantee that file1 (which is modified)
928
# will be applied before file2. And if it's applied after file2, it
929
# obviously can't bleed into file2's change output. But for now, it
931
transform, root = self.get_transform()
932
transform.new_file('file1', root, 'blah', 'id-1')
933
transform.new_file('file2', root, 'blah', 'id-2')
935
transform, root = self.get_transform()
937
transform.delete_contents(transform.trans_id_file_id('id-1'))
938
transform.set_executability(True,
939
transform.trans_id_file_id('id-2'))
940
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
941
('eert_toor', 'eert_toor'), ('file1', u'file1'),
942
('file', None), (False, False)),
943
('id-2', (u'file2', u'file2'), False, (True, True),
944
('eert_toor', 'eert_toor'), ('file2', u'file2'),
945
('file', 'file'), (False, True))],
946
list(transform._iter_changes()))
950
def test_iter_changes_move_missing(self):
951
"""Test moving ids with no files around"""
952
self.wt.set_root_id('toor_eert')
953
# Need two steps because versioning a non-existant file is a conflict.
954
transform, root = self.get_transform()
955
transform.new_directory('floater', root, 'floater-id')
957
transform, root = self.get_transform()
958
transform.delete_contents(transform.trans_id_tree_path('floater'))
960
transform, root = self.get_transform()
961
floater = transform.trans_id_tree_path('floater')
963
transform.adjust_path('flitter', root, floater)
964
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
965
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
966
(None, None), (False, False))], list(transform._iter_changes()))
970
def test_iter_changes_pointless(self):
971
"""Ensure that no-ops are not treated as modifications"""
972
self.wt.set_root_id('eert_toor')
973
transform, root = self.get_transform()
974
transform.new_file('old', root, 'blah', 'id-1')
975
transform.new_directory('subdir', root, 'subdir-id')
977
transform, root = self.get_transform()
979
old = transform.trans_id_tree_path('old')
980
subdir = transform.trans_id_tree_file_id('subdir-id')
981
self.assertEqual([], list(transform._iter_changes()))
982
transform.delete_contents(subdir)
983
transform.create_directory(subdir)
984
transform.set_executability(False, old)
985
transform.unversion_file(old)
986
transform.version_file('id-1', old)
987
transform.adjust_path('old', root, old)
988
self.assertEqual([], list(transform._iter_changes()))
992
def test_rename_count(self):
993
transform, root = self.get_transform()
994
transform.new_file('name1', root, 'contents')
995
self.assertEqual(transform.rename_count, 0)
997
self.assertEqual(transform.rename_count, 1)
998
transform2, root = self.get_transform()
999
transform2.adjust_path('name2', root,
1000
transform2.trans_id_tree_path('name1'))
1001
self.assertEqual(transform2.rename_count, 0)
1003
self.assertEqual(transform2.rename_count, 2)
1005
def test_change_parent(self):
1006
"""Ensure that after we change a parent, the results are still right.
1008
Renames and parent changes on pending transforms can happen as part
1009
of conflict resolution, and are explicitly permitted by the
1012
This test ensures they work correctly with the rename-avoidance
1015
transform, root = self.get_transform()
1016
parent1 = transform.new_directory('parent1', root)
1017
child1 = transform.new_file('child1', parent1, 'contents')
1018
parent2 = transform.new_directory('parent2', root)
1019
transform.adjust_path('child1', parent2, child1)
1021
self.failIfExists(self.wt.abspath('parent1/child1'))
1022
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1023
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1024
# no rename for child1 (counting only renames during apply)
1025
self.failUnlessEqual(2, transform.rename_count)
1027
def test_cancel_parent(self):
1028
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1030
This is like the test_change_parent, except that we cancel the parent
1031
before adjusting the path. The transform must detect that the
1032
directory is non-empty, and move children to safe locations.
1034
transform, root = self.get_transform()
1035
parent1 = transform.new_directory('parent1', root)
1036
child1 = transform.new_file('child1', parent1, 'contents')
1037
child2 = transform.new_file('child2', parent1, 'contents')
1039
transform.cancel_creation(parent1)
1041
self.fail('Failed to move child1 before deleting parent1')
1042
transform.cancel_creation(child2)
1043
transform.create_directory(parent1)
1045
transform.cancel_creation(parent1)
1046
# If the transform incorrectly believes that child2 is still in
1047
# parent1's limbo directory, it will try to rename it and fail
1048
# because was already moved by the first cancel_creation.
1050
self.fail('Transform still thinks child2 is a child of parent1')
1051
parent2 = transform.new_directory('parent2', root)
1052
transform.adjust_path('child1', parent2, child1)
1054
self.failIfExists(self.wt.abspath('parent1'))
1055
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1056
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1057
self.failUnlessEqual(2, transform.rename_count)
1059
def test_adjust_and_cancel(self):
1060
"""Make sure adjust_path keeps track of limbo children properly"""
1061
transform, root = self.get_transform()
1062
parent1 = transform.new_directory('parent1', root)
1063
child1 = transform.new_file('child1', parent1, 'contents')
1064
parent2 = transform.new_directory('parent2', root)
1065
transform.adjust_path('child1', parent2, child1)
1066
transform.cancel_creation(child1)
1068
transform.cancel_creation(parent1)
1069
# if the transform thinks child1 is still in parent1's limbo
1070
# directory, it will attempt to move it and fail.
1072
self.fail('Transform still thinks child1 is a child of parent1')
1073
transform.finalize()
1075
def test_noname_contents(self):
1076
"""TreeTransform should permit deferring naming files."""
1077
transform, root = self.get_transform()
1078
parent = transform.trans_id_file_id('parent-id')
1080
transform.create_directory(parent)
1082
self.fail("Can't handle contents with no name")
1083
transform.finalize()
1085
def test_noname_contents_nested(self):
1086
"""TreeTransform should permit deferring naming files."""
1087
transform, root = self.get_transform()
1088
parent = transform.trans_id_file_id('parent-id')
1090
transform.create_directory(parent)
1092
self.fail("Can't handle contents with no name")
1093
child = transform.new_directory('child', parent)
1094
transform.adjust_path('parent', root, parent)
1096
self.failUnlessExists(self.wt.abspath('parent/child'))
1097
self.assertEqual(1, transform.rename_count)
1099
def test_reuse_name(self):
1100
"""Avoid reusing the same limbo name for different files"""
1101
transform, root = self.get_transform()
1102
parent = transform.new_directory('parent', root)
1103
child1 = transform.new_directory('child', parent)
1105
child2 = transform.new_directory('child', parent)
1107
self.fail('Tranform tried to use the same limbo name twice')
1108
transform.adjust_path('child2', parent, child2)
1110
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1111
# child2 is put into top-level limbo because child1 has already
1112
# claimed the direct limbo path when child2 is created. There is no
1113
# advantage in renaming files once they're in top-level limbo, except
1115
self.assertEqual(2, transform.rename_count)
1117
def test_reuse_when_first_moved(self):
1118
"""Don't avoid direct paths when it is safe to use them"""
1119
transform, root = self.get_transform()
1120
parent = transform.new_directory('parent', root)
1121
child1 = transform.new_directory('child', parent)
1122
transform.adjust_path('child1', parent, child1)
1123
child2 = transform.new_directory('child', parent)
1125
# limbo/new-1 => parent
1126
self.assertEqual(1, transform.rename_count)
1128
def test_reuse_after_cancel(self):
1129
"""Don't avoid direct paths when it is safe to use them"""
1130
transform, root = self.get_transform()
1131
parent2 = transform.new_directory('parent2', root)
1132
child1 = transform.new_directory('child1', parent2)
1133
transform.cancel_creation(parent2)
1134
transform.create_directory(parent2)
1135
child2 = transform.new_directory('child1', parent2)
1136
transform.adjust_path('child2', parent2, child1)
1138
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1139
self.assertEqual(2, transform.rename_count)
1141
def test_finalize_order(self):
1142
"""Finalize must be done in child-to-parent order"""
1143
transform, root = self.get_transform()
1144
parent = transform.new_directory('parent', root)
1145
child = transform.new_directory('child', parent)
1147
transform.finalize()
1149
self.fail('Tried to remove parent before child1')
1151
def test_cancel_with_cancelled_child_should_succeed(self):
1152
transform, root = self.get_transform()
1153
parent = transform.new_directory('parent', root)
1154
child = transform.new_directory('child', parent)
1155
transform.cancel_creation(child)
1156
transform.cancel_creation(parent)
1157
transform.finalize()
1159
def test_change_entry(self):
1160
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1161
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1164
def test_case_insensitive_clash(self):
1165
self.requireFeature(CaseInsensitiveFilesystemFeature)
1167
wt = self.make_branch_and_tree('.')
1168
tt = TreeTransform(wt) # TreeTransform obtains write lock
1170
tt.new_file('foo', tt.root, 'bar')
1171
tt.new_file('Foo', tt.root, 'spam')
1172
# Lie to tt that we've already resolved all conflicts.
1173
tt.apply(no_conflicts=True)
1177
err = self.assertRaises(errors.FileExists, tt_helper)
1178
self.assertContainsRe(str(err),
1179
"^File exists: .+/foo")
1181
def test_two_directories_clash(self):
1183
wt = self.make_branch_and_tree('.')
1184
tt = TreeTransform(wt) # TreeTransform obtains write lock
1186
foo_1 = tt.new_directory('foo', tt.root)
1187
tt.new_directory('bar', foo_1)
1188
foo_2 = tt.new_directory('foo', tt.root)
1189
tt.new_directory('baz', foo_2)
1190
# Lie to tt that we've already resolved all conflicts.
1191
tt.apply(no_conflicts=True)
1195
err = self.assertRaises(errors.FileExists, tt_helper)
1196
self.assertContainsRe(str(err),
1197
"^File exists: .+/foo")
1199
def test_two_directories_clash_finalize(self):
1201
wt = self.make_branch_and_tree('.')
1202
tt = TreeTransform(wt) # TreeTransform obtains write lock
1204
foo_1 = tt.new_directory('foo', tt.root)
1205
tt.new_directory('bar', foo_1)
1206
foo_2 = tt.new_directory('foo', tt.root)
1207
tt.new_directory('baz', foo_2)
1208
# Lie to tt that we've already resolved all conflicts.
1209
tt.apply(no_conflicts=True)
1213
err = self.assertRaises(errors.FileExists, tt_helper)
1214
self.assertContainsRe(str(err),
1215
"^File exists: .+/foo")
1218
class TransformGroup(object):
1220
def __init__(self, dirname, root_id):
1223
self.wt = BzrDir.create_standalone_workingtree(dirname)
1224
self.wt.set_root_id(root_id)
1225
self.b = self.wt.branch
1226
self.tt = TreeTransform(self.wt)
1227
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1230
def conflict_text(tree, merge):
1231
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1232
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1235
class TestTransformMerge(TestCaseInTempDir):
1237
def test_text_merge(self):
1238
root_id = generate_ids.gen_root_id()
1239
base = TransformGroup("base", root_id)
1240
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1241
base.tt.new_file('b', base.root, 'b1', 'b')
1242
base.tt.new_file('c', base.root, 'c', 'c')
1243
base.tt.new_file('d', base.root, 'd', 'd')
1244
base.tt.new_file('e', base.root, 'e', 'e')
1245
base.tt.new_file('f', base.root, 'f', 'f')
1246
base.tt.new_directory('g', base.root, 'g')
1247
base.tt.new_directory('h', base.root, 'h')
1249
other = TransformGroup("other", root_id)
1250
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1251
other.tt.new_file('b', other.root, 'b2', 'b')
1252
other.tt.new_file('c', other.root, 'c2', 'c')
1253
other.tt.new_file('d', other.root, 'd', 'd')
1254
other.tt.new_file('e', other.root, 'e2', 'e')
1255
other.tt.new_file('f', other.root, 'f', 'f')
1256
other.tt.new_file('g', other.root, 'g', 'g')
1257
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1258
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1260
this = TransformGroup("this", root_id)
1261
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1262
this.tt.new_file('b', this.root, 'b', 'b')
1263
this.tt.new_file('c', this.root, 'c', 'c')
1264
this.tt.new_file('d', this.root, 'd2', 'd')
1265
this.tt.new_file('e', this.root, 'e2', 'e')
1266
this.tt.new_file('f', this.root, 'f', 'f')
1267
this.tt.new_file('g', this.root, 'g', 'g')
1268
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1269
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1271
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1274
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1275
# three-way text conflict
1276
self.assertEqual(this.wt.get_file('b').read(),
1277
conflict_text('b', 'b2'))
1279
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1281
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1282
# Ambigious clean merge
1283
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1285
self.assertEqual(this.wt.get_file('f').read(), 'f')
1286
# Correct correct results when THIS == OTHER
1287
self.assertEqual(this.wt.get_file('g').read(), 'g')
1288
# Text conflict when THIS & OTHER are text and BASE is dir
1289
self.assertEqual(this.wt.get_file('h').read(),
1290
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1291
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1293
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1295
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1296
self.assertEqual(this.wt.get_file('i').read(),
1297
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1298
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1300
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1302
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1303
modified = ['a', 'b', 'c', 'h', 'i']
1304
merge_modified = this.wt.merge_modified()
1305
self.assertSubset(merge_modified, modified)
1306
self.assertEqual(len(merge_modified), len(modified))
1307
file(this.wt.id2abspath('a'), 'wb').write('booga')
1309
merge_modified = this.wt.merge_modified()
1310
self.assertSubset(merge_modified, modified)
1311
self.assertEqual(len(merge_modified), len(modified))
1315
def test_file_merge(self):
1316
self.requireFeature(SymlinkFeature)
1317
root_id = generate_ids.gen_root_id()
1318
base = TransformGroup("BASE", root_id)
1319
this = TransformGroup("THIS", root_id)
1320
other = TransformGroup("OTHER", root_id)
1321
for tg in this, base, other:
1322
tg.tt.new_directory('a', tg.root, 'a')
1323
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1324
tg.tt.new_file('c', tg.root, 'c', 'c')
1325
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1326
targets = ((base, 'base-e', 'base-f', None, None),
1327
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1328
(other, 'other-e', None, 'other-g', 'other-h'))
1329
for tg, e_target, f_target, g_target, h_target in targets:
1330
for link, target in (('e', e_target), ('f', f_target),
1331
('g', g_target), ('h', h_target)):
1332
if target is not None:
1333
tg.tt.new_symlink(link, tg.root, target, link)
1335
for tg in this, base, other:
1337
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1338
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1339
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1340
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1341
for suffix in ('THIS', 'BASE', 'OTHER'):
1342
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1343
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1344
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1345
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1346
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1347
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1348
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1349
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1350
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1351
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1352
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1353
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1354
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1355
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1357
def test_filename_merge(self):
1358
root_id = generate_ids.gen_root_id()
1359
base = TransformGroup("BASE", root_id)
1360
this = TransformGroup("THIS", root_id)
1361
other = TransformGroup("OTHER", root_id)
1362
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1363
for t in [base, this, other]]
1364
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1365
for t in [base, this, other]]
1366
base.tt.new_directory('c', base_a, 'c')
1367
this.tt.new_directory('c1', this_a, 'c')
1368
other.tt.new_directory('c', other_b, 'c')
1370
base.tt.new_directory('d', base_a, 'd')
1371
this.tt.new_directory('d1', this_b, 'd')
1372
other.tt.new_directory('d', other_a, 'd')
1374
base.tt.new_directory('e', base_a, 'e')
1375
this.tt.new_directory('e', this_a, 'e')
1376
other.tt.new_directory('e1', other_b, 'e')
1378
base.tt.new_directory('f', base_a, 'f')
1379
this.tt.new_directory('f1', this_b, 'f')
1380
other.tt.new_directory('f1', other_b, 'f')
1382
for tg in [this, base, other]:
1384
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1385
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1386
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1387
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1388
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1390
def test_filename_merge_conflicts(self):
1391
root_id = generate_ids.gen_root_id()
1392
base = TransformGroup("BASE", root_id)
1393
this = TransformGroup("THIS", root_id)
1394
other = TransformGroup("OTHER", root_id)
1395
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1396
for t in [base, this, other]]
1397
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1398
for t in [base, this, other]]
1400
base.tt.new_file('g', base_a, 'g', 'g')
1401
other.tt.new_file('g1', other_b, 'g1', 'g')
1403
base.tt.new_file('h', base_a, 'h', 'h')
1404
this.tt.new_file('h1', this_b, 'h1', 'h')
1406
base.tt.new_file('i', base.root, 'i', 'i')
1407
other.tt.new_directory('i1', this_b, 'i')
1409
for tg in [this, base, other]:
1411
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1413
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1414
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1415
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1416
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1417
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1418
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1419
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1422
class TestBuildTree(tests.TestCaseWithTransport):
1424
def test_build_tree_with_symlinks(self):
1425
self.requireFeature(SymlinkFeature)
1427
a = BzrDir.create_standalone_workingtree('a')
1429
file('a/foo/bar', 'wb').write('contents')
1430
os.symlink('a/foo/bar', 'a/foo/baz')
1431
a.add(['foo', 'foo/bar', 'foo/baz'])
1432
a.commit('initial commit')
1433
b = BzrDir.create_standalone_workingtree('b')
1434
basis = a.basis_tree()
1436
self.addCleanup(basis.unlock)
1437
build_tree(basis, b)
1438
self.assertIs(os.path.isdir('b/foo'), True)
1439
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1440
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1442
def test_build_with_references(self):
1443
tree = self.make_branch_and_tree('source',
1444
format='dirstate-with-subtree')
1445
subtree = self.make_branch_and_tree('source/subtree',
1446
format='dirstate-with-subtree')
1447
tree.add_reference(subtree)
1448
tree.commit('a revision')
1449
tree.branch.create_checkout('target')
1450
self.failUnlessExists('target')
1451
self.failUnlessExists('target/subtree')
1453
def test_file_conflict_handling(self):
1454
"""Ensure that when building trees, conflict handling is done"""
1455
source = self.make_branch_and_tree('source')
1456
target = self.make_branch_and_tree('target')
1457
self.build_tree(['source/file', 'target/file'])
1458
source.add('file', 'new-file')
1459
source.commit('added file')
1460
build_tree(source.basis_tree(), target)
1461
self.assertEqual([DuplicateEntry('Moved existing file to',
1462
'file.moved', 'file', None, 'new-file')],
1464
target2 = self.make_branch_and_tree('target2')
1465
target_file = file('target2/file', 'wb')
1467
source_file = file('source/file', 'rb')
1469
target_file.write(source_file.read())
1474
build_tree(source.basis_tree(), target2)
1475
self.assertEqual([], target2.conflicts())
1477
def test_symlink_conflict_handling(self):
1478
"""Ensure that when building trees, conflict handling is done"""
1479
self.requireFeature(SymlinkFeature)
1480
source = self.make_branch_and_tree('source')
1481
os.symlink('foo', 'source/symlink')
1482
source.add('symlink', 'new-symlink')
1483
source.commit('added file')
1484
target = self.make_branch_and_tree('target')
1485
os.symlink('bar', 'target/symlink')
1486
build_tree(source.basis_tree(), target)
1487
self.assertEqual([DuplicateEntry('Moved existing file to',
1488
'symlink.moved', 'symlink', None, 'new-symlink')],
1490
target = self.make_branch_and_tree('target2')
1491
os.symlink('foo', 'target2/symlink')
1492
build_tree(source.basis_tree(), target)
1493
self.assertEqual([], target.conflicts())
1495
def test_directory_conflict_handling(self):
1496
"""Ensure that when building trees, conflict handling is done"""
1497
source = self.make_branch_and_tree('source')
1498
target = self.make_branch_and_tree('target')
1499
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1500
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1501
source.commit('added file')
1502
build_tree(source.basis_tree(), target)
1503
self.assertEqual([], target.conflicts())
1504
self.failUnlessExists('target/dir1/file')
1506
# Ensure contents are merged
1507
target = self.make_branch_and_tree('target2')
1508
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1509
build_tree(source.basis_tree(), target)
1510
self.assertEqual([], target.conflicts())
1511
self.failUnlessExists('target2/dir1/file2')
1512
self.failUnlessExists('target2/dir1/file')
1514
# Ensure new contents are suppressed for existing branches
1515
target = self.make_branch_and_tree('target3')
1516
self.make_branch('target3/dir1')
1517
self.build_tree(['target3/dir1/file2'])
1518
build_tree(source.basis_tree(), target)
1519
self.failIfExists('target3/dir1/file')
1520
self.failUnlessExists('target3/dir1/file2')
1521
self.failUnlessExists('target3/dir1.diverted/file')
1522
self.assertEqual([DuplicateEntry('Diverted to',
1523
'dir1.diverted', 'dir1', 'new-dir1', None)],
1526
target = self.make_branch_and_tree('target4')
1527
self.build_tree(['target4/dir1/'])
1528
self.make_branch('target4/dir1/file')
1529
build_tree(source.basis_tree(), target)
1530
self.failUnlessExists('target4/dir1/file')
1531
self.assertEqual('directory', file_kind('target4/dir1/file'))
1532
self.failUnlessExists('target4/dir1/file.diverted')
1533
self.assertEqual([DuplicateEntry('Diverted to',
1534
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1537
def test_mixed_conflict_handling(self):
1538
"""Ensure that when building trees, conflict handling is done"""
1539
source = self.make_branch_and_tree('source')
1540
target = self.make_branch_and_tree('target')
1541
self.build_tree(['source/name', 'target/name/'])
1542
source.add('name', 'new-name')
1543
source.commit('added file')
1544
build_tree(source.basis_tree(), target)
1545
self.assertEqual([DuplicateEntry('Moved existing file to',
1546
'name.moved', 'name', None, 'new-name')], target.conflicts())
1548
def test_raises_in_populated(self):
1549
source = self.make_branch_and_tree('source')
1550
self.build_tree(['source/name'])
1552
source.commit('added name')
1553
target = self.make_branch_and_tree('target')
1554
self.build_tree(['target/name'])
1556
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1557
build_tree, source.basis_tree(), target)
1559
def test_build_tree_rename_count(self):
1560
source = self.make_branch_and_tree('source')
1561
self.build_tree(['source/file1', 'source/dir1/'])
1562
source.add(['file1', 'dir1'])
1563
source.commit('add1')
1564
target1 = self.make_branch_and_tree('target1')
1565
transform_result = build_tree(source.basis_tree(), target1)
1566
self.assertEqual(2, transform_result.rename_count)
1568
self.build_tree(['source/dir1/file2'])
1569
source.add(['dir1/file2'])
1570
source.commit('add3')
1571
target2 = self.make_branch_and_tree('target2')
1572
transform_result = build_tree(source.basis_tree(), target2)
1573
# children of non-root directories should not be renamed
1574
self.assertEqual(2, transform_result.rename_count)
1576
def test_build_tree_accelerator_tree(self):
1577
source = self.make_branch_and_tree('source')
1578
self.build_tree_contents([('source/file1', 'A')])
1579
self.build_tree_contents([('source/file2', 'B')])
1580
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1581
source.commit('commit files')
1582
self.build_tree_contents([('source/file2', 'C')])
1584
real_source_get_file = source.get_file
1585
def get_file(file_id, path=None):
1586
calls.append(file_id)
1587
return real_source_get_file(file_id, path)
1588
source.get_file = get_file
1590
self.addCleanup(source.unlock)
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)
1596
self.assertEqual(['file1-id'], calls)
1598
self.addCleanup(target.unlock)
1599
self.assertEqual([], list(target._iter_changes(revision_tree)))
1601
def test_build_tree_accelerator_tree_missing_file(self):
1602
source = self.make_branch_and_tree('source')
1603
self.build_tree_contents([('source/file1', 'A')])
1604
self.build_tree_contents([('source/file2', 'B')])
1605
source.add(['file1', 'file2'])
1606
source.commit('commit files')
1607
os.unlink('source/file1')
1608
source.remove(['file2'])
1609
target = self.make_branch_and_tree('target')
1610
revision_tree = source.basis_tree()
1611
revision_tree.lock_read()
1612
self.addCleanup(revision_tree.unlock)
1613
build_tree(revision_tree, target, source)
1615
self.addCleanup(target.unlock)
1616
self.assertEqual([], list(target._iter_changes(revision_tree)))
1618
def test_build_tree_accelerator_wrong_kind(self):
1619
self.requireFeature(SymlinkFeature)
1620
source = self.make_branch_and_tree('source')
1621
self.build_tree_contents([('source/file1', '')])
1622
self.build_tree_contents([('source/file2', '')])
1623
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1624
source.commit('commit files')
1625
os.unlink('source/file2')
1626
self.build_tree_contents([('source/file2/', 'C')])
1627
os.unlink('source/file1')
1628
os.symlink('file2', 'source/file1')
1630
real_source_get_file = source.get_file
1631
def get_file(file_id, path=None):
1632
calls.append(file_id)
1633
return real_source_get_file(file_id, path)
1634
source.get_file = get_file
1636
self.addCleanup(source.unlock)
1637
target = self.make_branch_and_tree('target')
1638
revision_tree = source.basis_tree()
1639
revision_tree.lock_read()
1640
self.addCleanup(revision_tree.unlock)
1641
build_tree(revision_tree, target, source)
1642
self.assertEqual([], calls)
1644
self.addCleanup(target.unlock)
1645
self.assertEqual([], list(target._iter_changes(revision_tree)))
1647
def test_build_tree_accelerator_tree_moved(self):
1648
source = self.make_branch_and_tree('source')
1649
self.build_tree_contents([('source/file1', 'A')])
1650
source.add(['file1'], ['file1-id'])
1651
source.commit('commit files')
1652
source.rename_one('file1', 'file2')
1654
self.addCleanup(source.unlock)
1655
target = self.make_branch_and_tree('target')
1656
revision_tree = source.basis_tree()
1657
revision_tree.lock_read()
1658
self.addCleanup(revision_tree.unlock)
1659
build_tree(revision_tree, target, source)
1661
self.addCleanup(target.unlock)
1662
self.assertEqual([], list(target._iter_changes(revision_tree)))
1665
class MockTransform(object):
1667
def has_named_child(self, by_parent, parent_id, name):
1668
for child_id in by_parent[parent_id]:
1672
elif name == "name.~%s~" % child_id:
1677
class MockEntry(object):
1679
object.__init__(self)
1683
class TestGetBackupName(TestCase):
1684
def test_get_backup_name(self):
1685
tt = MockTransform()
1686
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1687
self.assertEqual(name, 'name.~1~')
1688
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1689
self.assertEqual(name, 'name.~2~')
1690
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1691
self.assertEqual(name, 'name.~1~')
1692
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1693
self.assertEqual(name, 'name.~1~')
1694
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1695
self.assertEqual(name, 'name.~4~')
1698
class TestFileMover(tests.TestCaseWithTransport):
1700
def test_file_mover(self):
1701
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1702
mover = _FileMover()
1703
mover.rename('a', 'q')
1704
self.failUnlessExists('q')
1705
self.failIfExists('a')
1706
self.failUnlessExists('q/b')
1707
self.failUnlessExists('c')
1708
self.failUnlessExists('c/d')
1710
def test_pre_delete_rollback(self):
1711
self.build_tree(['a/'])
1712
mover = _FileMover()
1713
mover.pre_delete('a', 'q')
1714
self.failUnlessExists('q')
1715
self.failIfExists('a')
1717
self.failIfExists('q')
1718
self.failUnlessExists('a')
1720
def test_apply_deletions(self):
1721
self.build_tree(['a/', 'b/'])
1722
mover = _FileMover()
1723
mover.pre_delete('a', 'q')
1724
mover.pre_delete('b', 'r')
1725
self.failUnlessExists('q')
1726
self.failUnlessExists('r')
1727
self.failIfExists('a')
1728
self.failIfExists('b')
1729
mover.apply_deletions()
1730
self.failIfExists('q')
1731
self.failIfExists('r')
1732
self.failIfExists('a')
1733
self.failIfExists('b')
1735
def test_file_mover_rollback(self):
1736
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1737
mover = _FileMover()
1738
mover.rename('c/d', 'c/f')
1739
mover.rename('c/e', 'c/d')
1741
mover.rename('a', 'c')
1742
except errors.FileExists, e:
1744
self.failUnlessExists('a')
1745
self.failUnlessExists('c/d')
1748
class Bogus(Exception):
1752
class TestTransformRollback(tests.TestCaseWithTransport):
1754
class ExceptionFileMover(_FileMover):
1756
def __init__(self, bad_source=None, bad_target=None):
1757
_FileMover.__init__(self)
1758
self.bad_source = bad_source
1759
self.bad_target = bad_target
1761
def rename(self, source, target):
1762
if (self.bad_source is not None and
1763
source.endswith(self.bad_source)):
1765
elif (self.bad_target is not None and
1766
target.endswith(self.bad_target)):
1769
_FileMover.rename(self, source, target)
1771
def test_rollback_rename(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_source='a'))
1781
self.failUnlessExists('a')
1782
self.failUnlessExists('a/b')
1784
self.failUnlessExists('c')
1785
self.failUnlessExists('c/d')
1787
def test_rollback_rename_into_place(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.adjust_path('c', tt.root, a_id)
1794
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1795
self.assertRaises(Bogus, tt.apply,
1796
_mover=self.ExceptionFileMover(bad_target='c/d'))
1797
self.failUnlessExists('a')
1798
self.failUnlessExists('a/b')
1800
self.failUnlessExists('c')
1801
self.failUnlessExists('c/d')
1803
def test_rollback_deletion(self):
1804
tree = self.make_branch_and_tree('.')
1805
self.build_tree(['a/', 'a/b'])
1806
tt = TreeTransform(tree)
1807
self.addCleanup(tt.finalize)
1808
a_id = tt.trans_id_tree_path('a')
1809
tt.delete_contents(a_id)
1810
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1811
self.assertRaises(Bogus, tt.apply,
1812
_mover=self.ExceptionFileMover(bad_target='d'))
1813
self.failUnlessExists('a')
1814
self.failUnlessExists('a/b')
1816
def test_resolve_no_parent(self):
1817
wt = self.make_branch_and_tree('.')
1818
tt = TreeTransform(wt)
1819
self.addCleanup(tt.finalize)
1820
parent = tt.trans_id_file_id('parent-id')
1821
tt.new_file('file', parent, 'Contents')
1822
resolve_conflicts(tt)
1825
class TestTransformPreview(tests.TestCaseWithTransport):
1827
def create_tree(self):
1828
tree = self.make_branch_and_tree('.')
1829
self.build_tree_contents([('a', 'content 1')])
1830
tree.add('a', 'a-id')
1831
tree.commit('rev1', rev_id='rev1')
1832
return tree.branch.repository.revision_tree('rev1')
1834
def get_empty_preview(self):
1835
repository = self.make_repository('repo')
1836
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
1837
preview = TransformPreview(tree)
1838
self.addCleanup(preview.finalize)
1841
def test_transform_preview(self):
1842
revision_tree = self.create_tree()
1843
preview = TransformPreview(revision_tree)
1844
self.addCleanup(preview.finalize)
1846
def test_transform_preview_tree(self):
1847
revision_tree = self.create_tree()
1848
preview = TransformPreview(revision_tree)
1849
self.addCleanup(preview.finalize)
1850
preview.get_preview_tree()
1852
def test_transform_new_file(self):
1853
revision_tree = self.create_tree()
1854
preview = TransformPreview(revision_tree)
1855
self.addCleanup(preview.finalize)
1856
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1857
preview_tree = preview.get_preview_tree()
1858
self.assertEqual(preview_tree.kind('file2-id'), 'file')
1860
preview_tree.get_file('file2-id').read(), 'content B\n')
1862
def test_diff_preview_tree(self):
1863
revision_tree = self.create_tree()
1864
preview = TransformPreview(revision_tree)
1865
self.addCleanup(preview.finalize)
1866
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1867
preview_tree = preview.get_preview_tree()
1869
show_diff_trees(revision_tree, preview_tree, out)
1870
lines = out.getvalue().splitlines()
1871
self.assertEqual(lines[0], "=== added file 'file2'")
1872
# 3 lines of diff administrivia
1873
self.assertEqual(lines[4], "+content B")
1875
def test_transform_conflicts(self):
1876
revision_tree = self.create_tree()
1877
preview = TransformPreview(revision_tree)
1878
self.addCleanup(preview.finalize)
1879
preview.new_file('a', preview.root, 'content 2')
1880
resolve_conflicts(preview)
1881
trans_id = preview.trans_id_file_id('a-id')
1882
self.assertEqual('a.moved', preview.final_name(trans_id))
1884
def get_tree_and_preview_tree(self):
1885
revision_tree = self.create_tree()
1886
preview = TransformPreview(revision_tree)
1887
self.addCleanup(preview.finalize)
1888
a_trans_id = preview.trans_id_file_id('a-id')
1889
preview.delete_contents(a_trans_id)
1890
preview.create_file('b content', a_trans_id)
1891
preview_tree = preview.get_preview_tree()
1892
return revision_tree, preview_tree
1894
def test_iter_changes(self):
1895
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1896
root = revision_tree.inventory.root.file_id
1897
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
1898
(root, root), ('a', 'a'), ('file', 'file'),
1900
list(preview_tree._iter_changes(revision_tree)))
1902
def test_wrong_tree_value_error(self):
1903
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1904
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1906
self.assertEqual('from_tree must be transform source tree.', str(e))
1908
def test_include_unchanged_value_error(self):
1909
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1910
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1911
revision_tree, include_unchanged=True)
1912
self.assertEqual('include_unchanged is not supported', str(e))
1914
def test_specific_files(self):
1915
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1916
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1917
revision_tree, specific_files=['pete'])
1918
self.assertEqual('specific_files is not supported', str(e))
1920
def test_want_unversioned_value_error(self):
1921
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1922
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1923
revision_tree, want_unversioned=True)
1924
self.assertEqual('want_unversioned is not supported', str(e))
1926
def test_ignore_extra_trees_no_specific_files(self):
1927
# extra_trees is harmless without specific_files, so we'll silently
1928
# accept it, even though we won't use it.
1929
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1930
preview_tree._iter_changes(revision_tree, extra_trees=[preview_tree])
1932
def test_ignore_require_versioned_no_specific_files(self):
1933
# require_versioned is meaningless without specific_files.
1934
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1935
preview_tree._iter_changes(revision_tree, require_versioned=False)
1937
def test_ignore_pb(self):
1938
# pb could be supported, but TT.iter_changes doesn't support it.
1939
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1940
preview_tree._iter_changes(revision_tree, pb=progress.DummyProgress())
1942
def test_kind(self):
1943
revision_tree = self.create_tree()
1944
preview = TransformPreview(revision_tree)
1945
self.addCleanup(preview.finalize)
1946
preview.new_file('file', preview.root, 'contents', 'file-id')
1947
preview.new_directory('directory', preview.root, 'dir-id')
1948
preview_tree = preview.get_preview_tree()
1949
self.assertEqual('file', preview_tree.kind('file-id'))
1950
self.assertEqual('directory', preview_tree.kind('dir-id'))
1952
def test_get_file_mtime(self):
1953
preview = self.get_empty_preview()
1954
file_trans_id = preview.new_file('file', preview.root, 'contents',
1956
limbo_path = preview._limbo_name(file_trans_id)
1957
preview_tree = preview.get_preview_tree()
1958
self.assertEqual(os.stat(limbo_path).st_mtime,
1959
preview_tree.get_file_mtime('file-id'))
1961
def test_get_file(self):
1962
preview = self.get_empty_preview()
1963
preview.new_file('file', preview.root, 'contents', 'file-id')
1964
preview_tree = preview.get_preview_tree()
1965
tree_file = preview_tree.get_file('file-id')
1967
self.assertEqual('contents', tree_file.read())