1
# Copyright (C) 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28
from bzrlib.bzrdir import BzrDir
29
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
UnversionedParent, ParentLoop, DeletingParent,)
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
ReusingTransform, CantMoveRoot,
33
PathsNotVersionedError, ExistingLimbo,
34
ExistingPendingDeletion, ImmortalLimbo,
35
ImmortalPendingDeletion, LockError)
36
from bzrlib.osutils import file_kind, pathjoin
37
from bzrlib.merge import Merge3Merger
38
from bzrlib.tests import (
44
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
45
resolve_conflicts, cook_conflicts,
46
find_interesting, build_tree, get_backup_name,
47
change_entry, _FileMover, resolve_checkout)
50
class TestTreeTransform(tests.TestCaseWithTransport):
53
super(TestTreeTransform, self).setUp()
54
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
57
def get_transform(self):
58
transform = TreeTransform(self.wt)
59
#self.addCleanup(transform.finalize)
60
return transform, transform.root
62
def test_existing_limbo(self):
63
transform, root = self.get_transform()
64
limbo_name = transform._limbodir
65
deletion_path = transform._deletiondir
66
os.mkdir(pathjoin(limbo_name, 'hehe'))
67
self.assertRaises(ImmortalLimbo, transform.apply)
68
self.assertRaises(LockError, self.wt.unlock)
69
self.assertRaises(ExistingLimbo, self.get_transform)
70
self.assertRaises(LockError, self.wt.unlock)
71
os.rmdir(pathjoin(limbo_name, 'hehe'))
73
os.rmdir(deletion_path)
74
transform, root = self.get_transform()
77
def test_existing_pending_deletion(self):
78
transform, root = self.get_transform()
79
deletion_path = self._limbodir = urlutils.local_path_from_url(
80
transform._tree._control_files.controlfilename('pending-deletion'))
81
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
82
self.assertRaises(ImmortalPendingDeletion, transform.apply)
83
self.assertRaises(LockError, self.wt.unlock)
84
self.assertRaises(ExistingPendingDeletion, self.get_transform)
87
transform, root = self.get_transform()
88
self.wt.lock_tree_write()
89
self.addCleanup(self.wt.unlock)
90
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
91
imaginary_id = transform.trans_id_tree_path('imaginary')
92
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
93
self.assertEqual(imaginary_id, imaginary_id2)
94
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
95
self.assertEqual(transform.final_kind(root), 'directory')
96
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
97
trans_id = transform.create_path('name', root)
98
self.assertIs(transform.final_file_id(trans_id), None)
99
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
100
transform.create_file('contents', trans_id)
101
transform.set_executability(True, trans_id)
102
transform.version_file('my_pretties', trans_id)
103
self.assertRaises(DuplicateKey, transform.version_file,
104
'my_pretties', trans_id)
105
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
106
self.assertEqual(transform.final_parent(trans_id), root)
107
self.assertIs(transform.final_parent(root), ROOT_PARENT)
108
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
109
oz_id = transform.create_path('oz', root)
110
transform.create_directory(oz_id)
111
transform.version_file('ozzie', oz_id)
112
trans_id2 = transform.create_path('name2', root)
113
transform.create_file('contents', trans_id2)
114
transform.set_executability(False, trans_id2)
115
transform.version_file('my_pretties2', trans_id2)
116
modified_paths = transform.apply().modified_paths
117
self.assertEqual('contents', self.wt.get_file_byname('name').read())
118
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
119
self.assertIs(self.wt.is_executable('my_pretties'), True)
120
self.assertIs(self.wt.is_executable('my_pretties2'), False)
121
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
122
self.assertEqual(len(modified_paths), 3)
123
tree_mod_paths = [self.wt.id2abspath(f) for f in
124
('ozzie', 'my_pretties', 'my_pretties2')]
125
self.assertSubset(tree_mod_paths, modified_paths)
126
# is it safe to finalize repeatedly?
130
def test_convenience(self):
131
transform, root = self.get_transform()
132
self.wt.lock_tree_write()
133
self.addCleanup(self.wt.unlock)
134
trans_id = transform.new_file('name', root, 'contents',
136
oz = transform.new_directory('oz', root, 'oz-id')
137
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
138
toto = transform.new_file('toto', dorothy, 'toto-contents',
141
self.assertEqual(len(transform.find_conflicts()), 0)
143
self.assertRaises(ReusingTransform, transform.find_conflicts)
144
self.assertEqual('contents', file(self.wt.abspath('name')).read())
145
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
146
self.assertIs(self.wt.is_executable('my_pretties'), True)
147
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
148
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
149
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
151
self.assertEqual('toto-contents',
152
self.wt.get_file_byname('oz/dorothy/toto').read())
153
self.assertIs(self.wt.is_executable('toto-id'), False)
155
def test_tree_reference(self):
156
transform, root = self.get_transform()
157
tree = transform._tree
158
trans_id = transform.new_directory('reference', root, 'subtree-id')
159
transform.set_tree_reference('subtree-revision', trans_id)
162
self.addCleanup(tree.unlock)
163
self.assertEqual('subtree-revision',
164
tree.inventory['subtree-id'].reference_revision)
166
def test_conflicts(self):
167
transform, root = self.get_transform()
168
trans_id = transform.new_file('name', root, 'contents',
170
self.assertEqual(len(transform.find_conflicts()), 0)
171
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
172
self.assertEqual(transform.find_conflicts(),
173
[('duplicate', trans_id, trans_id2, 'name')])
174
self.assertRaises(MalformedTransform, transform.apply)
175
transform.adjust_path('name', trans_id, trans_id2)
176
self.assertEqual(transform.find_conflicts(),
177
[('non-directory parent', trans_id)])
178
tinman_id = transform.trans_id_tree_path('tinman')
179
transform.adjust_path('name', tinman_id, trans_id2)
180
self.assertEqual(transform.find_conflicts(),
181
[('unversioned parent', tinman_id),
182
('missing parent', tinman_id)])
183
lion_id = transform.create_path('lion', root)
184
self.assertEqual(transform.find_conflicts(),
185
[('unversioned parent', tinman_id),
186
('missing parent', tinman_id)])
187
transform.adjust_path('name', lion_id, trans_id2)
188
self.assertEqual(transform.find_conflicts(),
189
[('unversioned parent', lion_id),
190
('missing parent', lion_id)])
191
transform.version_file("Courage", lion_id)
192
self.assertEqual(transform.find_conflicts(),
193
[('missing parent', lion_id),
194
('versioning no contents', lion_id)])
195
transform.adjust_path('name2', root, trans_id2)
196
self.assertEqual(transform.find_conflicts(),
197
[('versioning no contents', lion_id)])
198
transform.create_file('Contents, okay?', lion_id)
199
transform.adjust_path('name2', trans_id2, trans_id2)
200
self.assertEqual(transform.find_conflicts(),
201
[('parent loop', trans_id2),
202
('non-directory parent', trans_id2)])
203
transform.adjust_path('name2', root, trans_id2)
204
oz_id = transform.new_directory('oz', root)
205
transform.set_executability(True, oz_id)
206
self.assertEqual(transform.find_conflicts(),
207
[('unversioned executability', oz_id)])
208
transform.version_file('oz-id', oz_id)
209
self.assertEqual(transform.find_conflicts(),
210
[('non-file executability', oz_id)])
211
transform.set_executability(None, oz_id)
212
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
214
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
215
self.assertEqual('contents', file(self.wt.abspath('name')).read())
216
transform2, root = self.get_transform()
217
oz_id = transform2.trans_id_tree_file_id('oz-id')
218
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
219
result = transform2.find_conflicts()
220
fp = FinalPaths(transform2)
221
self.assert_('oz/tip' in transform2._tree_path_ids)
222
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
223
self.assertEqual(len(result), 2)
224
self.assertEqual((result[0][0], result[0][1]),
225
('duplicate', newtip))
226
self.assertEqual((result[1][0], result[1][2]),
227
('duplicate id', newtip))
228
transform2.finalize()
229
transform3 = TreeTransform(self.wt)
230
self.addCleanup(transform3.finalize)
231
oz_id = transform3.trans_id_tree_file_id('oz-id')
232
transform3.delete_contents(oz_id)
233
self.assertEqual(transform3.find_conflicts(),
234
[('missing parent', oz_id)])
235
root_id = transform3.root
236
tip_id = transform3.trans_id_tree_file_id('tip-id')
237
transform3.adjust_path('tip', root_id, tip_id)
240
def test_conflict_on_case_insensitive(self):
241
tree = self.make_branch_and_tree('tree')
242
# Don't try this at home, kids!
243
# Force the tree to report that it is case sensitive, for conflict
245
tree.case_sensitive = True
246
transform = TreeTransform(tree)
247
self.addCleanup(transform.finalize)
248
transform.new_file('file', transform.root, 'content')
249
transform.new_file('FiLe', transform.root, 'content')
250
result = transform.find_conflicts()
251
self.assertEqual([], result)
252
# Force the tree to report that it is case insensitive, for conflict
254
tree.case_sensitive = False
255
result = transform.find_conflicts()
256
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
258
def test_conflict_on_case_insensitive_existing(self):
259
tree = self.make_branch_and_tree('tree')
260
self.build_tree(['tree/FiLe'])
261
# Don't try this at home, kids!
262
# Force the tree to report that it is case sensitive, for conflict
264
tree.case_sensitive = True
265
transform = TreeTransform(tree)
266
self.addCleanup(transform.finalize)
267
transform.new_file('file', transform.root, 'content')
268
result = transform.find_conflicts()
269
self.assertEqual([], result)
270
# Force the tree to report that it is case insensitive, for conflict
272
tree.case_sensitive = False
273
result = transform.find_conflicts()
274
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
276
def test_resolve_case_insensitive_conflict(self):
277
tree = self.make_branch_and_tree('tree')
278
# Don't try this at home, kids!
279
# Force the tree to report that it is case insensitive, for conflict
281
tree.case_sensitive = False
282
transform = TreeTransform(tree)
283
self.addCleanup(transform.finalize)
284
transform.new_file('file', transform.root, 'content')
285
transform.new_file('FiLe', transform.root, 'content')
286
resolve_conflicts(transform)
288
self.failUnlessExists('tree/file')
289
self.failUnlessExists('tree/FiLe.moved')
291
def test_resolve_checkout_case_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,
302
pass_func=lambda t, c: resolve_checkout(t, c, []))
304
self.failUnlessExists('tree/file')
305
self.failUnlessExists('tree/FiLe.moved')
307
def test_apply_case_conflict(self):
308
"""Ensure that a transform with case conflicts can always be applied"""
309
tree = self.make_branch_and_tree('tree')
310
transform = TreeTransform(tree)
311
self.addCleanup(transform.finalize)
312
transform.new_file('file', transform.root, 'content')
313
transform.new_file('FiLe', transform.root, 'content')
314
dir = transform.new_directory('dir', transform.root)
315
transform.new_file('dirfile', dir, 'content')
316
transform.new_file('dirFiLe', dir, 'content')
317
resolve_conflicts(transform)
319
self.failUnlessExists('tree/file')
320
if not os.path.exists('tree/FiLe.moved'):
321
self.failUnlessExists('tree/FiLe')
322
self.failUnlessExists('tree/dir/dirfile')
323
if not os.path.exists('tree/dir/dirFiLe.moved'):
324
self.failUnlessExists('tree/dir/dirFiLe')
326
def test_case_insensitive_limbo(self):
327
tree = self.make_branch_and_tree('tree')
328
# Don't try this at home, kids!
329
# Force the tree to report that it is case insensitive
330
tree.case_sensitive = False
331
transform = TreeTransform(tree)
332
self.addCleanup(transform.finalize)
333
dir = transform.new_directory('dir', transform.root)
334
first = transform.new_file('file', dir, 'content')
335
second = transform.new_file('FiLe', dir, 'content')
336
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
337
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
339
def test_add_del(self):
340
start, root = self.get_transform()
341
start.new_directory('a', root, 'a')
343
transform, root = self.get_transform()
344
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
345
transform.new_directory('a', root, 'a')
348
def test_unversioning(self):
349
create_tree, root = self.get_transform()
350
parent_id = create_tree.new_directory('parent', root, 'parent-id')
351
create_tree.new_file('child', parent_id, 'child', 'child-id')
353
unversion = TreeTransform(self.wt)
354
self.addCleanup(unversion.finalize)
355
parent = unversion.trans_id_tree_path('parent')
356
unversion.unversion_file(parent)
357
self.assertEqual(unversion.find_conflicts(),
358
[('unversioned parent', parent_id)])
359
file_id = unversion.trans_id_tree_file_id('child-id')
360
unversion.unversion_file(file_id)
363
def test_name_invariants(self):
364
create_tree, root = self.get_transform()
366
root = create_tree.root
367
create_tree.new_file('name1', root, 'hello1', 'name1')
368
create_tree.new_file('name2', root, 'hello2', 'name2')
369
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
370
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
371
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
372
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
375
mangle_tree,root = self.get_transform()
376
root = mangle_tree.root
378
name1 = mangle_tree.trans_id_tree_file_id('name1')
379
name2 = mangle_tree.trans_id_tree_file_id('name2')
380
mangle_tree.adjust_path('name2', root, name1)
381
mangle_tree.adjust_path('name1', root, name2)
383
#tests for deleting parent directories
384
ddir = mangle_tree.trans_id_tree_file_id('ddir')
385
mangle_tree.delete_contents(ddir)
386
dfile = mangle_tree.trans_id_tree_file_id('dfile')
387
mangle_tree.delete_versioned(dfile)
388
mangle_tree.unversion_file(dfile)
389
mfile = mangle_tree.trans_id_tree_file_id('mfile')
390
mangle_tree.adjust_path('mfile', root, mfile)
392
#tests for adding parent directories
393
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
394
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
395
mangle_tree.adjust_path('mfile2', newdir, mfile2)
396
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
397
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
398
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
399
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
401
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
402
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
403
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
404
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
405
self.assertEqual(file(mfile2_path).read(), 'later2')
406
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
407
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
408
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
409
self.assertEqual(file(newfile_path).read(), 'hello3')
410
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
411
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
412
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
414
def test_both_rename(self):
415
create_tree,root = self.get_transform()
416
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
417
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
419
mangle_tree,root = self.get_transform()
420
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
421
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
422
mangle_tree.adjust_path('test', root, selftest)
423
mangle_tree.adjust_path('test_too_much', root, selftest)
424
mangle_tree.set_executability(True, blackbox)
427
def test_both_rename2(self):
428
create_tree,root = self.get_transform()
429
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
430
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
431
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
432
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
435
mangle_tree,root = self.get_transform()
436
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
437
tests = mangle_tree.trans_id_tree_file_id('tests-id')
438
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
439
mangle_tree.adjust_path('selftest', bzrlib, tests)
440
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
441
mangle_tree.set_executability(True, test_too_much)
444
def test_both_rename3(self):
445
create_tree,root = self.get_transform()
446
tests = create_tree.new_directory('tests', root, 'tests-id')
447
create_tree.new_file('test_too_much.py', tests, 'hello1',
450
mangle_tree,root = self.get_transform()
451
tests = mangle_tree.trans_id_tree_file_id('tests-id')
452
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
453
mangle_tree.adjust_path('selftest', root, tests)
454
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
455
mangle_tree.set_executability(True, test_too_much)
458
def test_move_dangling_ie(self):
459
create_tree, root = self.get_transform()
461
root = create_tree.root
462
create_tree.new_file('name1', root, 'hello1', 'name1')
464
delete_contents, root = self.get_transform()
465
file = delete_contents.trans_id_tree_file_id('name1')
466
delete_contents.delete_contents(file)
467
delete_contents.apply()
468
move_id, root = self.get_transform()
469
name1 = move_id.trans_id_tree_file_id('name1')
470
newdir = move_id.new_directory('dir', root, 'newdir')
471
move_id.adjust_path('name2', newdir, name1)
474
def test_replace_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 = TreeTransform(self.wt)
481
self.addCleanup(delete_contents.finalize)
482
file = delete_contents.trans_id_tree_file_id('name1')
483
delete_contents.delete_contents(file)
484
delete_contents.apply()
485
delete_contents.finalize()
486
replace = TreeTransform(self.wt)
487
self.addCleanup(replace.finalize)
488
name2 = replace.new_file('name2', root, 'hello2', 'name1')
489
conflicts = replace.find_conflicts()
490
name1 = replace.trans_id_tree_file_id('name1')
491
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
492
resolve_conflicts(replace)
495
def test_symlinks(self):
496
self.requireFeature(SymlinkFeature)
497
transform,root = self.get_transform()
498
oz_id = transform.new_directory('oz', root, 'oz-id')
499
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
501
wiz_id = transform.create_path('wizard2', oz_id)
502
transform.create_symlink('behind_curtain', wiz_id)
503
transform.version_file('wiz-id2', wiz_id)
504
transform.set_executability(True, wiz_id)
505
self.assertEqual(transform.find_conflicts(),
506
[('non-file executability', wiz_id)])
507
transform.set_executability(None, wiz_id)
509
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
510
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
511
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
513
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
516
def test_unable_create_symlink(self):
518
wt = self.make_branch_and_tree('.')
519
tt = TreeTransform(wt) # TreeTransform obtains write lock
521
tt.new_symlink('foo', tt.root, 'bar')
525
os_symlink = getattr(os, 'symlink', None)
528
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
530
"Unable to create symlink 'foo' on this platform",
534
os.symlink = os_symlink
536
def get_conflicted(self):
537
create,root = self.get_transform()
538
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
539
oz = create.new_directory('oz', root, 'oz-id')
540
create.new_directory('emeraldcity', oz, 'emerald-id')
542
conflicts,root = self.get_transform()
543
# set up duplicate entry, duplicate id
544
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
546
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
547
oz = conflicts.trans_id_tree_file_id('oz-id')
548
# set up DeletedParent parent conflict
549
conflicts.delete_versioned(oz)
550
emerald = conflicts.trans_id_tree_file_id('emerald-id')
551
# set up MissingParent conflict
552
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
553
conflicts.adjust_path('munchkincity', root, munchkincity)
554
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
556
conflicts.adjust_path('emeraldcity', emerald, emerald)
557
return conflicts, emerald, oz, old_dorothy, new_dorothy
559
def test_conflict_resolution(self):
560
conflicts, emerald, oz, old_dorothy, new_dorothy =\
561
self.get_conflicted()
562
resolve_conflicts(conflicts)
563
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
564
self.assertIs(conflicts.final_file_id(old_dorothy), None)
565
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
566
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
567
self.assertEqual(conflicts.final_parent(emerald), oz)
570
def test_cook_conflicts(self):
571
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
572
raw_conflicts = resolve_conflicts(tt)
573
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
574
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
575
'dorothy', None, 'dorothy-id')
576
self.assertEqual(cooked_conflicts[0], duplicate)
577
duplicate_id = DuplicateID('Unversioned existing file',
578
'dorothy.moved', 'dorothy', None,
580
self.assertEqual(cooked_conflicts[1], duplicate_id)
581
missing_parent = MissingParent('Created directory', 'munchkincity',
583
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
584
self.assertEqual(cooked_conflicts[2], missing_parent)
585
unversioned_parent = UnversionedParent('Versioned directory',
588
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
590
self.assertEqual(cooked_conflicts[3], unversioned_parent)
591
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
592
'oz/emeraldcity', 'emerald-id', 'emerald-id')
593
self.assertEqual(cooked_conflicts[4], deleted_parent)
594
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
595
self.assertEqual(cooked_conflicts[6], parent_loop)
596
self.assertEqual(len(cooked_conflicts), 7)
599
def test_string_conflicts(self):
600
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
601
raw_conflicts = resolve_conflicts(tt)
602
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
604
conflicts_s = [str(c) for c in cooked_conflicts]
605
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
606
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
607
'Moved existing file to '
609
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
610
'Unversioned existing file '
612
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
613
' munchkincity. Created directory.')
614
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
615
' versioned, but has versioned'
616
' children. Versioned directory.')
617
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
618
" is not empty. Not deleting.")
619
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
620
' versioned, but has versioned'
621
' children. Versioned directory.')
622
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
623
' oz/emeraldcity. Cancelled move.')
625
def test_moving_versioned_directories(self):
626
create, root = self.get_transform()
627
kansas = create.new_directory('kansas', root, 'kansas-id')
628
create.new_directory('house', kansas, 'house-id')
629
create.new_directory('oz', root, 'oz-id')
631
cyclone, root = self.get_transform()
632
oz = cyclone.trans_id_tree_file_id('oz-id')
633
house = cyclone.trans_id_tree_file_id('house-id')
634
cyclone.adjust_path('house', oz, house)
637
def test_moving_root(self):
638
create, root = self.get_transform()
639
fun = create.new_directory('fun', root, 'fun-id')
640
create.new_directory('sun', root, 'sun-id')
641
create.new_directory('moon', root, 'moon')
643
transform, root = self.get_transform()
644
transform.adjust_root_path('oldroot', fun)
645
new_root=transform.trans_id_tree_path('')
646
transform.version_file('new-root', new_root)
649
def test_renames(self):
650
create, root = self.get_transform()
651
old = create.new_directory('old-parent', root, 'old-id')
652
intermediate = create.new_directory('intermediate', old, 'im-id')
653
myfile = create.new_file('myfile', intermediate, 'myfile-text',
656
rename, root = self.get_transform()
657
old = rename.trans_id_file_id('old-id')
658
rename.adjust_path('new', root, old)
659
myfile = rename.trans_id_file_id('myfile-id')
660
rename.set_executability(True, myfile)
663
def test_find_interesting(self):
664
create, root = self.get_transform()
666
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
667
create.new_file('uvfile', root, 'othertext')
669
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
670
find_interesting, wt, wt, ['vfile'])
671
self.assertEqual(result, set(['myfile-id']))
673
def test_set_executability_order(self):
674
"""Ensure that executability behaves the same, no matter what order.
676
- create file and set executability simultaneously
677
- create file and set executability afterward
678
- unsetting the executability of a file whose executability has not been
679
declared should throw an exception (this may happen when a
680
merge attempts to create a file with a duplicate ID)
682
transform, root = self.get_transform()
685
self.addCleanup(wt.unlock)
686
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
688
sac = transform.new_file('set_after_creation', root,
689
'Set after creation', 'sac')
690
transform.set_executability(True, sac)
691
uws = transform.new_file('unset_without_set', root, 'Unset badly',
693
self.assertRaises(KeyError, transform.set_executability, None, uws)
695
self.assertTrue(wt.is_executable('soc'))
696
self.assertTrue(wt.is_executable('sac'))
698
def test_preserve_mode(self):
699
"""File mode is preserved when replacing content"""
700
if sys.platform == 'win32':
701
raise TestSkipped('chmod has no effect on win32')
702
transform, root = self.get_transform()
703
transform.new_file('file1', root, 'contents', 'file1-id', True)
705
self.assertTrue(self.wt.is_executable('file1-id'))
706
transform, root = self.get_transform()
707
file1_id = transform.trans_id_tree_file_id('file1-id')
708
transform.delete_contents(file1_id)
709
transform.create_file('contents2', file1_id)
711
self.assertTrue(self.wt.is_executable('file1-id'))
713
def test__set_mode_stats_correctly(self):
714
"""_set_mode stats to determine file mode."""
715
if sys.platform == 'win32':
716
raise TestSkipped('chmod has no effect on win32')
720
def instrumented_stat(path):
721
stat_paths.append(path)
722
return real_stat(path)
724
transform, root = self.get_transform()
726
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
727
file_id='bar-id-1', executable=False)
730
transform, root = self.get_transform()
731
bar1_id = transform.trans_id_tree_path('bar')
732
bar2_id = transform.trans_id_tree_path('bar2')
734
os.stat = instrumented_stat
735
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
740
bar1_abspath = self.wt.abspath('bar')
741
self.assertEqual([bar1_abspath], stat_paths)
743
def test_iter_changes(self):
744
self.wt.set_root_id('eert_toor')
745
transform, root = self.get_transform()
746
transform.new_file('old', root, 'blah', 'id-1', True)
748
transform, root = self.get_transform()
750
self.assertEqual([], list(transform._iter_changes()))
751
old = transform.trans_id_tree_file_id('id-1')
752
transform.unversion_file(old)
753
self.assertEqual([('id-1', ('old', None), False, (True, False),
754
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
755
(True, True))], list(transform._iter_changes()))
756
transform.new_directory('new', root, 'id-1')
757
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
758
('eert_toor', 'eert_toor'), ('old', 'new'),
759
('file', 'directory'),
760
(True, False))], list(transform._iter_changes()))
764
def test_iter_changes_new(self):
765
self.wt.set_root_id('eert_toor')
766
transform, root = self.get_transform()
767
transform.new_file('old', root, 'blah')
769
transform, root = self.get_transform()
771
old = transform.trans_id_tree_path('old')
772
transform.version_file('id-1', old)
773
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
774
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
775
(False, False))], list(transform._iter_changes()))
779
def test_iter_changes_modifications(self):
780
self.wt.set_root_id('eert_toor')
781
transform, root = self.get_transform()
782
transform.new_file('old', root, 'blah', 'id-1')
783
transform.new_file('new', root, 'blah')
784
transform.new_directory('subdir', root, 'subdir-id')
786
transform, root = self.get_transform()
788
old = transform.trans_id_tree_path('old')
789
subdir = transform.trans_id_tree_file_id('subdir-id')
790
new = transform.trans_id_tree_path('new')
791
self.assertEqual([], list(transform._iter_changes()))
794
transform.delete_contents(old)
795
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
796
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
797
(False, False))], list(transform._iter_changes()))
800
transform.create_file('blah', old)
801
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
802
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
803
(False, False))], list(transform._iter_changes()))
804
transform.cancel_deletion(old)
805
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
806
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
807
(False, False))], list(transform._iter_changes()))
808
transform.cancel_creation(old)
810
# move file_id to a different file
811
self.assertEqual([], list(transform._iter_changes()))
812
transform.unversion_file(old)
813
transform.version_file('id-1', new)
814
transform.adjust_path('old', root, new)
815
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
816
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
817
(False, False))], list(transform._iter_changes()))
818
transform.cancel_versioning(new)
819
transform._removed_id = set()
822
self.assertEqual([], list(transform._iter_changes()))
823
transform.set_executability(True, old)
824
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
825
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
826
(False, True))], list(transform._iter_changes()))
827
transform.set_executability(None, old)
830
self.assertEqual([], list(transform._iter_changes()))
831
transform.adjust_path('new', root, old)
832
transform._new_parent = {}
833
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
834
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
835
(False, False))], list(transform._iter_changes()))
836
transform._new_name = {}
839
self.assertEqual([], list(transform._iter_changes()))
840
transform.adjust_path('new', subdir, old)
841
transform._new_name = {}
842
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
843
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
844
('file', 'file'), (False, False))],
845
list(transform._iter_changes()))
846
transform._new_path = {}
851
def test_iter_changes_modified_bleed(self):
852
self.wt.set_root_id('eert_toor')
853
"""Modified flag should not bleed from one change to another"""
854
# unfortunately, we have no guarantee that file1 (which is modified)
855
# will be applied before file2. And if it's applied after file2, it
856
# obviously can't bleed into file2's change output. But for now, it
858
transform, root = self.get_transform()
859
transform.new_file('file1', root, 'blah', 'id-1')
860
transform.new_file('file2', root, 'blah', 'id-2')
862
transform, root = self.get_transform()
864
transform.delete_contents(transform.trans_id_file_id('id-1'))
865
transform.set_executability(True,
866
transform.trans_id_file_id('id-2'))
867
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
868
('eert_toor', 'eert_toor'), ('file1', u'file1'),
869
('file', None), (False, False)),
870
('id-2', (u'file2', u'file2'), False, (True, True),
871
('eert_toor', 'eert_toor'), ('file2', u'file2'),
872
('file', 'file'), (False, True))],
873
list(transform._iter_changes()))
877
def test_iter_changes_move_missing(self):
878
"""Test moving ids with no files around"""
879
self.wt.set_root_id('toor_eert')
880
# Need two steps because versioning a non-existant file is a conflict.
881
transform, root = self.get_transform()
882
transform.new_directory('floater', root, 'floater-id')
884
transform, root = self.get_transform()
885
transform.delete_contents(transform.trans_id_tree_path('floater'))
887
transform, root = self.get_transform()
888
floater = transform.trans_id_tree_path('floater')
890
transform.adjust_path('flitter', root, floater)
891
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
892
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
893
(None, None), (False, False))], list(transform._iter_changes()))
897
def test_iter_changes_pointless(self):
898
"""Ensure that no-ops are not treated as modifications"""
899
self.wt.set_root_id('eert_toor')
900
transform, root = self.get_transform()
901
transform.new_file('old', root, 'blah', 'id-1')
902
transform.new_directory('subdir', root, 'subdir-id')
904
transform, root = self.get_transform()
906
old = transform.trans_id_tree_path('old')
907
subdir = transform.trans_id_tree_file_id('subdir-id')
908
self.assertEqual([], list(transform._iter_changes()))
909
transform.delete_contents(subdir)
910
transform.create_directory(subdir)
911
transform.set_executability(False, old)
912
transform.unversion_file(old)
913
transform.version_file('id-1', old)
914
transform.adjust_path('old', root, old)
915
self.assertEqual([], list(transform._iter_changes()))
919
def test_rename_count(self):
920
transform, root = self.get_transform()
921
transform.new_file('name1', root, 'contents')
922
self.assertEqual(transform.rename_count, 0)
924
self.assertEqual(transform.rename_count, 1)
925
transform2, root = self.get_transform()
926
transform2.adjust_path('name2', root,
927
transform2.trans_id_tree_path('name1'))
928
self.assertEqual(transform2.rename_count, 0)
930
self.assertEqual(transform2.rename_count, 2)
932
def test_change_parent(self):
933
"""Ensure that after we change a parent, the results are still right.
935
Renames and parent changes on pending transforms can happen as part
936
of conflict resolution, and are explicitly permitted by the
939
This test ensures they work correctly with the rename-avoidance
942
transform, root = self.get_transform()
943
parent1 = transform.new_directory('parent1', root)
944
child1 = transform.new_file('child1', parent1, 'contents')
945
parent2 = transform.new_directory('parent2', root)
946
transform.adjust_path('child1', parent2, child1)
948
self.failIfExists(self.wt.abspath('parent1/child1'))
949
self.failUnlessExists(self.wt.abspath('parent2/child1'))
950
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
951
# no rename for child1 (counting only renames during apply)
952
self.failUnlessEqual(2, transform.rename_count)
954
def test_cancel_parent(self):
955
"""Cancelling a parent doesn't cause deletion of a non-empty directory
957
This is like the test_change_parent, except that we cancel the parent
958
before adjusting the path. The transform must detect that the
959
directory is non-empty, and move children to safe locations.
961
transform, root = self.get_transform()
962
parent1 = transform.new_directory('parent1', root)
963
child1 = transform.new_file('child1', parent1, 'contents')
964
child2 = transform.new_file('child2', parent1, 'contents')
966
transform.cancel_creation(parent1)
968
self.fail('Failed to move child1 before deleting parent1')
969
transform.cancel_creation(child2)
970
transform.create_directory(parent1)
972
transform.cancel_creation(parent1)
973
# If the transform incorrectly believes that child2 is still in
974
# parent1's limbo directory, it will try to rename it and fail
975
# because was already moved by the first cancel_creation.
977
self.fail('Transform still thinks child2 is a child of parent1')
978
parent2 = transform.new_directory('parent2', root)
979
transform.adjust_path('child1', parent2, child1)
981
self.failIfExists(self.wt.abspath('parent1'))
982
self.failUnlessExists(self.wt.abspath('parent2/child1'))
983
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
984
self.failUnlessEqual(2, transform.rename_count)
986
def test_adjust_and_cancel(self):
987
"""Make sure adjust_path keeps track of limbo children properly"""
988
transform, root = self.get_transform()
989
parent1 = transform.new_directory('parent1', root)
990
child1 = transform.new_file('child1', parent1, 'contents')
991
parent2 = transform.new_directory('parent2', root)
992
transform.adjust_path('child1', parent2, child1)
993
transform.cancel_creation(child1)
995
transform.cancel_creation(parent1)
996
# if the transform thinks child1 is still in parent1's limbo
997
# directory, it will attempt to move it and fail.
999
self.fail('Transform still thinks child1 is a child of parent1')
1000
transform.finalize()
1002
def test_noname_contents(self):
1003
"""TreeTransform should permit deferring naming files."""
1004
transform, root = self.get_transform()
1005
parent = transform.trans_id_file_id('parent-id')
1007
transform.create_directory(parent)
1009
self.fail("Can't handle contents with no name")
1010
transform.finalize()
1012
def test_noname_contents_nested(self):
1013
"""TreeTransform should permit deferring naming files."""
1014
transform, root = self.get_transform()
1015
parent = transform.trans_id_file_id('parent-id')
1017
transform.create_directory(parent)
1019
self.fail("Can't handle contents with no name")
1020
child = transform.new_directory('child', parent)
1021
transform.adjust_path('parent', root, parent)
1023
self.failUnlessExists(self.wt.abspath('parent/child'))
1024
self.assertEqual(1, transform.rename_count)
1026
def test_reuse_name(self):
1027
"""Avoid reusing the same limbo name for different files"""
1028
transform, root = self.get_transform()
1029
parent = transform.new_directory('parent', root)
1030
child1 = transform.new_directory('child', parent)
1032
child2 = transform.new_directory('child', parent)
1034
self.fail('Tranform tried to use the same limbo name twice')
1035
transform.adjust_path('child2', parent, child2)
1037
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1038
# child2 is put into top-level limbo because child1 has already
1039
# claimed the direct limbo path when child2 is created. There is no
1040
# advantage in renaming files once they're in top-level limbo, except
1042
self.assertEqual(2, transform.rename_count)
1044
def test_reuse_when_first_moved(self):
1045
"""Don't avoid direct paths when it is safe to use them"""
1046
transform, root = self.get_transform()
1047
parent = transform.new_directory('parent', root)
1048
child1 = transform.new_directory('child', parent)
1049
transform.adjust_path('child1', parent, child1)
1050
child2 = transform.new_directory('child', parent)
1052
# limbo/new-1 => parent
1053
self.assertEqual(1, transform.rename_count)
1055
def test_reuse_after_cancel(self):
1056
"""Don't avoid direct paths when it is safe to use them"""
1057
transform, root = self.get_transform()
1058
parent2 = transform.new_directory('parent2', root)
1059
child1 = transform.new_directory('child1', parent2)
1060
transform.cancel_creation(parent2)
1061
transform.create_directory(parent2)
1062
child2 = transform.new_directory('child1', parent2)
1063
transform.adjust_path('child2', parent2, child1)
1065
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1066
self.assertEqual(2, transform.rename_count)
1068
def test_finalize_order(self):
1069
"""Finalize must be done in child-to-parent order"""
1070
transform, root = self.get_transform()
1071
parent = transform.new_directory('parent', root)
1072
child = transform.new_directory('child', parent)
1074
transform.finalize()
1076
self.fail('Tried to remove parent before child1')
1078
def test_cancel_with_cancelled_child_should_succeed(self):
1079
transform, root = self.get_transform()
1080
parent = transform.new_directory('parent', root)
1081
child = transform.new_directory('child', parent)
1082
transform.cancel_creation(child)
1083
transform.cancel_creation(parent)
1084
transform.finalize()
1086
def test_change_entry(self):
1087
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1088
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1092
class TransformGroup(object):
1093
def __init__(self, dirname, root_id):
1096
self.wt = BzrDir.create_standalone_workingtree(dirname)
1097
self.wt.set_root_id(root_id)
1098
self.b = self.wt.branch
1099
self.tt = TreeTransform(self.wt)
1100
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1103
def conflict_text(tree, merge):
1104
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1105
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1108
class TestTransformMerge(TestCaseInTempDir):
1109
def test_text_merge(self):
1110
root_id = generate_ids.gen_root_id()
1111
base = TransformGroup("base", root_id)
1112
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1113
base.tt.new_file('b', base.root, 'b1', 'b')
1114
base.tt.new_file('c', base.root, 'c', 'c')
1115
base.tt.new_file('d', base.root, 'd', 'd')
1116
base.tt.new_file('e', base.root, 'e', 'e')
1117
base.tt.new_file('f', base.root, 'f', 'f')
1118
base.tt.new_directory('g', base.root, 'g')
1119
base.tt.new_directory('h', base.root, 'h')
1121
other = TransformGroup("other", root_id)
1122
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1123
other.tt.new_file('b', other.root, 'b2', 'b')
1124
other.tt.new_file('c', other.root, 'c2', 'c')
1125
other.tt.new_file('d', other.root, 'd', 'd')
1126
other.tt.new_file('e', other.root, 'e2', 'e')
1127
other.tt.new_file('f', other.root, 'f', 'f')
1128
other.tt.new_file('g', other.root, 'g', 'g')
1129
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1130
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1132
this = TransformGroup("this", root_id)
1133
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1134
this.tt.new_file('b', this.root, 'b', 'b')
1135
this.tt.new_file('c', this.root, 'c', 'c')
1136
this.tt.new_file('d', this.root, 'd2', 'd')
1137
this.tt.new_file('e', this.root, 'e2', 'e')
1138
this.tt.new_file('f', this.root, 'f', 'f')
1139
this.tt.new_file('g', this.root, 'g', 'g')
1140
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1141
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1143
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1145
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1146
# three-way text conflict
1147
self.assertEqual(this.wt.get_file('b').read(),
1148
conflict_text('b', 'b2'))
1150
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1152
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1153
# Ambigious clean merge
1154
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1156
self.assertEqual(this.wt.get_file('f').read(), 'f')
1157
# Correct correct results when THIS == OTHER
1158
self.assertEqual(this.wt.get_file('g').read(), 'g')
1159
# Text conflict when THIS & OTHER are text and BASE is dir
1160
self.assertEqual(this.wt.get_file('h').read(),
1161
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1162
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1164
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1166
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1167
self.assertEqual(this.wt.get_file('i').read(),
1168
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1169
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1171
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1173
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1174
modified = ['a', 'b', 'c', 'h', 'i']
1175
merge_modified = this.wt.merge_modified()
1176
self.assertSubset(merge_modified, modified)
1177
self.assertEqual(len(merge_modified), len(modified))
1178
file(this.wt.id2abspath('a'), 'wb').write('booga')
1180
merge_modified = this.wt.merge_modified()
1181
self.assertSubset(merge_modified, modified)
1182
self.assertEqual(len(merge_modified), len(modified))
1186
def test_file_merge(self):
1187
self.requireFeature(SymlinkFeature)
1188
root_id = generate_ids.gen_root_id()
1189
base = TransformGroup("BASE", root_id)
1190
this = TransformGroup("THIS", root_id)
1191
other = TransformGroup("OTHER", root_id)
1192
for tg in this, base, other:
1193
tg.tt.new_directory('a', tg.root, 'a')
1194
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1195
tg.tt.new_file('c', tg.root, 'c', 'c')
1196
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1197
targets = ((base, 'base-e', 'base-f', None, None),
1198
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1199
(other, 'other-e', None, 'other-g', 'other-h'))
1200
for tg, e_target, f_target, g_target, h_target in targets:
1201
for link, target in (('e', e_target), ('f', f_target),
1202
('g', g_target), ('h', h_target)):
1203
if target is not None:
1204
tg.tt.new_symlink(link, tg.root, target, link)
1206
for tg in this, base, other:
1208
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1209
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1210
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1211
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1212
for suffix in ('THIS', 'BASE', 'OTHER'):
1213
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1214
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1215
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1216
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1217
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1218
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1219
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1220
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1221
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1222
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1223
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1224
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1225
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1226
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1228
def test_filename_merge(self):
1229
root_id = generate_ids.gen_root_id()
1230
base = TransformGroup("BASE", root_id)
1231
this = TransformGroup("THIS", root_id)
1232
other = TransformGroup("OTHER", root_id)
1233
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1234
for t in [base, this, other]]
1235
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1236
for t in [base, this, other]]
1237
base.tt.new_directory('c', base_a, 'c')
1238
this.tt.new_directory('c1', this_a, 'c')
1239
other.tt.new_directory('c', other_b, 'c')
1241
base.tt.new_directory('d', base_a, 'd')
1242
this.tt.new_directory('d1', this_b, 'd')
1243
other.tt.new_directory('d', other_a, 'd')
1245
base.tt.new_directory('e', base_a, 'e')
1246
this.tt.new_directory('e', this_a, 'e')
1247
other.tt.new_directory('e1', other_b, 'e')
1249
base.tt.new_directory('f', base_a, 'f')
1250
this.tt.new_directory('f1', this_b, 'f')
1251
other.tt.new_directory('f1', other_b, 'f')
1253
for tg in [this, base, other]:
1255
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1256
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1257
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1258
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1259
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1261
def test_filename_merge_conflicts(self):
1262
root_id = generate_ids.gen_root_id()
1263
base = TransformGroup("BASE", root_id)
1264
this = TransformGroup("THIS", root_id)
1265
other = TransformGroup("OTHER", root_id)
1266
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1267
for t in [base, this, other]]
1268
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1269
for t in [base, this, other]]
1271
base.tt.new_file('g', base_a, 'g', 'g')
1272
other.tt.new_file('g1', other_b, 'g1', 'g')
1274
base.tt.new_file('h', base_a, 'h', 'h')
1275
this.tt.new_file('h1', this_b, 'h1', 'h')
1277
base.tt.new_file('i', base.root, 'i', 'i')
1278
other.tt.new_directory('i1', this_b, 'i')
1280
for tg in [this, base, other]:
1282
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1284
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1285
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1286
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1287
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1288
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1289
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1290
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1293
class TestBuildTree(tests.TestCaseWithTransport):
1295
def test_build_tree_with_symlinks(self):
1296
self.requireFeature(SymlinkFeature)
1298
a = BzrDir.create_standalone_workingtree('a')
1300
file('a/foo/bar', 'wb').write('contents')
1301
os.symlink('a/foo/bar', 'a/foo/baz')
1302
a.add(['foo', 'foo/bar', 'foo/baz'])
1303
a.commit('initial commit')
1304
b = BzrDir.create_standalone_workingtree('b')
1305
basis = a.basis_tree()
1307
self.addCleanup(basis.unlock)
1308
build_tree(basis, b)
1309
self.assertIs(os.path.isdir('b/foo'), True)
1310
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1311
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1313
def test_build_with_references(self):
1314
tree = self.make_branch_and_tree('source',
1315
format='dirstate-with-subtree')
1316
subtree = self.make_branch_and_tree('source/subtree',
1317
format='dirstate-with-subtree')
1318
tree.add_reference(subtree)
1319
tree.commit('a revision')
1320
tree.branch.create_checkout('target')
1321
self.failUnlessExists('target')
1322
self.failUnlessExists('target/subtree')
1324
def test_file_conflict_handling(self):
1325
"""Ensure that when building trees, conflict handling is done"""
1326
source = self.make_branch_and_tree('source')
1327
target = self.make_branch_and_tree('target')
1328
self.build_tree(['source/file', 'target/file'])
1329
source.add('file', 'new-file')
1330
source.commit('added file')
1331
build_tree(source.basis_tree(), target)
1332
self.assertEqual([DuplicateEntry('Moved existing file to',
1333
'file.moved', 'file', None, 'new-file')],
1335
target2 = self.make_branch_and_tree('target2')
1336
target_file = file('target2/file', 'wb')
1338
source_file = file('source/file', 'rb')
1340
target_file.write(source_file.read())
1345
build_tree(source.basis_tree(), target2)
1346
self.assertEqual([], target2.conflicts())
1348
def test_symlink_conflict_handling(self):
1349
"""Ensure that when building trees, conflict handling is done"""
1350
self.requireFeature(SymlinkFeature)
1351
source = self.make_branch_and_tree('source')
1352
os.symlink('foo', 'source/symlink')
1353
source.add('symlink', 'new-symlink')
1354
source.commit('added file')
1355
target = self.make_branch_and_tree('target')
1356
os.symlink('bar', 'target/symlink')
1357
build_tree(source.basis_tree(), target)
1358
self.assertEqual([DuplicateEntry('Moved existing file to',
1359
'symlink.moved', 'symlink', None, 'new-symlink')],
1361
target = self.make_branch_and_tree('target2')
1362
os.symlink('foo', 'target2/symlink')
1363
build_tree(source.basis_tree(), target)
1364
self.assertEqual([], target.conflicts())
1366
def test_directory_conflict_handling(self):
1367
"""Ensure that when building trees, conflict handling is done"""
1368
source = self.make_branch_and_tree('source')
1369
target = self.make_branch_and_tree('target')
1370
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1371
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1372
source.commit('added file')
1373
build_tree(source.basis_tree(), target)
1374
self.assertEqual([], target.conflicts())
1375
self.failUnlessExists('target/dir1/file')
1377
# Ensure contents are merged
1378
target = self.make_branch_and_tree('target2')
1379
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1380
build_tree(source.basis_tree(), target)
1381
self.assertEqual([], target.conflicts())
1382
self.failUnlessExists('target2/dir1/file2')
1383
self.failUnlessExists('target2/dir1/file')
1385
# Ensure new contents are suppressed for existing branches
1386
target = self.make_branch_and_tree('target3')
1387
self.make_branch('target3/dir1')
1388
self.build_tree(['target3/dir1/file2'])
1389
build_tree(source.basis_tree(), target)
1390
self.failIfExists('target3/dir1/file')
1391
self.failUnlessExists('target3/dir1/file2')
1392
self.failUnlessExists('target3/dir1.diverted/file')
1393
self.assertEqual([DuplicateEntry('Diverted to',
1394
'dir1.diverted', 'dir1', 'new-dir1', None)],
1397
target = self.make_branch_and_tree('target4')
1398
self.build_tree(['target4/dir1/'])
1399
self.make_branch('target4/dir1/file')
1400
build_tree(source.basis_tree(), target)
1401
self.failUnlessExists('target4/dir1/file')
1402
self.assertEqual('directory', file_kind('target4/dir1/file'))
1403
self.failUnlessExists('target4/dir1/file.diverted')
1404
self.assertEqual([DuplicateEntry('Diverted to',
1405
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1408
def test_mixed_conflict_handling(self):
1409
"""Ensure that when building trees, conflict handling is done"""
1410
source = self.make_branch_and_tree('source')
1411
target = self.make_branch_and_tree('target')
1412
self.build_tree(['source/name', 'target/name/'])
1413
source.add('name', 'new-name')
1414
source.commit('added file')
1415
build_tree(source.basis_tree(), target)
1416
self.assertEqual([DuplicateEntry('Moved existing file to',
1417
'name.moved', 'name', None, 'new-name')], target.conflicts())
1419
def test_raises_in_populated(self):
1420
source = self.make_branch_and_tree('source')
1421
self.build_tree(['source/name'])
1423
source.commit('added name')
1424
target = self.make_branch_and_tree('target')
1425
self.build_tree(['target/name'])
1427
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1428
build_tree, source.basis_tree(), target)
1430
def test_build_tree_rename_count(self):
1431
source = self.make_branch_and_tree('source')
1432
self.build_tree(['source/file1', 'source/dir1/'])
1433
source.add(['file1', 'dir1'])
1434
source.commit('add1')
1435
target1 = self.make_branch_and_tree('target1')
1436
transform_result = build_tree(source.basis_tree(), target1)
1437
self.assertEqual(2, transform_result.rename_count)
1439
self.build_tree(['source/dir1/file2'])
1440
source.add(['dir1/file2'])
1441
source.commit('add3')
1442
target2 = self.make_branch_and_tree('target2')
1443
transform_result = build_tree(source.basis_tree(), target2)
1444
# children of non-root directories should not be renamed
1445
self.assertEqual(2, transform_result.rename_count)
1448
class MockTransform(object):
1450
def has_named_child(self, by_parent, parent_id, name):
1451
for child_id in by_parent[parent_id]:
1455
elif name == "name.~%s~" % child_id:
1460
class MockEntry(object):
1462
object.__init__(self)
1465
class TestGetBackupName(TestCase):
1466
def test_get_backup_name(self):
1467
tt = MockTransform()
1468
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1469
self.assertEqual(name, 'name.~1~')
1470
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1471
self.assertEqual(name, 'name.~2~')
1472
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1473
self.assertEqual(name, 'name.~1~')
1474
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1475
self.assertEqual(name, 'name.~1~')
1476
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1477
self.assertEqual(name, 'name.~4~')
1480
class TestFileMover(tests.TestCaseWithTransport):
1482
def test_file_mover(self):
1483
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1484
mover = _FileMover()
1485
mover.rename('a', 'q')
1486
self.failUnlessExists('q')
1487
self.failIfExists('a')
1488
self.failUnlessExists('q/b')
1489
self.failUnlessExists('c')
1490
self.failUnlessExists('c/d')
1492
def test_pre_delete_rollback(self):
1493
self.build_tree(['a/'])
1494
mover = _FileMover()
1495
mover.pre_delete('a', 'q')
1496
self.failUnlessExists('q')
1497
self.failIfExists('a')
1499
self.failIfExists('q')
1500
self.failUnlessExists('a')
1502
def test_apply_deletions(self):
1503
self.build_tree(['a/', 'b/'])
1504
mover = _FileMover()
1505
mover.pre_delete('a', 'q')
1506
mover.pre_delete('b', 'r')
1507
self.failUnlessExists('q')
1508
self.failUnlessExists('r')
1509
self.failIfExists('a')
1510
self.failIfExists('b')
1511
mover.apply_deletions()
1512
self.failIfExists('q')
1513
self.failIfExists('r')
1514
self.failIfExists('a')
1515
self.failIfExists('b')
1517
def test_file_mover_rollback(self):
1518
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1519
mover = _FileMover()
1520
mover.rename('c/d', 'c/f')
1521
mover.rename('c/e', 'c/d')
1523
mover.rename('a', 'c')
1526
self.failUnlessExists('a')
1527
self.failUnlessExists('c/d')
1530
class Bogus(Exception):
1534
class TestTransformRollback(tests.TestCaseWithTransport):
1536
class ExceptionFileMover(_FileMover):
1538
def __init__(self, bad_source=None, bad_target=None):
1539
_FileMover.__init__(self)
1540
self.bad_source = bad_source
1541
self.bad_target = bad_target
1543
def rename(self, source, target):
1544
if (self.bad_source is not None and
1545
source.endswith(self.bad_source)):
1547
elif (self.bad_target is not None and
1548
target.endswith(self.bad_target)):
1551
_FileMover.rename(self, source, target)
1553
def test_rollback_rename(self):
1554
tree = self.make_branch_and_tree('.')
1555
self.build_tree(['a/', 'a/b'])
1556
tt = TreeTransform(tree)
1557
self.addCleanup(tt.finalize)
1558
a_id = tt.trans_id_tree_path('a')
1559
tt.adjust_path('c', tt.root, a_id)
1560
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1561
self.assertRaises(Bogus, tt.apply,
1562
_mover=self.ExceptionFileMover(bad_source='a'))
1563
self.failUnlessExists('a')
1564
self.failUnlessExists('a/b')
1566
self.failUnlessExists('c')
1567
self.failUnlessExists('c/d')
1569
def test_rollback_rename_into_place(self):
1570
tree = self.make_branch_and_tree('.')
1571
self.build_tree(['a/', 'a/b'])
1572
tt = TreeTransform(tree)
1573
self.addCleanup(tt.finalize)
1574
a_id = tt.trans_id_tree_path('a')
1575
tt.adjust_path('c', tt.root, a_id)
1576
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1577
self.assertRaises(Bogus, tt.apply,
1578
_mover=self.ExceptionFileMover(bad_target='c/d'))
1579
self.failUnlessExists('a')
1580
self.failUnlessExists('a/b')
1582
self.failUnlessExists('c')
1583
self.failUnlessExists('c/d')
1585
def test_rollback_deletion(self):
1586
tree = self.make_branch_and_tree('.')
1587
self.build_tree(['a/', 'a/b'])
1588
tt = TreeTransform(tree)
1589
self.addCleanup(tt.finalize)
1590
a_id = tt.trans_id_tree_path('a')
1591
tt.delete_contents(a_id)
1592
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1593
self.assertRaises(Bogus, tt.apply,
1594
_mover=self.ExceptionFileMover(bad_target='d'))
1595
self.failUnlessExists('a')
1596
self.failUnlessExists('a/b')
1598
def test_resolve_no_parent(self):
1599
wt = self.make_branch_and_tree('.')
1600
tt = TreeTransform(wt)
1601
self.addCleanup(tt.finalize)
1602
parent = tt.trans_id_file_id('parent-id')
1603
tt.new_file('file', parent, 'Contents')
1604
resolve_conflicts(tt)