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
26
revision as _mod_revision,
31
from bzrlib.bzrdir import BzrDir
32
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
33
UnversionedParent, ParentLoop, DeletingParent,
35
from bzrlib.diff import show_diff_trees
36
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
37
ReusingTransform, CantMoveRoot,
38
PathsNotVersionedError, ExistingLimbo,
39
ExistingPendingDeletion, ImmortalLimbo,
40
ImmortalPendingDeletion, LockError)
41
from bzrlib.osutils import file_kind, pathjoin
42
from bzrlib.merge import Merge3Merger
43
from bzrlib.tests import (
44
CaseInsensitiveFilesystemFeature,
50
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
51
resolve_conflicts, cook_conflicts,
52
find_interesting, build_tree, get_backup_name,
53
change_entry, _FileMover, resolve_checkout,
56
class TestTreeTransform(tests.TestCaseWithTransport):
59
super(TestTreeTransform, self).setUp()
60
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
63
def get_transform(self):
64
transform = TreeTransform(self.wt)
65
#self.addCleanup(transform.finalize)
66
return transform, transform.root
68
def test_existing_limbo(self):
69
transform, root = self.get_transform()
70
limbo_name = transform._limbodir
71
deletion_path = transform._deletiondir
72
os.mkdir(pathjoin(limbo_name, 'hehe'))
73
self.assertRaises(ImmortalLimbo, transform.apply)
74
self.assertRaises(LockError, self.wt.unlock)
75
self.assertRaises(ExistingLimbo, self.get_transform)
76
self.assertRaises(LockError, self.wt.unlock)
77
os.rmdir(pathjoin(limbo_name, 'hehe'))
79
os.rmdir(deletion_path)
80
transform, root = self.get_transform()
83
def test_existing_pending_deletion(self):
84
transform, root = self.get_transform()
85
deletion_path = self._limbodir = urlutils.local_path_from_url(
86
transform._tree._control_files.controlfilename('pending-deletion'))
87
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
88
self.assertRaises(ImmortalPendingDeletion, transform.apply)
89
self.assertRaises(LockError, self.wt.unlock)
90
self.assertRaises(ExistingPendingDeletion, self.get_transform)
93
transform, root = self.get_transform()
94
self.wt.lock_tree_write()
95
self.addCleanup(self.wt.unlock)
96
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
97
imaginary_id = transform.trans_id_tree_path('imaginary')
98
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
99
self.assertEqual(imaginary_id, imaginary_id2)
100
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
101
self.assertEqual(transform.final_kind(root), 'directory')
102
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
103
trans_id = transform.create_path('name', root)
104
self.assertIs(transform.final_file_id(trans_id), None)
105
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
106
transform.create_file('contents', trans_id)
107
transform.set_executability(True, trans_id)
108
transform.version_file('my_pretties', trans_id)
109
self.assertRaises(DuplicateKey, transform.version_file,
110
'my_pretties', trans_id)
111
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
112
self.assertEqual(transform.final_parent(trans_id), root)
113
self.assertIs(transform.final_parent(root), ROOT_PARENT)
114
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
115
oz_id = transform.create_path('oz', root)
116
transform.create_directory(oz_id)
117
transform.version_file('ozzie', oz_id)
118
trans_id2 = transform.create_path('name2', root)
119
transform.create_file('contents', trans_id2)
120
transform.set_executability(False, trans_id2)
121
transform.version_file('my_pretties2', trans_id2)
122
modified_paths = transform.apply().modified_paths
123
self.assertEqual('contents', self.wt.get_file_byname('name').read())
124
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
125
self.assertIs(self.wt.is_executable('my_pretties'), True)
126
self.assertIs(self.wt.is_executable('my_pretties2'), False)
127
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
128
self.assertEqual(len(modified_paths), 3)
129
tree_mod_paths = [self.wt.id2abspath(f) for f in
130
('ozzie', 'my_pretties', 'my_pretties2')]
131
self.assertSubset(tree_mod_paths, modified_paths)
132
# is it safe to finalize repeatedly?
136
def test_convenience(self):
137
transform, root = self.get_transform()
138
self.wt.lock_tree_write()
139
self.addCleanup(self.wt.unlock)
140
trans_id = transform.new_file('name', root, 'contents',
142
oz = transform.new_directory('oz', root, 'oz-id')
143
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
144
toto = transform.new_file('toto', dorothy, 'toto-contents',
147
self.assertEqual(len(transform.find_conflicts()), 0)
149
self.assertRaises(ReusingTransform, transform.find_conflicts)
150
self.assertEqual('contents', file(self.wt.abspath('name')).read())
151
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
152
self.assertIs(self.wt.is_executable('my_pretties'), True)
153
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
154
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
155
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
157
self.assertEqual('toto-contents',
158
self.wt.get_file_byname('oz/dorothy/toto').read())
159
self.assertIs(self.wt.is_executable('toto-id'), False)
161
def test_tree_reference(self):
162
transform, root = self.get_transform()
163
tree = transform._tree
164
trans_id = transform.new_directory('reference', root, 'subtree-id')
165
transform.set_tree_reference('subtree-revision', trans_id)
168
self.addCleanup(tree.unlock)
169
self.assertEqual('subtree-revision',
170
tree.inventory['subtree-id'].reference_revision)
172
def test_conflicts(self):
173
transform, root = self.get_transform()
174
trans_id = transform.new_file('name', root, 'contents',
176
self.assertEqual(len(transform.find_conflicts()), 0)
177
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
178
self.assertEqual(transform.find_conflicts(),
179
[('duplicate', trans_id, trans_id2, 'name')])
180
self.assertRaises(MalformedTransform, transform.apply)
181
transform.adjust_path('name', trans_id, trans_id2)
182
self.assertEqual(transform.find_conflicts(),
183
[('non-directory parent', trans_id)])
184
tinman_id = transform.trans_id_tree_path('tinman')
185
transform.adjust_path('name', tinman_id, trans_id2)
186
self.assertEqual(transform.find_conflicts(),
187
[('unversioned parent', tinman_id),
188
('missing parent', tinman_id)])
189
lion_id = transform.create_path('lion', root)
190
self.assertEqual(transform.find_conflicts(),
191
[('unversioned parent', tinman_id),
192
('missing parent', tinman_id)])
193
transform.adjust_path('name', lion_id, trans_id2)
194
self.assertEqual(transform.find_conflicts(),
195
[('unversioned parent', lion_id),
196
('missing parent', lion_id)])
197
transform.version_file("Courage", lion_id)
198
self.assertEqual(transform.find_conflicts(),
199
[('missing parent', lion_id),
200
('versioning no contents', lion_id)])
201
transform.adjust_path('name2', root, trans_id2)
202
self.assertEqual(transform.find_conflicts(),
203
[('versioning no contents', lion_id)])
204
transform.create_file('Contents, okay?', lion_id)
205
transform.adjust_path('name2', trans_id2, trans_id2)
206
self.assertEqual(transform.find_conflicts(),
207
[('parent loop', trans_id2),
208
('non-directory parent', trans_id2)])
209
transform.adjust_path('name2', root, trans_id2)
210
oz_id = transform.new_directory('oz', root)
211
transform.set_executability(True, oz_id)
212
self.assertEqual(transform.find_conflicts(),
213
[('unversioned executability', oz_id)])
214
transform.version_file('oz-id', oz_id)
215
self.assertEqual(transform.find_conflicts(),
216
[('non-file executability', oz_id)])
217
transform.set_executability(None, oz_id)
218
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
220
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
221
self.assertEqual('contents', file(self.wt.abspath('name')).read())
222
transform2, root = self.get_transform()
223
oz_id = transform2.trans_id_tree_file_id('oz-id')
224
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
225
result = transform2.find_conflicts()
226
fp = FinalPaths(transform2)
227
self.assert_('oz/tip' in transform2._tree_path_ids)
228
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
229
self.assertEqual(len(result), 2)
230
self.assertEqual((result[0][0], result[0][1]),
231
('duplicate', newtip))
232
self.assertEqual((result[1][0], result[1][2]),
233
('duplicate id', newtip))
234
transform2.finalize()
235
transform3 = TreeTransform(self.wt)
236
self.addCleanup(transform3.finalize)
237
oz_id = transform3.trans_id_tree_file_id('oz-id')
238
transform3.delete_contents(oz_id)
239
self.assertEqual(transform3.find_conflicts(),
240
[('missing parent', oz_id)])
241
root_id = transform3.root
242
tip_id = transform3.trans_id_tree_file_id('tip-id')
243
transform3.adjust_path('tip', root_id, tip_id)
246
def test_conflict_on_case_insensitive(self):
247
tree = self.make_branch_and_tree('tree')
248
# Don't try this at home, kids!
249
# Force the tree to report that it is case sensitive, for conflict
251
tree.case_sensitive = True
252
transform = TreeTransform(tree)
253
self.addCleanup(transform.finalize)
254
transform.new_file('file', transform.root, 'content')
255
transform.new_file('FiLe', transform.root, 'content')
256
result = transform.find_conflicts()
257
self.assertEqual([], result)
259
# Force the tree to report that it is case insensitive, for conflict
261
tree.case_sensitive = False
262
transform = TreeTransform(tree)
263
self.addCleanup(transform.finalize)
264
transform.new_file('file', transform.root, 'content')
265
transform.new_file('FiLe', transform.root, 'content')
266
result = transform.find_conflicts()
267
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
269
def test_conflict_on_case_insensitive_existing(self):
270
tree = self.make_branch_and_tree('tree')
271
self.build_tree(['tree/FiLe'])
272
# Don't try this at home, kids!
273
# Force the tree to report that it is case sensitive, for conflict
275
tree.case_sensitive = True
276
transform = TreeTransform(tree)
277
self.addCleanup(transform.finalize)
278
transform.new_file('file', transform.root, 'content')
279
result = transform.find_conflicts()
280
self.assertEqual([], result)
282
# Force the tree to report that it is case insensitive, for conflict
284
tree.case_sensitive = False
285
transform = TreeTransform(tree)
286
self.addCleanup(transform.finalize)
287
transform.new_file('file', transform.root, 'content')
288
result = transform.find_conflicts()
289
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
291
def test_resolve_case_insensitive_conflict(self):
292
tree = self.make_branch_and_tree('tree')
293
# Don't try this at home, kids!
294
# Force the tree to report that it is case insensitive, for conflict
296
tree.case_sensitive = False
297
transform = TreeTransform(tree)
298
self.addCleanup(transform.finalize)
299
transform.new_file('file', transform.root, 'content')
300
transform.new_file('FiLe', transform.root, 'content')
301
resolve_conflicts(transform)
303
self.failUnlessExists('tree/file')
304
self.failUnlessExists('tree/FiLe.moved')
306
def test_resolve_checkout_case_conflict(self):
307
tree = self.make_branch_and_tree('tree')
308
# Don't try this at home, kids!
309
# Force the tree to report that it is case insensitive, for conflict
311
tree.case_sensitive = False
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
resolve_conflicts(transform,
317
pass_func=lambda t, c: resolve_checkout(t, c, []))
319
self.failUnlessExists('tree/file')
320
self.failUnlessExists('tree/FiLe.moved')
322
def test_apply_case_conflict(self):
323
"""Ensure that a transform with case conflicts can always be applied"""
324
tree = self.make_branch_and_tree('tree')
325
transform = TreeTransform(tree)
326
self.addCleanup(transform.finalize)
327
transform.new_file('file', transform.root, 'content')
328
transform.new_file('FiLe', transform.root, 'content')
329
dir = transform.new_directory('dir', transform.root)
330
transform.new_file('dirfile', dir, 'content')
331
transform.new_file('dirFiLe', dir, 'content')
332
resolve_conflicts(transform)
334
self.failUnlessExists('tree/file')
335
if not os.path.exists('tree/FiLe.moved'):
336
self.failUnlessExists('tree/FiLe')
337
self.failUnlessExists('tree/dir/dirfile')
338
if not os.path.exists('tree/dir/dirFiLe.moved'):
339
self.failUnlessExists('tree/dir/dirFiLe')
341
def test_case_insensitive_limbo(self):
342
tree = self.make_branch_and_tree('tree')
343
# Don't try this at home, kids!
344
# Force the tree to report that it is case insensitive
345
tree.case_sensitive = False
346
transform = TreeTransform(tree)
347
self.addCleanup(transform.finalize)
348
dir = transform.new_directory('dir', transform.root)
349
first = transform.new_file('file', dir, 'content')
350
second = transform.new_file('FiLe', dir, 'content')
351
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
352
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
354
def test_add_del(self):
355
start, root = self.get_transform()
356
start.new_directory('a', root, 'a')
358
transform, root = self.get_transform()
359
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
360
transform.new_directory('a', root, 'a')
363
def test_unversioning(self):
364
create_tree, root = self.get_transform()
365
parent_id = create_tree.new_directory('parent', root, 'parent-id')
366
create_tree.new_file('child', parent_id, 'child', 'child-id')
368
unversion = TreeTransform(self.wt)
369
self.addCleanup(unversion.finalize)
370
parent = unversion.trans_id_tree_path('parent')
371
unversion.unversion_file(parent)
372
self.assertEqual(unversion.find_conflicts(),
373
[('unversioned parent', parent_id)])
374
file_id = unversion.trans_id_tree_file_id('child-id')
375
unversion.unversion_file(file_id)
378
def test_name_invariants(self):
379
create_tree, root = self.get_transform()
381
root = create_tree.root
382
create_tree.new_file('name1', root, 'hello1', 'name1')
383
create_tree.new_file('name2', root, 'hello2', 'name2')
384
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
385
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
386
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
387
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
390
mangle_tree,root = self.get_transform()
391
root = mangle_tree.root
393
name1 = mangle_tree.trans_id_tree_file_id('name1')
394
name2 = mangle_tree.trans_id_tree_file_id('name2')
395
mangle_tree.adjust_path('name2', root, name1)
396
mangle_tree.adjust_path('name1', root, name2)
398
#tests for deleting parent directories
399
ddir = mangle_tree.trans_id_tree_file_id('ddir')
400
mangle_tree.delete_contents(ddir)
401
dfile = mangle_tree.trans_id_tree_file_id('dfile')
402
mangle_tree.delete_versioned(dfile)
403
mangle_tree.unversion_file(dfile)
404
mfile = mangle_tree.trans_id_tree_file_id('mfile')
405
mangle_tree.adjust_path('mfile', root, mfile)
407
#tests for adding parent directories
408
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
409
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
410
mangle_tree.adjust_path('mfile2', newdir, mfile2)
411
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
412
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
413
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
414
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
416
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
417
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
418
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
419
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
420
self.assertEqual(file(mfile2_path).read(), 'later2')
421
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
422
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
423
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
424
self.assertEqual(file(newfile_path).read(), 'hello3')
425
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
426
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
427
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
429
def test_both_rename(self):
430
create_tree,root = self.get_transform()
431
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
432
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
434
mangle_tree,root = self.get_transform()
435
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
436
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
437
mangle_tree.adjust_path('test', root, selftest)
438
mangle_tree.adjust_path('test_too_much', root, selftest)
439
mangle_tree.set_executability(True, blackbox)
442
def test_both_rename2(self):
443
create_tree,root = self.get_transform()
444
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
445
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
446
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
447
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
450
mangle_tree,root = self.get_transform()
451
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
452
tests = mangle_tree.trans_id_tree_file_id('tests-id')
453
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
454
mangle_tree.adjust_path('selftest', bzrlib, tests)
455
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
456
mangle_tree.set_executability(True, test_too_much)
459
def test_both_rename3(self):
460
create_tree,root = self.get_transform()
461
tests = create_tree.new_directory('tests', root, 'tests-id')
462
create_tree.new_file('test_too_much.py', tests, 'hello1',
465
mangle_tree,root = self.get_transform()
466
tests = mangle_tree.trans_id_tree_file_id('tests-id')
467
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
468
mangle_tree.adjust_path('selftest', root, tests)
469
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
470
mangle_tree.set_executability(True, test_too_much)
473
def test_move_dangling_ie(self):
474
create_tree, root = self.get_transform()
476
root = create_tree.root
477
create_tree.new_file('name1', root, 'hello1', 'name1')
479
delete_contents, root = self.get_transform()
480
file = delete_contents.trans_id_tree_file_id('name1')
481
delete_contents.delete_contents(file)
482
delete_contents.apply()
483
move_id, root = self.get_transform()
484
name1 = move_id.trans_id_tree_file_id('name1')
485
newdir = move_id.new_directory('dir', root, 'newdir')
486
move_id.adjust_path('name2', newdir, name1)
489
def test_replace_dangling_ie(self):
490
create_tree, root = self.get_transform()
492
root = create_tree.root
493
create_tree.new_file('name1', root, 'hello1', 'name1')
495
delete_contents = TreeTransform(self.wt)
496
self.addCleanup(delete_contents.finalize)
497
file = delete_contents.trans_id_tree_file_id('name1')
498
delete_contents.delete_contents(file)
499
delete_contents.apply()
500
delete_contents.finalize()
501
replace = TreeTransform(self.wt)
502
self.addCleanup(replace.finalize)
503
name2 = replace.new_file('name2', root, 'hello2', 'name1')
504
conflicts = replace.find_conflicts()
505
name1 = replace.trans_id_tree_file_id('name1')
506
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
507
resolve_conflicts(replace)
510
def test_symlinks(self):
511
self.requireFeature(SymlinkFeature)
512
transform,root = self.get_transform()
513
oz_id = transform.new_directory('oz', root, 'oz-id')
514
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
516
wiz_id = transform.create_path('wizard2', oz_id)
517
transform.create_symlink('behind_curtain', wiz_id)
518
transform.version_file('wiz-id2', wiz_id)
519
transform.set_executability(True, wiz_id)
520
self.assertEqual(transform.find_conflicts(),
521
[('non-file executability', wiz_id)])
522
transform.set_executability(None, wiz_id)
524
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
525
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
526
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
528
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
531
def test_unable_create_symlink(self):
533
wt = self.make_branch_and_tree('.')
534
tt = TreeTransform(wt) # TreeTransform obtains write lock
536
tt.new_symlink('foo', tt.root, 'bar')
540
os_symlink = getattr(os, 'symlink', None)
543
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
545
"Unable to create symlink 'foo' on this platform",
549
os.symlink = os_symlink
551
def get_conflicted(self):
552
create,root = self.get_transform()
553
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
554
oz = create.new_directory('oz', root, 'oz-id')
555
create.new_directory('emeraldcity', oz, 'emerald-id')
557
conflicts,root = self.get_transform()
558
# set up duplicate entry, duplicate id
559
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
561
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
562
oz = conflicts.trans_id_tree_file_id('oz-id')
563
# set up DeletedParent parent conflict
564
conflicts.delete_versioned(oz)
565
emerald = conflicts.trans_id_tree_file_id('emerald-id')
566
# set up MissingParent conflict
567
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
568
conflicts.adjust_path('munchkincity', root, munchkincity)
569
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
571
conflicts.adjust_path('emeraldcity', emerald, emerald)
572
return conflicts, emerald, oz, old_dorothy, new_dorothy
574
def test_conflict_resolution(self):
575
conflicts, emerald, oz, old_dorothy, new_dorothy =\
576
self.get_conflicted()
577
resolve_conflicts(conflicts)
578
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
579
self.assertIs(conflicts.final_file_id(old_dorothy), None)
580
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
581
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
582
self.assertEqual(conflicts.final_parent(emerald), oz)
585
def test_cook_conflicts(self):
586
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
587
raw_conflicts = resolve_conflicts(tt)
588
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
589
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
590
'dorothy', None, 'dorothy-id')
591
self.assertEqual(cooked_conflicts[0], duplicate)
592
duplicate_id = DuplicateID('Unversioned existing file',
593
'dorothy.moved', 'dorothy', None,
595
self.assertEqual(cooked_conflicts[1], duplicate_id)
596
missing_parent = MissingParent('Created directory', 'munchkincity',
598
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
599
self.assertEqual(cooked_conflicts[2], missing_parent)
600
unversioned_parent = UnversionedParent('Versioned directory',
603
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
605
self.assertEqual(cooked_conflicts[3], unversioned_parent)
606
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
607
'oz/emeraldcity', 'emerald-id', 'emerald-id')
608
self.assertEqual(cooked_conflicts[4], deleted_parent)
609
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
610
self.assertEqual(cooked_conflicts[6], parent_loop)
611
self.assertEqual(len(cooked_conflicts), 7)
614
def test_string_conflicts(self):
615
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
616
raw_conflicts = resolve_conflicts(tt)
617
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
619
conflicts_s = [str(c) for c in cooked_conflicts]
620
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
621
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
622
'Moved existing file to '
624
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
625
'Unversioned existing file '
627
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
628
' munchkincity. Created directory.')
629
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
630
' versioned, but has versioned'
631
' children. Versioned directory.')
632
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
633
" is not empty. Not deleting.")
634
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
635
' versioned, but has versioned'
636
' children. Versioned directory.')
637
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
638
' oz/emeraldcity. Cancelled move.')
640
def prepare_wrong_parent_kind(self):
641
tt, root = self.get_transform()
642
tt.new_file('parent', root, 'contents', 'parent-id')
644
tt, root = self.get_transform()
645
parent_id = tt.trans_id_file_id('parent-id')
646
tt.new_file('child,', parent_id, 'contents2', 'file-id')
649
def test_find_conflicts_wrong_parent_kind(self):
650
tt = self.prepare_wrong_parent_kind()
653
def test_resolve_conflicts_wrong_existing_parent_kind(self):
654
tt = self.prepare_wrong_parent_kind()
655
raw_conflicts = resolve_conflicts(tt)
656
self.assertEqual(set([('non-directory parent', 'Created directory',
657
'new-3')]), raw_conflicts)
658
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
659
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
660
'parent-id')], cooked_conflicts)
662
self.assertEqual(None, self.wt.path2id('parent'))
663
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
665
def test_resolve_conflicts_wrong_new_parent_kind(self):
666
tt, root = self.get_transform()
667
parent_id = tt.new_directory('parent', root, 'parent-id')
668
tt.new_file('child,', parent_id, 'contents2', 'file-id')
670
tt, root = self.get_transform()
671
parent_id = tt.trans_id_file_id('parent-id')
672
tt.delete_contents(parent_id)
673
tt.create_file('contents', parent_id)
674
raw_conflicts = resolve_conflicts(tt)
675
self.assertEqual(set([('non-directory parent', 'Created directory',
676
'new-3')]), raw_conflicts)
678
self.assertEqual(None, self.wt.path2id('parent'))
679
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
681
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
682
tt, root = self.get_transform()
683
parent_id = tt.new_directory('parent', root)
684
tt.new_file('child,', parent_id, 'contents2')
686
tt, root = self.get_transform()
687
parent_id = tt.trans_id_tree_path('parent')
688
tt.delete_contents(parent_id)
689
tt.create_file('contents', parent_id)
690
resolve_conflicts(tt)
692
self.assertIs(None, self.wt.path2id('parent'))
693
self.assertIs(None, self.wt.path2id('parent.new'))
695
def test_moving_versioned_directories(self):
696
create, root = self.get_transform()
697
kansas = create.new_directory('kansas', root, 'kansas-id')
698
create.new_directory('house', kansas, 'house-id')
699
create.new_directory('oz', root, 'oz-id')
701
cyclone, root = self.get_transform()
702
oz = cyclone.trans_id_tree_file_id('oz-id')
703
house = cyclone.trans_id_tree_file_id('house-id')
704
cyclone.adjust_path('house', oz, house)
707
def test_moving_root(self):
708
create, root = self.get_transform()
709
fun = create.new_directory('fun', root, 'fun-id')
710
create.new_directory('sun', root, 'sun-id')
711
create.new_directory('moon', root, 'moon')
713
transform, root = self.get_transform()
714
transform.adjust_root_path('oldroot', fun)
715
new_root=transform.trans_id_tree_path('')
716
transform.version_file('new-root', new_root)
719
def test_renames(self):
720
create, root = self.get_transform()
721
old = create.new_directory('old-parent', root, 'old-id')
722
intermediate = create.new_directory('intermediate', old, 'im-id')
723
myfile = create.new_file('myfile', intermediate, 'myfile-text',
726
rename, root = self.get_transform()
727
old = rename.trans_id_file_id('old-id')
728
rename.adjust_path('new', root, old)
729
myfile = rename.trans_id_file_id('myfile-id')
730
rename.set_executability(True, myfile)
733
def test_find_interesting(self):
734
create, root = self.get_transform()
736
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
737
create.new_file('uvfile', root, 'othertext')
739
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
740
find_interesting, wt, wt, ['vfile'])
741
self.assertEqual(result, set(['myfile-id']))
743
def test_set_executability_order(self):
744
"""Ensure that executability behaves the same, no matter what order.
746
- create file and set executability simultaneously
747
- create file and set executability afterward
748
- unsetting the executability of a file whose executability has not been
749
declared should throw an exception (this may happen when a
750
merge attempts to create a file with a duplicate ID)
752
transform, root = self.get_transform()
755
self.addCleanup(wt.unlock)
756
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
758
sac = transform.new_file('set_after_creation', root,
759
'Set after creation', 'sac')
760
transform.set_executability(True, sac)
761
uws = transform.new_file('unset_without_set', root, 'Unset badly',
763
self.assertRaises(KeyError, transform.set_executability, None, uws)
765
self.assertTrue(wt.is_executable('soc'))
766
self.assertTrue(wt.is_executable('sac'))
768
def test_preserve_mode(self):
769
"""File mode is preserved when replacing content"""
770
if sys.platform == 'win32':
771
raise TestSkipped('chmod has no effect on win32')
772
transform, root = self.get_transform()
773
transform.new_file('file1', root, 'contents', 'file1-id', True)
776
self.addCleanup(self.wt.unlock)
777
self.assertTrue(self.wt.is_executable('file1-id'))
778
transform, root = self.get_transform()
779
file1_id = transform.trans_id_tree_file_id('file1-id')
780
transform.delete_contents(file1_id)
781
transform.create_file('contents2', file1_id)
783
self.assertTrue(self.wt.is_executable('file1-id'))
785
def test__set_mode_stats_correctly(self):
786
"""_set_mode stats to determine file mode."""
787
if sys.platform == 'win32':
788
raise TestSkipped('chmod has no effect on win32')
792
def instrumented_stat(path):
793
stat_paths.append(path)
794
return real_stat(path)
796
transform, root = self.get_transform()
798
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
799
file_id='bar-id-1', executable=False)
802
transform, root = self.get_transform()
803
bar1_id = transform.trans_id_tree_path('bar')
804
bar2_id = transform.trans_id_tree_path('bar2')
806
os.stat = instrumented_stat
807
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
812
bar1_abspath = self.wt.abspath('bar')
813
self.assertEqual([bar1_abspath], stat_paths)
815
def test_iter_changes(self):
816
self.wt.set_root_id('eert_toor')
817
transform, root = self.get_transform()
818
transform.new_file('old', root, 'blah', 'id-1', True)
820
transform, root = self.get_transform()
822
self.assertEqual([], list(transform._iter_changes()))
823
old = transform.trans_id_tree_file_id('id-1')
824
transform.unversion_file(old)
825
self.assertEqual([('id-1', ('old', None), False, (True, False),
826
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
827
(True, True))], list(transform._iter_changes()))
828
transform.new_directory('new', root, 'id-1')
829
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
830
('eert_toor', 'eert_toor'), ('old', 'new'),
831
('file', 'directory'),
832
(True, False))], list(transform._iter_changes()))
836
def test_iter_changes_new(self):
837
self.wt.set_root_id('eert_toor')
838
transform, root = self.get_transform()
839
transform.new_file('old', root, 'blah')
841
transform, root = self.get_transform()
843
old = transform.trans_id_tree_path('old')
844
transform.version_file('id-1', old)
845
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
846
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
847
(False, False))], list(transform._iter_changes()))
851
def test_iter_changes_modifications(self):
852
self.wt.set_root_id('eert_toor')
853
transform, root = self.get_transform()
854
transform.new_file('old', root, 'blah', 'id-1')
855
transform.new_file('new', root, 'blah')
856
transform.new_directory('subdir', root, 'subdir-id')
858
transform, root = self.get_transform()
860
old = transform.trans_id_tree_path('old')
861
subdir = transform.trans_id_tree_file_id('subdir-id')
862
new = transform.trans_id_tree_path('new')
863
self.assertEqual([], list(transform._iter_changes()))
866
transform.delete_contents(old)
867
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
868
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
869
(False, False))], list(transform._iter_changes()))
872
transform.create_file('blah', old)
873
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
874
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
875
(False, False))], list(transform._iter_changes()))
876
transform.cancel_deletion(old)
877
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
878
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
879
(False, False))], list(transform._iter_changes()))
880
transform.cancel_creation(old)
882
# move file_id to a different file
883
self.assertEqual([], list(transform._iter_changes()))
884
transform.unversion_file(old)
885
transform.version_file('id-1', new)
886
transform.adjust_path('old', root, new)
887
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
888
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
889
(False, False))], list(transform._iter_changes()))
890
transform.cancel_versioning(new)
891
transform._removed_id = set()
894
self.assertEqual([], list(transform._iter_changes()))
895
transform.set_executability(True, old)
896
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
897
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
898
(False, True))], list(transform._iter_changes()))
899
transform.set_executability(None, old)
902
self.assertEqual([], list(transform._iter_changes()))
903
transform.adjust_path('new', root, old)
904
transform._new_parent = {}
905
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
906
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
907
(False, False))], list(transform._iter_changes()))
908
transform._new_name = {}
911
self.assertEqual([], list(transform._iter_changes()))
912
transform.adjust_path('new', subdir, old)
913
transform._new_name = {}
914
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
915
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
916
('file', 'file'), (False, False))],
917
list(transform._iter_changes()))
918
transform._new_path = {}
923
def test_iter_changes_modified_bleed(self):
924
self.wt.set_root_id('eert_toor')
925
"""Modified flag should not bleed from one change to another"""
926
# unfortunately, we have no guarantee that file1 (which is modified)
927
# will be applied before file2. And if it's applied after file2, it
928
# obviously can't bleed into file2's change output. But for now, it
930
transform, root = self.get_transform()
931
transform.new_file('file1', root, 'blah', 'id-1')
932
transform.new_file('file2', root, 'blah', 'id-2')
934
transform, root = self.get_transform()
936
transform.delete_contents(transform.trans_id_file_id('id-1'))
937
transform.set_executability(True,
938
transform.trans_id_file_id('id-2'))
939
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
940
('eert_toor', 'eert_toor'), ('file1', u'file1'),
941
('file', None), (False, False)),
942
('id-2', (u'file2', u'file2'), False, (True, True),
943
('eert_toor', 'eert_toor'), ('file2', u'file2'),
944
('file', 'file'), (False, True))],
945
list(transform._iter_changes()))
949
def test_iter_changes_move_missing(self):
950
"""Test moving ids with no files around"""
951
self.wt.set_root_id('toor_eert')
952
# Need two steps because versioning a non-existant file is a conflict.
953
transform, root = self.get_transform()
954
transform.new_directory('floater', root, 'floater-id')
956
transform, root = self.get_transform()
957
transform.delete_contents(transform.trans_id_tree_path('floater'))
959
transform, root = self.get_transform()
960
floater = transform.trans_id_tree_path('floater')
962
transform.adjust_path('flitter', root, floater)
963
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
964
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
965
(None, None), (False, False))], list(transform._iter_changes()))
969
def test_iter_changes_pointless(self):
970
"""Ensure that no-ops are not treated as modifications"""
971
self.wt.set_root_id('eert_toor')
972
transform, root = self.get_transform()
973
transform.new_file('old', root, 'blah', 'id-1')
974
transform.new_directory('subdir', root, 'subdir-id')
976
transform, root = self.get_transform()
978
old = transform.trans_id_tree_path('old')
979
subdir = transform.trans_id_tree_file_id('subdir-id')
980
self.assertEqual([], list(transform._iter_changes()))
981
transform.delete_contents(subdir)
982
transform.create_directory(subdir)
983
transform.set_executability(False, old)
984
transform.unversion_file(old)
985
transform.version_file('id-1', old)
986
transform.adjust_path('old', root, old)
987
self.assertEqual([], list(transform._iter_changes()))
991
def test_rename_count(self):
992
transform, root = self.get_transform()
993
transform.new_file('name1', root, 'contents')
994
self.assertEqual(transform.rename_count, 0)
996
self.assertEqual(transform.rename_count, 1)
997
transform2, root = self.get_transform()
998
transform2.adjust_path('name2', root,
999
transform2.trans_id_tree_path('name1'))
1000
self.assertEqual(transform2.rename_count, 0)
1002
self.assertEqual(transform2.rename_count, 2)
1004
def test_change_parent(self):
1005
"""Ensure that after we change a parent, the results are still right.
1007
Renames and parent changes on pending transforms can happen as part
1008
of conflict resolution, and are explicitly permitted by the
1011
This test ensures they work correctly with the rename-avoidance
1014
transform, root = self.get_transform()
1015
parent1 = transform.new_directory('parent1', root)
1016
child1 = transform.new_file('child1', parent1, 'contents')
1017
parent2 = transform.new_directory('parent2', root)
1018
transform.adjust_path('child1', parent2, child1)
1020
self.failIfExists(self.wt.abspath('parent1/child1'))
1021
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1022
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1023
# no rename for child1 (counting only renames during apply)
1024
self.failUnlessEqual(2, transform.rename_count)
1026
def test_cancel_parent(self):
1027
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1029
This is like the test_change_parent, except that we cancel the parent
1030
before adjusting the path. The transform must detect that the
1031
directory is non-empty, and move children to safe locations.
1033
transform, root = self.get_transform()
1034
parent1 = transform.new_directory('parent1', root)
1035
child1 = transform.new_file('child1', parent1, 'contents')
1036
child2 = transform.new_file('child2', parent1, 'contents')
1038
transform.cancel_creation(parent1)
1040
self.fail('Failed to move child1 before deleting parent1')
1041
transform.cancel_creation(child2)
1042
transform.create_directory(parent1)
1044
transform.cancel_creation(parent1)
1045
# If the transform incorrectly believes that child2 is still in
1046
# parent1's limbo directory, it will try to rename it and fail
1047
# because was already moved by the first cancel_creation.
1049
self.fail('Transform still thinks child2 is a child of parent1')
1050
parent2 = transform.new_directory('parent2', root)
1051
transform.adjust_path('child1', parent2, child1)
1053
self.failIfExists(self.wt.abspath('parent1'))
1054
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1055
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1056
self.failUnlessEqual(2, transform.rename_count)
1058
def test_adjust_and_cancel(self):
1059
"""Make sure adjust_path keeps track of limbo children properly"""
1060
transform, root = self.get_transform()
1061
parent1 = transform.new_directory('parent1', root)
1062
child1 = transform.new_file('child1', parent1, 'contents')
1063
parent2 = transform.new_directory('parent2', root)
1064
transform.adjust_path('child1', parent2, child1)
1065
transform.cancel_creation(child1)
1067
transform.cancel_creation(parent1)
1068
# if the transform thinks child1 is still in parent1's limbo
1069
# directory, it will attempt to move it and fail.
1071
self.fail('Transform still thinks child1 is a child of parent1')
1072
transform.finalize()
1074
def test_noname_contents(self):
1075
"""TreeTransform should permit deferring naming files."""
1076
transform, root = self.get_transform()
1077
parent = transform.trans_id_file_id('parent-id')
1079
transform.create_directory(parent)
1081
self.fail("Can't handle contents with no name")
1082
transform.finalize()
1084
def test_noname_contents_nested(self):
1085
"""TreeTransform should permit deferring naming files."""
1086
transform, root = self.get_transform()
1087
parent = transform.trans_id_file_id('parent-id')
1089
transform.create_directory(parent)
1091
self.fail("Can't handle contents with no name")
1092
child = transform.new_directory('child', parent)
1093
transform.adjust_path('parent', root, parent)
1095
self.failUnlessExists(self.wt.abspath('parent/child'))
1096
self.assertEqual(1, transform.rename_count)
1098
def test_reuse_name(self):
1099
"""Avoid reusing the same limbo name for different files"""
1100
transform, root = self.get_transform()
1101
parent = transform.new_directory('parent', root)
1102
child1 = transform.new_directory('child', parent)
1104
child2 = transform.new_directory('child', parent)
1106
self.fail('Tranform tried to use the same limbo name twice')
1107
transform.adjust_path('child2', parent, child2)
1109
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1110
# child2 is put into top-level limbo because child1 has already
1111
# claimed the direct limbo path when child2 is created. There is no
1112
# advantage in renaming files once they're in top-level limbo, except
1114
self.assertEqual(2, transform.rename_count)
1116
def test_reuse_when_first_moved(self):
1117
"""Don't avoid direct paths when it is safe to use them"""
1118
transform, root = self.get_transform()
1119
parent = transform.new_directory('parent', root)
1120
child1 = transform.new_directory('child', parent)
1121
transform.adjust_path('child1', parent, child1)
1122
child2 = transform.new_directory('child', parent)
1124
# limbo/new-1 => parent
1125
self.assertEqual(1, transform.rename_count)
1127
def test_reuse_after_cancel(self):
1128
"""Don't avoid direct paths when it is safe to use them"""
1129
transform, root = self.get_transform()
1130
parent2 = transform.new_directory('parent2', root)
1131
child1 = transform.new_directory('child1', parent2)
1132
transform.cancel_creation(parent2)
1133
transform.create_directory(parent2)
1134
child2 = transform.new_directory('child1', parent2)
1135
transform.adjust_path('child2', parent2, child1)
1137
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1138
self.assertEqual(2, transform.rename_count)
1140
def test_finalize_order(self):
1141
"""Finalize must be done in child-to-parent order"""
1142
transform, root = self.get_transform()
1143
parent = transform.new_directory('parent', root)
1144
child = transform.new_directory('child', parent)
1146
transform.finalize()
1148
self.fail('Tried to remove parent before child1')
1150
def test_cancel_with_cancelled_child_should_succeed(self):
1151
transform, root = self.get_transform()
1152
parent = transform.new_directory('parent', root)
1153
child = transform.new_directory('child', parent)
1154
transform.cancel_creation(child)
1155
transform.cancel_creation(parent)
1156
transform.finalize()
1158
def test_change_entry(self):
1159
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1160
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1163
def test_case_insensitive_clash(self):
1164
self.requireFeature(CaseInsensitiveFilesystemFeature)
1166
wt = self.make_branch_and_tree('.')
1167
tt = TreeTransform(wt) # TreeTransform obtains write lock
1169
tt.new_file('foo', tt.root, 'bar')
1170
tt.new_file('Foo', tt.root, 'spam')
1171
# Lie to tt that we've already resolved all conflicts.
1172
tt.apply(no_conflicts=True)
1176
err = self.assertRaises(errors.FileExists, tt_helper)
1177
self.assertContainsRe(str(err),
1178
"^File exists: .+/foo")
1180
def test_two_directories_clash(self):
1182
wt = self.make_branch_and_tree('.')
1183
tt = TreeTransform(wt) # TreeTransform obtains write lock
1185
foo_1 = tt.new_directory('foo', tt.root)
1186
tt.new_directory('bar', foo_1)
1187
foo_2 = tt.new_directory('foo', tt.root)
1188
tt.new_directory('baz', foo_2)
1189
# Lie to tt that we've already resolved all conflicts.
1190
tt.apply(no_conflicts=True)
1194
err = self.assertRaises(errors.FileExists, tt_helper)
1195
self.assertContainsRe(str(err),
1196
"^File exists: .+/foo")
1198
def test_two_directories_clash_finalize(self):
1200
wt = self.make_branch_and_tree('.')
1201
tt = TreeTransform(wt) # TreeTransform obtains write lock
1203
foo_1 = tt.new_directory('foo', tt.root)
1204
tt.new_directory('bar', foo_1)
1205
foo_2 = tt.new_directory('foo', tt.root)
1206
tt.new_directory('baz', foo_2)
1207
# Lie to tt that we've already resolved all conflicts.
1208
tt.apply(no_conflicts=True)
1212
err = self.assertRaises(errors.FileExists, tt_helper)
1213
self.assertContainsRe(str(err),
1214
"^File exists: .+/foo")
1217
class TransformGroup(object):
1219
def __init__(self, dirname, root_id):
1222
self.wt = BzrDir.create_standalone_workingtree(dirname)
1223
self.wt.set_root_id(root_id)
1224
self.b = self.wt.branch
1225
self.tt = TreeTransform(self.wt)
1226
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1229
def conflict_text(tree, merge):
1230
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1231
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1234
class TestTransformMerge(TestCaseInTempDir):
1235
def test_text_merge(self):
1236
root_id = generate_ids.gen_root_id()
1237
base = TransformGroup("base", root_id)
1238
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1239
base.tt.new_file('b', base.root, 'b1', 'b')
1240
base.tt.new_file('c', base.root, 'c', 'c')
1241
base.tt.new_file('d', base.root, 'd', 'd')
1242
base.tt.new_file('e', base.root, 'e', 'e')
1243
base.tt.new_file('f', base.root, 'f', 'f')
1244
base.tt.new_directory('g', base.root, 'g')
1245
base.tt.new_directory('h', base.root, 'h')
1247
other = TransformGroup("other", root_id)
1248
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1249
other.tt.new_file('b', other.root, 'b2', 'b')
1250
other.tt.new_file('c', other.root, 'c2', 'c')
1251
other.tt.new_file('d', other.root, 'd', 'd')
1252
other.tt.new_file('e', other.root, 'e2', 'e')
1253
other.tt.new_file('f', other.root, 'f', 'f')
1254
other.tt.new_file('g', other.root, 'g', 'g')
1255
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1256
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1258
this = TransformGroup("this", root_id)
1259
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1260
this.tt.new_file('b', this.root, 'b', 'b')
1261
this.tt.new_file('c', this.root, 'c', 'c')
1262
this.tt.new_file('d', this.root, 'd2', 'd')
1263
this.tt.new_file('e', this.root, 'e2', 'e')
1264
this.tt.new_file('f', this.root, 'f', 'f')
1265
this.tt.new_file('g', this.root, 'g', 'g')
1266
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1267
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1269
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1272
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1273
# three-way text conflict
1274
self.assertEqual(this.wt.get_file('b').read(),
1275
conflict_text('b', 'b2'))
1277
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1279
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1280
# Ambigious clean merge
1281
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1283
self.assertEqual(this.wt.get_file('f').read(), 'f')
1284
# Correct correct results when THIS == OTHER
1285
self.assertEqual(this.wt.get_file('g').read(), 'g')
1286
# Text conflict when THIS & OTHER are text and BASE is dir
1287
self.assertEqual(this.wt.get_file('h').read(),
1288
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1289
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1291
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1293
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1294
self.assertEqual(this.wt.get_file('i').read(),
1295
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1296
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1298
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1300
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1301
modified = ['a', 'b', 'c', 'h', 'i']
1302
merge_modified = this.wt.merge_modified()
1303
self.assertSubset(merge_modified, modified)
1304
self.assertEqual(len(merge_modified), len(modified))
1305
file(this.wt.id2abspath('a'), 'wb').write('booga')
1307
merge_modified = this.wt.merge_modified()
1308
self.assertSubset(merge_modified, modified)
1309
self.assertEqual(len(merge_modified), len(modified))
1313
def test_file_merge(self):
1314
self.requireFeature(SymlinkFeature)
1315
root_id = generate_ids.gen_root_id()
1316
base = TransformGroup("BASE", root_id)
1317
this = TransformGroup("THIS", root_id)
1318
other = TransformGroup("OTHER", root_id)
1319
for tg in this, base, other:
1320
tg.tt.new_directory('a', tg.root, 'a')
1321
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1322
tg.tt.new_file('c', tg.root, 'c', 'c')
1323
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1324
targets = ((base, 'base-e', 'base-f', None, None),
1325
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1326
(other, 'other-e', None, 'other-g', 'other-h'))
1327
for tg, e_target, f_target, g_target, h_target in targets:
1328
for link, target in (('e', e_target), ('f', f_target),
1329
('g', g_target), ('h', h_target)):
1330
if target is not None:
1331
tg.tt.new_symlink(link, tg.root, target, link)
1333
for tg in this, base, other:
1335
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1336
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1337
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1338
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1339
for suffix in ('THIS', 'BASE', 'OTHER'):
1340
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1341
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1342
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1343
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1344
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1345
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1346
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1347
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1348
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1349
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1350
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1351
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1352
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1353
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1355
def test_filename_merge(self):
1356
root_id = generate_ids.gen_root_id()
1357
base = TransformGroup("BASE", root_id)
1358
this = TransformGroup("THIS", root_id)
1359
other = TransformGroup("OTHER", root_id)
1360
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1361
for t in [base, this, other]]
1362
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1363
for t in [base, this, other]]
1364
base.tt.new_directory('c', base_a, 'c')
1365
this.tt.new_directory('c1', this_a, 'c')
1366
other.tt.new_directory('c', other_b, 'c')
1368
base.tt.new_directory('d', base_a, 'd')
1369
this.tt.new_directory('d1', this_b, 'd')
1370
other.tt.new_directory('d', other_a, 'd')
1372
base.tt.new_directory('e', base_a, 'e')
1373
this.tt.new_directory('e', this_a, 'e')
1374
other.tt.new_directory('e1', other_b, 'e')
1376
base.tt.new_directory('f', base_a, 'f')
1377
this.tt.new_directory('f1', this_b, 'f')
1378
other.tt.new_directory('f1', other_b, 'f')
1380
for tg in [this, base, other]:
1382
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1383
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1384
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1385
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1386
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1388
def test_filename_merge_conflicts(self):
1389
root_id = generate_ids.gen_root_id()
1390
base = TransformGroup("BASE", root_id)
1391
this = TransformGroup("THIS", root_id)
1392
other = TransformGroup("OTHER", root_id)
1393
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1394
for t in [base, this, other]]
1395
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1396
for t in [base, this, other]]
1398
base.tt.new_file('g', base_a, 'g', 'g')
1399
other.tt.new_file('g1', other_b, 'g1', 'g')
1401
base.tt.new_file('h', base_a, 'h', 'h')
1402
this.tt.new_file('h1', this_b, 'h1', 'h')
1404
base.tt.new_file('i', base.root, 'i', 'i')
1405
other.tt.new_directory('i1', this_b, 'i')
1407
for tg in [this, base, other]:
1409
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1411
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1412
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1413
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1414
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1415
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1416
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1417
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1420
class TestBuildTree(tests.TestCaseWithTransport):
1422
def test_build_tree_with_symlinks(self):
1423
self.requireFeature(SymlinkFeature)
1425
a = BzrDir.create_standalone_workingtree('a')
1427
file('a/foo/bar', 'wb').write('contents')
1428
os.symlink('a/foo/bar', 'a/foo/baz')
1429
a.add(['foo', 'foo/bar', 'foo/baz'])
1430
a.commit('initial commit')
1431
b = BzrDir.create_standalone_workingtree('b')
1432
basis = a.basis_tree()
1434
self.addCleanup(basis.unlock)
1435
build_tree(basis, b)
1436
self.assertIs(os.path.isdir('b/foo'), True)
1437
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1438
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1440
def test_build_with_references(self):
1441
tree = self.make_branch_and_tree('source',
1442
format='dirstate-with-subtree')
1443
subtree = self.make_branch_and_tree('source/subtree',
1444
format='dirstate-with-subtree')
1445
tree.add_reference(subtree)
1446
tree.commit('a revision')
1447
tree.branch.create_checkout('target')
1448
self.failUnlessExists('target')
1449
self.failUnlessExists('target/subtree')
1451
def test_file_conflict_handling(self):
1452
"""Ensure that when building trees, conflict handling is done"""
1453
source = self.make_branch_and_tree('source')
1454
target = self.make_branch_and_tree('target')
1455
self.build_tree(['source/file', 'target/file'])
1456
source.add('file', 'new-file')
1457
source.commit('added file')
1458
build_tree(source.basis_tree(), target)
1459
self.assertEqual([DuplicateEntry('Moved existing file to',
1460
'file.moved', 'file', None, 'new-file')],
1462
target2 = self.make_branch_and_tree('target2')
1463
target_file = file('target2/file', 'wb')
1465
source_file = file('source/file', 'rb')
1467
target_file.write(source_file.read())
1472
build_tree(source.basis_tree(), target2)
1473
self.assertEqual([], target2.conflicts())
1475
def test_symlink_conflict_handling(self):
1476
"""Ensure that when building trees, conflict handling is done"""
1477
self.requireFeature(SymlinkFeature)
1478
source = self.make_branch_and_tree('source')
1479
os.symlink('foo', 'source/symlink')
1480
source.add('symlink', 'new-symlink')
1481
source.commit('added file')
1482
target = self.make_branch_and_tree('target')
1483
os.symlink('bar', 'target/symlink')
1484
build_tree(source.basis_tree(), target)
1485
self.assertEqual([DuplicateEntry('Moved existing file to',
1486
'symlink.moved', 'symlink', None, 'new-symlink')],
1488
target = self.make_branch_and_tree('target2')
1489
os.symlink('foo', 'target2/symlink')
1490
build_tree(source.basis_tree(), target)
1491
self.assertEqual([], target.conflicts())
1493
def test_directory_conflict_handling(self):
1494
"""Ensure that when building trees, conflict handling is done"""
1495
source = self.make_branch_and_tree('source')
1496
target = self.make_branch_and_tree('target')
1497
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1498
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1499
source.commit('added file')
1500
build_tree(source.basis_tree(), target)
1501
self.assertEqual([], target.conflicts())
1502
self.failUnlessExists('target/dir1/file')
1504
# Ensure contents are merged
1505
target = self.make_branch_and_tree('target2')
1506
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1507
build_tree(source.basis_tree(), target)
1508
self.assertEqual([], target.conflicts())
1509
self.failUnlessExists('target2/dir1/file2')
1510
self.failUnlessExists('target2/dir1/file')
1512
# Ensure new contents are suppressed for existing branches
1513
target = self.make_branch_and_tree('target3')
1514
self.make_branch('target3/dir1')
1515
self.build_tree(['target3/dir1/file2'])
1516
build_tree(source.basis_tree(), target)
1517
self.failIfExists('target3/dir1/file')
1518
self.failUnlessExists('target3/dir1/file2')
1519
self.failUnlessExists('target3/dir1.diverted/file')
1520
self.assertEqual([DuplicateEntry('Diverted to',
1521
'dir1.diverted', 'dir1', 'new-dir1', None)],
1524
target = self.make_branch_and_tree('target4')
1525
self.build_tree(['target4/dir1/'])
1526
self.make_branch('target4/dir1/file')
1527
build_tree(source.basis_tree(), target)
1528
self.failUnlessExists('target4/dir1/file')
1529
self.assertEqual('directory', file_kind('target4/dir1/file'))
1530
self.failUnlessExists('target4/dir1/file.diverted')
1531
self.assertEqual([DuplicateEntry('Diverted to',
1532
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1535
def test_mixed_conflict_handling(self):
1536
"""Ensure that when building trees, conflict handling is done"""
1537
source = self.make_branch_and_tree('source')
1538
target = self.make_branch_and_tree('target')
1539
self.build_tree(['source/name', 'target/name/'])
1540
source.add('name', 'new-name')
1541
source.commit('added file')
1542
build_tree(source.basis_tree(), target)
1543
self.assertEqual([DuplicateEntry('Moved existing file to',
1544
'name.moved', 'name', None, 'new-name')], target.conflicts())
1546
def test_raises_in_populated(self):
1547
source = self.make_branch_and_tree('source')
1548
self.build_tree(['source/name'])
1550
source.commit('added name')
1551
target = self.make_branch_and_tree('target')
1552
self.build_tree(['target/name'])
1554
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1555
build_tree, source.basis_tree(), target)
1557
def test_build_tree_rename_count(self):
1558
source = self.make_branch_and_tree('source')
1559
self.build_tree(['source/file1', 'source/dir1/'])
1560
source.add(['file1', 'dir1'])
1561
source.commit('add1')
1562
target1 = self.make_branch_and_tree('target1')
1563
transform_result = build_tree(source.basis_tree(), target1)
1564
self.assertEqual(2, transform_result.rename_count)
1566
self.build_tree(['source/dir1/file2'])
1567
source.add(['dir1/file2'])
1568
source.commit('add3')
1569
target2 = self.make_branch_and_tree('target2')
1570
transform_result = build_tree(source.basis_tree(), target2)
1571
# children of non-root directories should not be renamed
1572
self.assertEqual(2, transform_result.rename_count)
1574
def test_build_tree_accelerator_tree(self):
1575
source = self.make_branch_and_tree('source')
1576
self.build_tree_contents([('source/file1', 'A')])
1577
self.build_tree_contents([('source/file2', 'B')])
1578
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1579
source.commit('commit files')
1580
self.build_tree_contents([('source/file2', 'C')])
1582
real_source_get_file = source.get_file
1583
def get_file(file_id, path=None):
1584
calls.append(file_id)
1585
return real_source_get_file(file_id, path)
1586
source.get_file = get_file
1588
self.addCleanup(source.unlock)
1589
target = self.make_branch_and_tree('target')
1590
revision_tree = source.basis_tree()
1591
revision_tree.lock_read()
1592
self.addCleanup(revision_tree.unlock)
1593
build_tree(revision_tree, target, source)
1594
self.assertEqual(['file1-id'], calls)
1596
self.addCleanup(target.unlock)
1597
self.assertEqual([], list(target._iter_changes(revision_tree)))
1599
def test_build_tree_accelerator_tree_missing_file(self):
1600
source = self.make_branch_and_tree('source')
1601
self.build_tree_contents([('source/file1', 'A')])
1602
self.build_tree_contents([('source/file2', 'B')])
1603
source.add(['file1', 'file2'])
1604
source.commit('commit files')
1605
os.unlink('source/file1')
1606
source.remove(['file2'])
1607
target = self.make_branch_and_tree('target')
1608
revision_tree = source.basis_tree()
1609
revision_tree.lock_read()
1610
self.addCleanup(revision_tree.unlock)
1611
build_tree(revision_tree, target, source)
1613
self.addCleanup(target.unlock)
1614
self.assertEqual([], list(target._iter_changes(revision_tree)))
1616
def test_build_tree_accelerator_wrong_kind(self):
1617
self.requireFeature(SymlinkFeature)
1618
source = self.make_branch_and_tree('source')
1619
self.build_tree_contents([('source/file1', '')])
1620
self.build_tree_contents([('source/file2', '')])
1621
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1622
source.commit('commit files')
1623
os.unlink('source/file2')
1624
self.build_tree_contents([('source/file2/', 'C')])
1625
os.unlink('source/file1')
1626
os.symlink('file2', 'source/file1')
1628
real_source_get_file = source.get_file
1629
def get_file(file_id, path=None):
1630
calls.append(file_id)
1631
return real_source_get_file(file_id, path)
1632
source.get_file = get_file
1634
self.addCleanup(source.unlock)
1635
target = self.make_branch_and_tree('target')
1636
revision_tree = source.basis_tree()
1637
revision_tree.lock_read()
1638
self.addCleanup(revision_tree.unlock)
1639
build_tree(revision_tree, target, source)
1640
self.assertEqual([], calls)
1642
self.addCleanup(target.unlock)
1643
self.assertEqual([], list(target._iter_changes(revision_tree)))
1645
def test_build_tree_accelerator_tree_moved(self):
1646
source = self.make_branch_and_tree('source')
1647
self.build_tree_contents([('source/file1', 'A')])
1648
source.add(['file1'], ['file1-id'])
1649
source.commit('commit files')
1650
source.rename_one('file1', 'file2')
1652
self.addCleanup(source.unlock)
1653
target = self.make_branch_and_tree('target')
1654
revision_tree = source.basis_tree()
1655
revision_tree.lock_read()
1656
self.addCleanup(revision_tree.unlock)
1657
build_tree(revision_tree, target, source)
1659
self.addCleanup(target.unlock)
1660
self.assertEqual([], list(target._iter_changes(revision_tree)))
1663
class MockTransform(object):
1665
def has_named_child(self, by_parent, parent_id, name):
1666
for child_id in by_parent[parent_id]:
1670
elif name == "name.~%s~" % child_id:
1675
class MockEntry(object):
1677
object.__init__(self)
1681
class TestGetBackupName(TestCase):
1682
def test_get_backup_name(self):
1683
tt = MockTransform()
1684
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1685
self.assertEqual(name, 'name.~1~')
1686
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1687
self.assertEqual(name, 'name.~2~')
1688
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1689
self.assertEqual(name, 'name.~1~')
1690
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1691
self.assertEqual(name, 'name.~1~')
1692
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1693
self.assertEqual(name, 'name.~4~')
1696
class TestFileMover(tests.TestCaseWithTransport):
1698
def test_file_mover(self):
1699
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1700
mover = _FileMover()
1701
mover.rename('a', 'q')
1702
self.failUnlessExists('q')
1703
self.failIfExists('a')
1704
self.failUnlessExists('q/b')
1705
self.failUnlessExists('c')
1706
self.failUnlessExists('c/d')
1708
def test_pre_delete_rollback(self):
1709
self.build_tree(['a/'])
1710
mover = _FileMover()
1711
mover.pre_delete('a', 'q')
1712
self.failUnlessExists('q')
1713
self.failIfExists('a')
1715
self.failIfExists('q')
1716
self.failUnlessExists('a')
1718
def test_apply_deletions(self):
1719
self.build_tree(['a/', 'b/'])
1720
mover = _FileMover()
1721
mover.pre_delete('a', 'q')
1722
mover.pre_delete('b', 'r')
1723
self.failUnlessExists('q')
1724
self.failUnlessExists('r')
1725
self.failIfExists('a')
1726
self.failIfExists('b')
1727
mover.apply_deletions()
1728
self.failIfExists('q')
1729
self.failIfExists('r')
1730
self.failIfExists('a')
1731
self.failIfExists('b')
1733
def test_file_mover_rollback(self):
1734
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1735
mover = _FileMover()
1736
mover.rename('c/d', 'c/f')
1737
mover.rename('c/e', 'c/d')
1739
mover.rename('a', 'c')
1740
except errors.FileExists, e:
1742
self.failUnlessExists('a')
1743
self.failUnlessExists('c/d')
1746
class Bogus(Exception):
1750
class TestTransformRollback(tests.TestCaseWithTransport):
1752
class ExceptionFileMover(_FileMover):
1754
def __init__(self, bad_source=None, bad_target=None):
1755
_FileMover.__init__(self)
1756
self.bad_source = bad_source
1757
self.bad_target = bad_target
1759
def rename(self, source, target):
1760
if (self.bad_source is not None and
1761
source.endswith(self.bad_source)):
1763
elif (self.bad_target is not None and
1764
target.endswith(self.bad_target)):
1767
_FileMover.rename(self, source, target)
1769
def test_rollback_rename(self):
1770
tree = self.make_branch_and_tree('.')
1771
self.build_tree(['a/', 'a/b'])
1772
tt = TreeTransform(tree)
1773
self.addCleanup(tt.finalize)
1774
a_id = tt.trans_id_tree_path('a')
1775
tt.adjust_path('c', tt.root, a_id)
1776
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1777
self.assertRaises(Bogus, tt.apply,
1778
_mover=self.ExceptionFileMover(bad_source='a'))
1779
self.failUnlessExists('a')
1780
self.failUnlessExists('a/b')
1782
self.failUnlessExists('c')
1783
self.failUnlessExists('c/d')
1785
def test_rollback_rename_into_place(self):
1786
tree = self.make_branch_and_tree('.')
1787
self.build_tree(['a/', 'a/b'])
1788
tt = TreeTransform(tree)
1789
self.addCleanup(tt.finalize)
1790
a_id = tt.trans_id_tree_path('a')
1791
tt.adjust_path('c', tt.root, a_id)
1792
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1793
self.assertRaises(Bogus, tt.apply,
1794
_mover=self.ExceptionFileMover(bad_target='c/d'))
1795
self.failUnlessExists('a')
1796
self.failUnlessExists('a/b')
1798
self.failUnlessExists('c')
1799
self.failUnlessExists('c/d')
1801
def test_rollback_deletion(self):
1802
tree = self.make_branch_and_tree('.')
1803
self.build_tree(['a/', 'a/b'])
1804
tt = TreeTransform(tree)
1805
self.addCleanup(tt.finalize)
1806
a_id = tt.trans_id_tree_path('a')
1807
tt.delete_contents(a_id)
1808
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1809
self.assertRaises(Bogus, tt.apply,
1810
_mover=self.ExceptionFileMover(bad_target='d'))
1811
self.failUnlessExists('a')
1812
self.failUnlessExists('a/b')
1814
def test_resolve_no_parent(self):
1815
wt = self.make_branch_and_tree('.')
1816
tt = TreeTransform(wt)
1817
self.addCleanup(tt.finalize)
1818
parent = tt.trans_id_file_id('parent-id')
1819
tt.new_file('file', parent, 'Contents')
1820
resolve_conflicts(tt)
1823
class TestTransformPreview(tests.TestCaseWithTransport):
1825
def create_tree(self):
1826
tree = self.make_branch_and_tree('.')
1827
self.build_tree_contents([('a', 'content 1')])
1828
tree.add('a', 'a-id')
1829
tree.commit('rev1', rev_id='rev1')
1830
return tree.branch.repository.revision_tree('rev1')
1832
def get_empty_preview(self):
1833
repository = self.make_repository('repo')
1834
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
1835
return TransformPreview(tree)
1837
def test_transform_preview(self):
1838
revision_tree = self.create_tree()
1839
preview = TransformPreview(revision_tree)
1841
def test_transform_preview_tree(self):
1842
revision_tree = self.create_tree()
1843
preview = TransformPreview(revision_tree)
1844
preview.get_preview_tree()
1846
def test_transform_new_file(self):
1847
revision_tree = self.create_tree()
1848
preview = TransformPreview(revision_tree)
1849
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1850
preview_tree = preview.get_preview_tree()
1851
self.assertEqual(preview_tree.kind('file2-id'), 'file')
1853
preview_tree.get_file('file2-id').read(), 'content B\n')
1855
def test_diff_preview_tree(self):
1856
revision_tree = self.create_tree()
1857
preview = TransformPreview(revision_tree)
1858
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1859
preview_tree = preview.get_preview_tree()
1861
show_diff_trees(revision_tree, preview_tree, out)
1862
lines = out.getvalue().splitlines()
1863
self.assertEqual(lines[0], "=== added file 'file2'")
1864
# 3 lines of diff administrivia
1865
self.assertEqual(lines[4], "+content B")
1867
def test_transform_conflicts(self):
1868
revision_tree = self.create_tree()
1869
preview = TransformPreview(revision_tree)
1870
preview.new_file('a', preview.root, 'content 2')
1871
resolve_conflicts(preview)
1872
trans_id = preview.trans_id_file_id('a-id')
1873
self.assertEqual('a.moved', preview.final_name(trans_id))
1875
def get_tree_and_preview_tree(self):
1876
revision_tree = self.create_tree()
1877
preview = TransformPreview(revision_tree)
1878
a_trans_id = preview.trans_id_file_id('a-id')
1879
preview.delete_contents(a_trans_id)
1880
preview.create_file('b content', a_trans_id)
1881
preview_tree = preview.get_preview_tree()
1882
return revision_tree, preview_tree
1884
def test_iter_changes(self):
1885
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1886
root = revision_tree.inventory.root.file_id
1887
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
1888
(root, root), ('a', 'a'), ('file', 'file'),
1890
list(preview_tree._iter_changes(revision_tree)))
1892
def test_wrong_tree_value_error(self):
1893
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1894
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1896
self.assertEqual('from_tree must be transform source tree.', str(e))
1898
def test_include_unchanged_value_error(self):
1899
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1900
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1901
revision_tree, include_unchanged=True)
1902
self.assertEqual('include_unchanged is not supported', str(e))
1904
def test_specific_files(self):
1905
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1906
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1907
revision_tree, specific_files=['pete'])
1908
self.assertEqual('specific_files is not supported', str(e))
1910
def test_want_unversioned_value_error(self):
1911
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1912
e = self.assertRaises(ValueError, preview_tree._iter_changes,
1913
revision_tree, want_unversioned=True)
1914
self.assertEqual('want_unversioned is not supported', str(e))
1916
def test_ignore_extra_trees_no_specific_files(self):
1917
# extra_trees is harmless without specific_files, so we'll silently
1918
# accept it, even though we won't use it.
1919
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1920
preview_tree._iter_changes(revision_tree, extra_trees=[preview_tree])
1922
def test_ignore_require_versioned_no_specific_files(self):
1923
# require_versioned is meaningless without specific_files.
1924
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1925
preview_tree._iter_changes(revision_tree, require_versioned=False)
1927
def test_ignore_pb(self):
1928
# pb could be supported, but TT.iter_changes doesn't support it.
1929
revision_tree, preview_tree = self.get_tree_and_preview_tree()
1930
preview_tree._iter_changes(revision_tree, pb=progress.DummyProgress())
1932
def test_kind(self):
1933
revision_tree = self.create_tree()
1934
preview = TransformPreview(revision_tree)
1935
preview.new_file('file', preview.root, 'contents', 'file-id')
1936
preview.new_directory('directory', preview.root, 'dir-id')
1937
preview_tree = preview.get_preview_tree()
1938
self.assertEqual('file', preview_tree.kind('file-id'))
1939
self.assertEqual('directory', preview_tree.kind('dir-id'))
1941
def test_get_file_mtime(self):
1942
preview = self.get_empty_preview()
1943
file_trans_id = preview.new_file('file', preview.root, 'contents',
1945
limbo_path = preview._limbo_name(file_trans_id)
1946
preview_tree = preview.get_preview_tree()
1947
self.assertEqual(os.stat(limbo_path).st_mtime,
1948
preview_tree.get_file_mtime('file-id'))
1950
def test_get_file(self):
1951
preview = self.get_empty_preview()
1952
preview.new_file('file', preview.root, 'contents', 'file-id')
1953
preview_tree = preview.get_preview_tree()
1954
tree_file = preview_tree.get_file('file-id')
1956
self.assertEqual('contents', tree_file.read())