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.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
89
imaginary_id = transform.trans_id_tree_path('imaginary')
90
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
91
self.assertEqual(imaginary_id, imaginary_id2)
92
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
93
self.assertEqual(transform.final_kind(root), 'directory')
94
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
95
trans_id = transform.create_path('name', root)
96
self.assertIs(transform.final_file_id(trans_id), None)
97
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
98
transform.create_file('contents', trans_id)
99
transform.set_executability(True, trans_id)
100
transform.version_file('my_pretties', trans_id)
101
self.assertRaises(DuplicateKey, transform.version_file,
102
'my_pretties', trans_id)
103
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
104
self.assertEqual(transform.final_parent(trans_id), root)
105
self.assertIs(transform.final_parent(root), ROOT_PARENT)
106
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
107
oz_id = transform.create_path('oz', root)
108
transform.create_directory(oz_id)
109
transform.version_file('ozzie', oz_id)
110
trans_id2 = transform.create_path('name2', root)
111
transform.create_file('contents', trans_id2)
112
transform.set_executability(False, trans_id2)
113
transform.version_file('my_pretties2', trans_id2)
114
modified_paths = transform.apply().modified_paths
115
self.assertEqual('contents', self.wt.get_file_byname('name').read())
116
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
117
self.assertIs(self.wt.is_executable('my_pretties'), True)
118
self.assertIs(self.wt.is_executable('my_pretties2'), False)
119
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
120
self.assertEqual(len(modified_paths), 3)
121
tree_mod_paths = [self.wt.id2abspath(f) for f in
122
('ozzie', 'my_pretties', 'my_pretties2')]
123
self.assertSubset(tree_mod_paths, modified_paths)
124
# is it safe to finalize repeatedly?
128
def test_convenience(self):
129
transform, root = self.get_transform()
130
trans_id = transform.new_file('name', root, 'contents',
132
oz = transform.new_directory('oz', root, 'oz-id')
133
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
134
toto = transform.new_file('toto', dorothy, 'toto-contents',
137
self.assertEqual(len(transform.find_conflicts()), 0)
139
self.assertRaises(ReusingTransform, transform.find_conflicts)
140
self.assertEqual('contents', file(self.wt.abspath('name')).read())
141
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
142
self.assertIs(self.wt.is_executable('my_pretties'), True)
143
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
144
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
145
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
147
self.assertEqual('toto-contents',
148
self.wt.get_file_byname('oz/dorothy/toto').read())
149
self.assertIs(self.wt.is_executable('toto-id'), False)
151
def test_tree_reference(self):
152
transform, root = self.get_transform()
153
tree = transform._tree
154
trans_id = transform.new_directory('reference', root, 'subtree-id')
155
transform.set_tree_reference('subtree-revision', trans_id)
158
self.addCleanup(tree.unlock)
159
self.assertEqual('subtree-revision',
160
tree.inventory['subtree-id'].reference_revision)
162
def test_conflicts(self):
163
transform, root = self.get_transform()
164
trans_id = transform.new_file('name', root, 'contents',
166
self.assertEqual(len(transform.find_conflicts()), 0)
167
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
168
self.assertEqual(transform.find_conflicts(),
169
[('duplicate', trans_id, trans_id2, 'name')])
170
self.assertRaises(MalformedTransform, transform.apply)
171
transform.adjust_path('name', trans_id, trans_id2)
172
self.assertEqual(transform.find_conflicts(),
173
[('non-directory parent', trans_id)])
174
tinman_id = transform.trans_id_tree_path('tinman')
175
transform.adjust_path('name', tinman_id, trans_id2)
176
self.assertEqual(transform.find_conflicts(),
177
[('unversioned parent', tinman_id),
178
('missing parent', tinman_id)])
179
lion_id = transform.create_path('lion', root)
180
self.assertEqual(transform.find_conflicts(),
181
[('unversioned parent', tinman_id),
182
('missing parent', tinman_id)])
183
transform.adjust_path('name', lion_id, trans_id2)
184
self.assertEqual(transform.find_conflicts(),
185
[('unversioned parent', lion_id),
186
('missing parent', lion_id)])
187
transform.version_file("Courage", lion_id)
188
self.assertEqual(transform.find_conflicts(),
189
[('missing parent', lion_id),
190
('versioning no contents', lion_id)])
191
transform.adjust_path('name2', root, trans_id2)
192
self.assertEqual(transform.find_conflicts(),
193
[('versioning no contents', lion_id)])
194
transform.create_file('Contents, okay?', lion_id)
195
transform.adjust_path('name2', trans_id2, trans_id2)
196
self.assertEqual(transform.find_conflicts(),
197
[('parent loop', trans_id2),
198
('non-directory parent', trans_id2)])
199
transform.adjust_path('name2', root, trans_id2)
200
oz_id = transform.new_directory('oz', root)
201
transform.set_executability(True, oz_id)
202
self.assertEqual(transform.find_conflicts(),
203
[('unversioned executability', oz_id)])
204
transform.version_file('oz-id', oz_id)
205
self.assertEqual(transform.find_conflicts(),
206
[('non-file executability', oz_id)])
207
transform.set_executability(None, oz_id)
208
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
210
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
211
self.assertEqual('contents', file(self.wt.abspath('name')).read())
212
transform2, root = self.get_transform()
213
oz_id = transform2.trans_id_tree_file_id('oz-id')
214
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
215
result = transform2.find_conflicts()
216
fp = FinalPaths(transform2)
217
self.assert_('oz/tip' in transform2._tree_path_ids)
218
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
219
self.assertEqual(len(result), 2)
220
self.assertEqual((result[0][0], result[0][1]),
221
('duplicate', newtip))
222
self.assertEqual((result[1][0], result[1][2]),
223
('duplicate id', newtip))
224
transform2.finalize()
225
transform3 = TreeTransform(self.wt)
226
self.addCleanup(transform3.finalize)
227
oz_id = transform3.trans_id_tree_file_id('oz-id')
228
transform3.delete_contents(oz_id)
229
self.assertEqual(transform3.find_conflicts(),
230
[('missing parent', oz_id)])
231
root_id = transform3.root
232
tip_id = transform3.trans_id_tree_file_id('tip-id')
233
transform3.adjust_path('tip', root_id, tip_id)
236
def test_conflict_on_case_insensitive(self):
237
tree = self.make_branch_and_tree('tree')
238
# Don't try this at home, kids!
239
# Force the tree to report that it is case sensitive, for conflict
241
tree.case_sensitive = True
242
transform = TreeTransform(tree)
243
self.addCleanup(transform.finalize)
244
transform.new_file('file', transform.root, 'content')
245
transform.new_file('FiLe', transform.root, 'content')
246
result = transform.find_conflicts()
247
self.assertEqual([], result)
248
# Force the tree to report that it is case insensitive, for conflict
250
tree.case_sensitive = False
251
result = transform.find_conflicts()
252
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
254
def test_conflict_on_case_insensitive_existing(self):
255
tree = self.make_branch_and_tree('tree')
256
self.build_tree(['tree/FiLe'])
257
# Don't try this at home, kids!
258
# Force the tree to report that it is case sensitive, for conflict
260
tree.case_sensitive = True
261
transform = TreeTransform(tree)
262
self.addCleanup(transform.finalize)
263
transform.new_file('file', transform.root, 'content')
264
result = transform.find_conflicts()
265
self.assertEqual([], result)
266
# Force the tree to report that it is case insensitive, for conflict
268
tree.case_sensitive = False
269
result = transform.find_conflicts()
270
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
272
def test_resolve_case_insensitive_conflict(self):
273
tree = self.make_branch_and_tree('tree')
274
# Don't try this at home, kids!
275
# Force the tree to report that it is case insensitive, for conflict
277
tree.case_sensitive = False
278
transform = TreeTransform(tree)
279
self.addCleanup(transform.finalize)
280
transform.new_file('file', transform.root, 'content')
281
transform.new_file('FiLe', transform.root, 'content')
282
resolve_conflicts(transform)
284
self.failUnlessExists('tree/file')
285
self.failUnlessExists('tree/FiLe.moved')
287
def test_resolve_checkout_case_conflict(self):
288
tree = self.make_branch_and_tree('tree')
289
# Don't try this at home, kids!
290
# Force the tree to report that it is case insensitive, for conflict
292
tree.case_sensitive = False
293
transform = TreeTransform(tree)
294
self.addCleanup(transform.finalize)
295
transform.new_file('file', transform.root, 'content')
296
transform.new_file('FiLe', transform.root, 'content')
297
resolve_conflicts(transform,
298
pass_func=lambda t, c: resolve_checkout(t, c, []))
300
self.failUnlessExists('tree/file')
301
self.failUnlessExists('tree/FiLe.moved')
303
def test_apply_case_conflict(self):
304
"""Ensure that a transform with case conflicts can always be applied"""
305
tree = self.make_branch_and_tree('tree')
306
transform = TreeTransform(tree)
307
self.addCleanup(transform.finalize)
308
transform.new_file('file', transform.root, 'content')
309
transform.new_file('FiLe', transform.root, 'content')
310
dir = transform.new_directory('dir', transform.root)
311
transform.new_file('dirfile', dir, 'content')
312
transform.new_file('dirFiLe', dir, 'content')
313
resolve_conflicts(transform)
315
self.failUnlessExists('tree/file')
316
if not os.path.exists('tree/FiLe.moved'):
317
self.failUnlessExists('tree/FiLe')
318
self.failUnlessExists('tree/dir/dirfile')
319
if not os.path.exists('tree/dir/dirFiLe.moved'):
320
self.failUnlessExists('tree/dir/dirFiLe')
322
def test_case_insensitive_limbo(self):
323
tree = self.make_branch_and_tree('tree')
324
# Don't try this at home, kids!
325
# Force the tree to report that it is case insensitive
326
tree.case_sensitive = False
327
transform = TreeTransform(tree)
328
self.addCleanup(transform.finalize)
329
dir = transform.new_directory('dir', transform.root)
330
first = transform.new_file('file', dir, 'content')
331
second = transform.new_file('FiLe', dir, 'content')
332
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
333
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
335
def test_add_del(self):
336
start, root = self.get_transform()
337
start.new_directory('a', root, 'a')
339
transform, root = self.get_transform()
340
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
341
transform.new_directory('a', root, 'a')
344
def test_unversioning(self):
345
create_tree, root = self.get_transform()
346
parent_id = create_tree.new_directory('parent', root, 'parent-id')
347
create_tree.new_file('child', parent_id, 'child', 'child-id')
349
unversion = TreeTransform(self.wt)
350
self.addCleanup(unversion.finalize)
351
parent = unversion.trans_id_tree_path('parent')
352
unversion.unversion_file(parent)
353
self.assertEqual(unversion.find_conflicts(),
354
[('unversioned parent', parent_id)])
355
file_id = unversion.trans_id_tree_file_id('child-id')
356
unversion.unversion_file(file_id)
359
def test_name_invariants(self):
360
create_tree, root = self.get_transform()
362
root = create_tree.root
363
create_tree.new_file('name1', root, 'hello1', 'name1')
364
create_tree.new_file('name2', root, 'hello2', 'name2')
365
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
366
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
367
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
368
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
371
mangle_tree,root = self.get_transform()
372
root = mangle_tree.root
374
name1 = mangle_tree.trans_id_tree_file_id('name1')
375
name2 = mangle_tree.trans_id_tree_file_id('name2')
376
mangle_tree.adjust_path('name2', root, name1)
377
mangle_tree.adjust_path('name1', root, name2)
379
#tests for deleting parent directories
380
ddir = mangle_tree.trans_id_tree_file_id('ddir')
381
mangle_tree.delete_contents(ddir)
382
dfile = mangle_tree.trans_id_tree_file_id('dfile')
383
mangle_tree.delete_versioned(dfile)
384
mangle_tree.unversion_file(dfile)
385
mfile = mangle_tree.trans_id_tree_file_id('mfile')
386
mangle_tree.adjust_path('mfile', root, mfile)
388
#tests for adding parent directories
389
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
390
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
391
mangle_tree.adjust_path('mfile2', newdir, mfile2)
392
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
393
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
394
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
395
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
397
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
398
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
399
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
400
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
401
self.assertEqual(file(mfile2_path).read(), 'later2')
402
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
403
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
404
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
405
self.assertEqual(file(newfile_path).read(), 'hello3')
406
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
407
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
408
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
410
def test_both_rename(self):
411
create_tree,root = self.get_transform()
412
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
413
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
415
mangle_tree,root = self.get_transform()
416
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
417
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
418
mangle_tree.adjust_path('test', root, selftest)
419
mangle_tree.adjust_path('test_too_much', root, selftest)
420
mangle_tree.set_executability(True, blackbox)
423
def test_both_rename2(self):
424
create_tree,root = self.get_transform()
425
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
426
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
427
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
428
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
431
mangle_tree,root = self.get_transform()
432
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
433
tests = mangle_tree.trans_id_tree_file_id('tests-id')
434
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
435
mangle_tree.adjust_path('selftest', bzrlib, tests)
436
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
437
mangle_tree.set_executability(True, test_too_much)
440
def test_both_rename3(self):
441
create_tree,root = self.get_transform()
442
tests = create_tree.new_directory('tests', root, 'tests-id')
443
create_tree.new_file('test_too_much.py', tests, 'hello1',
446
mangle_tree,root = self.get_transform()
447
tests = mangle_tree.trans_id_tree_file_id('tests-id')
448
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
449
mangle_tree.adjust_path('selftest', root, tests)
450
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
451
mangle_tree.set_executability(True, test_too_much)
454
def test_move_dangling_ie(self):
455
create_tree, root = self.get_transform()
457
root = create_tree.root
458
create_tree.new_file('name1', root, 'hello1', 'name1')
460
delete_contents, root = self.get_transform()
461
file = delete_contents.trans_id_tree_file_id('name1')
462
delete_contents.delete_contents(file)
463
delete_contents.apply()
464
move_id, root = self.get_transform()
465
name1 = move_id.trans_id_tree_file_id('name1')
466
newdir = move_id.new_directory('dir', root, 'newdir')
467
move_id.adjust_path('name2', newdir, name1)
470
def test_replace_dangling_ie(self):
471
create_tree, root = self.get_transform()
473
root = create_tree.root
474
create_tree.new_file('name1', root, 'hello1', 'name1')
476
delete_contents = TreeTransform(self.wt)
477
self.addCleanup(delete_contents.finalize)
478
file = delete_contents.trans_id_tree_file_id('name1')
479
delete_contents.delete_contents(file)
480
delete_contents.apply()
481
delete_contents.finalize()
482
replace = TreeTransform(self.wt)
483
self.addCleanup(replace.finalize)
484
name2 = replace.new_file('name2', root, 'hello2', 'name1')
485
conflicts = replace.find_conflicts()
486
name1 = replace.trans_id_tree_file_id('name1')
487
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
488
resolve_conflicts(replace)
491
def test_symlinks(self):
492
self.requireFeature(SymlinkFeature)
493
transform,root = self.get_transform()
494
oz_id = transform.new_directory('oz', root, 'oz-id')
495
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
497
wiz_id = transform.create_path('wizard2', oz_id)
498
transform.create_symlink('behind_curtain', wiz_id)
499
transform.version_file('wiz-id2', wiz_id)
500
transform.set_executability(True, wiz_id)
501
self.assertEqual(transform.find_conflicts(),
502
[('non-file executability', wiz_id)])
503
transform.set_executability(None, wiz_id)
505
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
506
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
507
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
509
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
512
def test_unable_create_symlink(self):
514
wt = self.make_branch_and_tree('.')
515
tt = TreeTransform(wt) # TreeTransform obtains write lock
517
tt.new_symlink('foo', tt.root, 'bar')
521
os_symlink = getattr(os, 'symlink', None)
524
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
526
"Unable to create symlink 'foo' on this platform",
530
os.symlink = os_symlink
532
def get_conflicted(self):
533
create,root = self.get_transform()
534
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
535
oz = create.new_directory('oz', root, 'oz-id')
536
create.new_directory('emeraldcity', oz, 'emerald-id')
538
conflicts,root = self.get_transform()
539
# set up duplicate entry, duplicate id
540
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
542
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
543
oz = conflicts.trans_id_tree_file_id('oz-id')
544
# set up DeletedParent parent conflict
545
conflicts.delete_versioned(oz)
546
emerald = conflicts.trans_id_tree_file_id('emerald-id')
547
# set up MissingParent conflict
548
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
549
conflicts.adjust_path('munchkincity', root, munchkincity)
550
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
552
conflicts.adjust_path('emeraldcity', emerald, emerald)
553
return conflicts, emerald, oz, old_dorothy, new_dorothy
555
def test_conflict_resolution(self):
556
conflicts, emerald, oz, old_dorothy, new_dorothy =\
557
self.get_conflicted()
558
resolve_conflicts(conflicts)
559
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
560
self.assertIs(conflicts.final_file_id(old_dorothy), None)
561
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
562
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
563
self.assertEqual(conflicts.final_parent(emerald), oz)
566
def test_cook_conflicts(self):
567
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
568
raw_conflicts = resolve_conflicts(tt)
569
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
570
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
571
'dorothy', None, 'dorothy-id')
572
self.assertEqual(cooked_conflicts[0], duplicate)
573
duplicate_id = DuplicateID('Unversioned existing file',
574
'dorothy.moved', 'dorothy', None,
576
self.assertEqual(cooked_conflicts[1], duplicate_id)
577
missing_parent = MissingParent('Created directory', 'munchkincity',
579
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
580
self.assertEqual(cooked_conflicts[2], missing_parent)
581
unversioned_parent = UnversionedParent('Versioned directory',
584
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
586
self.assertEqual(cooked_conflicts[3], unversioned_parent)
587
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
588
'oz/emeraldcity', 'emerald-id', 'emerald-id')
589
self.assertEqual(cooked_conflicts[4], deleted_parent)
590
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
591
self.assertEqual(cooked_conflicts[6], parent_loop)
592
self.assertEqual(len(cooked_conflicts), 7)
595
def test_string_conflicts(self):
596
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
597
raw_conflicts = resolve_conflicts(tt)
598
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
600
conflicts_s = [str(c) for c in cooked_conflicts]
601
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
602
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
603
'Moved existing file to '
605
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
606
'Unversioned existing file '
608
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
609
' munchkincity. Created directory.')
610
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
611
' versioned, but has versioned'
612
' children. Versioned directory.')
613
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
614
" is not empty. Not deleting.")
615
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
616
' versioned, but has versioned'
617
' children. Versioned directory.')
618
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
619
' oz/emeraldcity. Cancelled move.')
621
def test_moving_versioned_directories(self):
622
create, root = self.get_transform()
623
kansas = create.new_directory('kansas', root, 'kansas-id')
624
create.new_directory('house', kansas, 'house-id')
625
create.new_directory('oz', root, 'oz-id')
627
cyclone, root = self.get_transform()
628
oz = cyclone.trans_id_tree_file_id('oz-id')
629
house = cyclone.trans_id_tree_file_id('house-id')
630
cyclone.adjust_path('house', oz, house)
633
def test_moving_root(self):
634
create, root = self.get_transform()
635
fun = create.new_directory('fun', root, 'fun-id')
636
create.new_directory('sun', root, 'sun-id')
637
create.new_directory('moon', root, 'moon')
639
transform, root = self.get_transform()
640
transform.adjust_root_path('oldroot', fun)
641
new_root=transform.trans_id_tree_path('')
642
transform.version_file('new-root', new_root)
645
def test_renames(self):
646
create, root = self.get_transform()
647
old = create.new_directory('old-parent', root, 'old-id')
648
intermediate = create.new_directory('intermediate', old, 'im-id')
649
myfile = create.new_file('myfile', intermediate, 'myfile-text',
652
rename, root = self.get_transform()
653
old = rename.trans_id_file_id('old-id')
654
rename.adjust_path('new', root, old)
655
myfile = rename.trans_id_file_id('myfile-id')
656
rename.set_executability(True, myfile)
659
def test_find_interesting(self):
660
create, root = self.get_transform()
662
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
663
create.new_file('uvfile', root, 'othertext')
665
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
666
find_interesting, wt, wt, ['vfile'])
667
self.assertEqual(result, set(['myfile-id']))
669
def test_set_executability_order(self):
670
"""Ensure that executability behaves the same, no matter what order.
672
- create file and set executability simultaneously
673
- create file and set executability afterward
674
- unsetting the executability of a file whose executability has not been
675
declared should throw an exception (this may happen when a
676
merge attempts to create a file with a duplicate ID)
678
transform, root = self.get_transform()
680
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
682
sac = transform.new_file('set_after_creation', root,
683
'Set after creation', 'sac')
684
transform.set_executability(True, sac)
685
uws = transform.new_file('unset_without_set', root, 'Unset badly',
687
self.assertRaises(KeyError, transform.set_executability, None, uws)
689
self.assertTrue(wt.is_executable('soc'))
690
self.assertTrue(wt.is_executable('sac'))
692
def test_preserve_mode(self):
693
"""File mode is preserved when replacing content"""
694
if sys.platform == 'win32':
695
raise TestSkipped('chmod has no effect on win32')
696
transform, root = self.get_transform()
697
transform.new_file('file1', root, 'contents', 'file1-id', True)
699
self.assertTrue(self.wt.is_executable('file1-id'))
700
transform, root = self.get_transform()
701
file1_id = transform.trans_id_tree_file_id('file1-id')
702
transform.delete_contents(file1_id)
703
transform.create_file('contents2', file1_id)
705
self.assertTrue(self.wt.is_executable('file1-id'))
707
def test__set_mode_stats_correctly(self):
708
"""_set_mode stats to determine file mode."""
709
if sys.platform == 'win32':
710
raise TestSkipped('chmod has no effect on win32')
714
def instrumented_stat(path):
715
stat_paths.append(path)
716
return real_stat(path)
718
transform, root = self.get_transform()
720
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
721
file_id='bar-id-1', executable=False)
724
transform, root = self.get_transform()
725
bar1_id = transform.trans_id_tree_path('bar')
726
bar2_id = transform.trans_id_tree_path('bar2')
728
os.stat = instrumented_stat
729
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
734
bar1_abspath = self.wt.abspath('bar')
735
self.assertEqual([bar1_abspath], stat_paths)
737
def test_iter_changes(self):
738
self.wt.set_root_id('eert_toor')
739
transform, root = self.get_transform()
740
transform.new_file('old', root, 'blah', 'id-1', True)
742
transform, root = self.get_transform()
744
self.assertEqual([], list(transform._iter_changes()))
745
old = transform.trans_id_tree_file_id('id-1')
746
transform.unversion_file(old)
747
self.assertEqual([('id-1', ('old', None), False, (True, False),
748
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
749
(True, True))], list(transform._iter_changes()))
750
transform.new_directory('new', root, 'id-1')
751
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
752
('eert_toor', 'eert_toor'), ('old', 'new'),
753
('file', 'directory'),
754
(True, False))], list(transform._iter_changes()))
758
def test_iter_changes_new(self):
759
self.wt.set_root_id('eert_toor')
760
transform, root = self.get_transform()
761
transform.new_file('old', root, 'blah')
763
transform, root = self.get_transform()
765
old = transform.trans_id_tree_path('old')
766
transform.version_file('id-1', old)
767
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
768
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
769
(False, False))], list(transform._iter_changes()))
773
def test_iter_changes_modifications(self):
774
self.wt.set_root_id('eert_toor')
775
transform, root = self.get_transform()
776
transform.new_file('old', root, 'blah', 'id-1')
777
transform.new_file('new', root, 'blah')
778
transform.new_directory('subdir', root, 'subdir-id')
780
transform, root = self.get_transform()
782
old = transform.trans_id_tree_path('old')
783
subdir = transform.trans_id_tree_file_id('subdir-id')
784
new = transform.trans_id_tree_path('new')
785
self.assertEqual([], list(transform._iter_changes()))
788
transform.delete_contents(old)
789
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
790
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
791
(False, False))], list(transform._iter_changes()))
794
transform.create_file('blah', old)
795
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
796
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
797
(False, False))], list(transform._iter_changes()))
798
transform.cancel_deletion(old)
799
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
800
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
801
(False, False))], list(transform._iter_changes()))
802
transform.cancel_creation(old)
804
# move file_id to a different file
805
self.assertEqual([], list(transform._iter_changes()))
806
transform.unversion_file(old)
807
transform.version_file('id-1', new)
808
transform.adjust_path('old', root, new)
809
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
810
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
811
(False, False))], list(transform._iter_changes()))
812
transform.cancel_versioning(new)
813
transform._removed_id = set()
816
self.assertEqual([], list(transform._iter_changes()))
817
transform.set_executability(True, old)
818
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
819
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
820
(False, True))], list(transform._iter_changes()))
821
transform.set_executability(None, old)
824
self.assertEqual([], list(transform._iter_changes()))
825
transform.adjust_path('new', root, old)
826
transform._new_parent = {}
827
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
828
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
829
(False, False))], list(transform._iter_changes()))
830
transform._new_name = {}
833
self.assertEqual([], list(transform._iter_changes()))
834
transform.adjust_path('new', subdir, old)
835
transform._new_name = {}
836
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
837
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
838
('file', 'file'), (False, False))],
839
list(transform._iter_changes()))
840
transform._new_path = {}
845
def test_iter_changes_modified_bleed(self):
846
self.wt.set_root_id('eert_toor')
847
"""Modified flag should not bleed from one change to another"""
848
# unfortunately, we have no guarantee that file1 (which is modified)
849
# will be applied before file2. And if it's applied after file2, it
850
# obviously can't bleed into file2's change output. But for now, it
852
transform, root = self.get_transform()
853
transform.new_file('file1', root, 'blah', 'id-1')
854
transform.new_file('file2', root, 'blah', 'id-2')
856
transform, root = self.get_transform()
858
transform.delete_contents(transform.trans_id_file_id('id-1'))
859
transform.set_executability(True,
860
transform.trans_id_file_id('id-2'))
861
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
862
('eert_toor', 'eert_toor'), ('file1', u'file1'),
863
('file', None), (False, False)),
864
('id-2', (u'file2', u'file2'), False, (True, True),
865
('eert_toor', 'eert_toor'), ('file2', u'file2'),
866
('file', 'file'), (False, True))],
867
list(transform._iter_changes()))
871
def test_iter_changes_move_missing(self):
872
"""Test moving ids with no files around"""
873
self.wt.set_root_id('toor_eert')
874
# Need two steps because versioning a non-existant file is a conflict.
875
transform, root = self.get_transform()
876
transform.new_directory('floater', root, 'floater-id')
878
transform, root = self.get_transform()
879
transform.delete_contents(transform.trans_id_tree_path('floater'))
881
transform, root = self.get_transform()
882
floater = transform.trans_id_tree_path('floater')
884
transform.adjust_path('flitter', root, floater)
885
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
886
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
887
(None, None), (False, False))], list(transform._iter_changes()))
891
def test_iter_changes_pointless(self):
892
"""Ensure that no-ops are not treated as modifications"""
893
self.wt.set_root_id('eert_toor')
894
transform, root = self.get_transform()
895
transform.new_file('old', root, 'blah', 'id-1')
896
transform.new_directory('subdir', root, 'subdir-id')
898
transform, root = self.get_transform()
900
old = transform.trans_id_tree_path('old')
901
subdir = transform.trans_id_tree_file_id('subdir-id')
902
self.assertEqual([], list(transform._iter_changes()))
903
transform.delete_contents(subdir)
904
transform.create_directory(subdir)
905
transform.set_executability(False, old)
906
transform.unversion_file(old)
907
transform.version_file('id-1', old)
908
transform.adjust_path('old', root, old)
909
self.assertEqual([], list(transform._iter_changes()))
913
def test_rename_count(self):
914
transform, root = self.get_transform()
915
transform.new_file('name1', root, 'contents')
916
self.assertEqual(transform.rename_count, 0)
918
self.assertEqual(transform.rename_count, 1)
919
transform2, root = self.get_transform()
920
transform2.adjust_path('name2', root,
921
transform2.trans_id_tree_path('name1'))
922
self.assertEqual(transform2.rename_count, 0)
924
self.assertEqual(transform2.rename_count, 2)
926
def test_change_parent(self):
927
"""Ensure that after we change a parent, the results are still right.
929
Renames and parent changes on pending transforms can happen as part
930
of conflict resolution, and are explicitly permitted by the
933
This test ensures they work correctly with the rename-avoidance
936
transform, root = self.get_transform()
937
parent1 = transform.new_directory('parent1', root)
938
child1 = transform.new_file('child1', parent1, 'contents')
939
parent2 = transform.new_directory('parent2', root)
940
transform.adjust_path('child1', parent2, child1)
942
self.failIfExists(self.wt.abspath('parent1/child1'))
943
self.failUnlessExists(self.wt.abspath('parent2/child1'))
944
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
945
# no rename for child1 (counting only renames during apply)
946
self.failUnlessEqual(2, transform.rename_count)
948
def test_cancel_parent(self):
949
"""Cancelling a parent doesn't cause deletion of a non-empty directory
951
This is like the test_change_parent, except that we cancel the parent
952
before adjusting the path. The transform must detect that the
953
directory is non-empty, and move children to safe locations.
955
transform, root = self.get_transform()
956
parent1 = transform.new_directory('parent1', root)
957
child1 = transform.new_file('child1', parent1, 'contents')
958
child2 = transform.new_file('child2', parent1, 'contents')
960
transform.cancel_creation(parent1)
962
self.fail('Failed to move child1 before deleting parent1')
963
transform.cancel_creation(child2)
964
transform.create_directory(parent1)
966
transform.cancel_creation(parent1)
967
# If the transform incorrectly believes that child2 is still in
968
# parent1's limbo directory, it will try to rename it and fail
969
# because was already moved by the first cancel_creation.
971
self.fail('Transform still thinks child2 is a child of parent1')
972
parent2 = transform.new_directory('parent2', root)
973
transform.adjust_path('child1', parent2, child1)
975
self.failIfExists(self.wt.abspath('parent1'))
976
self.failUnlessExists(self.wt.abspath('parent2/child1'))
977
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
978
self.failUnlessEqual(2, transform.rename_count)
980
def test_adjust_and_cancel(self):
981
"""Make sure adjust_path keeps track of limbo children properly"""
982
transform, root = self.get_transform()
983
parent1 = transform.new_directory('parent1', root)
984
child1 = transform.new_file('child1', parent1, 'contents')
985
parent2 = transform.new_directory('parent2', root)
986
transform.adjust_path('child1', parent2, child1)
987
transform.cancel_creation(child1)
989
transform.cancel_creation(parent1)
990
# if the transform thinks child1 is still in parent1's limbo
991
# directory, it will attempt to move it and fail.
993
self.fail('Transform still thinks child1 is a child of parent1')
996
def test_noname_contents(self):
997
"""TreeTransform should permit deferring naming files."""
998
transform, root = self.get_transform()
999
parent = transform.trans_id_file_id('parent-id')
1001
transform.create_directory(parent)
1003
self.fail("Can't handle contents with no name")
1004
transform.finalize()
1006
def test_noname_contents_nested(self):
1007
"""TreeTransform should permit deferring naming files."""
1008
transform, root = self.get_transform()
1009
parent = transform.trans_id_file_id('parent-id')
1011
transform.create_directory(parent)
1013
self.fail("Can't handle contents with no name")
1014
child = transform.new_directory('child', parent)
1015
transform.adjust_path('parent', root, parent)
1017
self.failUnlessExists(self.wt.abspath('parent/child'))
1018
self.assertEqual(1, transform.rename_count)
1020
def test_reuse_name(self):
1021
"""Avoid reusing the same limbo name for different files"""
1022
transform, root = self.get_transform()
1023
parent = transform.new_directory('parent', root)
1024
child1 = transform.new_directory('child', parent)
1026
child2 = transform.new_directory('child', parent)
1028
self.fail('Tranform tried to use the same limbo name twice')
1029
transform.adjust_path('child2', parent, child2)
1031
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1032
# child2 is put into top-level limbo because child1 has already
1033
# claimed the direct limbo path when child2 is created. There is no
1034
# advantage in renaming files once they're in top-level limbo, except
1036
self.assertEqual(2, transform.rename_count)
1038
def test_reuse_when_first_moved(self):
1039
"""Don't avoid direct paths when it is safe to use them"""
1040
transform, root = self.get_transform()
1041
parent = transform.new_directory('parent', root)
1042
child1 = transform.new_directory('child', parent)
1043
transform.adjust_path('child1', parent, child1)
1044
child2 = transform.new_directory('child', parent)
1046
# limbo/new-1 => parent
1047
self.assertEqual(1, transform.rename_count)
1049
def test_reuse_after_cancel(self):
1050
"""Don't avoid direct paths when it is safe to use them"""
1051
transform, root = self.get_transform()
1052
parent2 = transform.new_directory('parent2', root)
1053
child1 = transform.new_directory('child1', parent2)
1054
transform.cancel_creation(parent2)
1055
transform.create_directory(parent2)
1056
child2 = transform.new_directory('child1', parent2)
1057
transform.adjust_path('child2', parent2, child1)
1059
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1060
self.assertEqual(2, transform.rename_count)
1062
def test_finalize_order(self):
1063
"""Finalize must be done in child-to-parent order"""
1064
transform, root = self.get_transform()
1065
parent = transform.new_directory('parent', root)
1066
child = transform.new_directory('child', parent)
1068
transform.finalize()
1070
self.fail('Tried to remove parent before child1')
1072
def test_cancel_with_cancelled_child_should_succeed(self):
1073
transform, root = self.get_transform()
1074
parent = transform.new_directory('parent', root)
1075
child = transform.new_directory('child', parent)
1076
transform.cancel_creation(child)
1077
transform.cancel_creation(parent)
1078
transform.finalize()
1080
def test_change_entry(self):
1081
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1082
self.callDeprecated([txt], change_entry, None, None, None, None, None,
1086
class TransformGroup(object):
1087
def __init__(self, dirname, root_id):
1090
self.wt = BzrDir.create_standalone_workingtree(dirname)
1091
self.wt.set_root_id(root_id)
1092
self.b = self.wt.branch
1093
self.tt = TreeTransform(self.wt)
1094
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1097
def conflict_text(tree, merge):
1098
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1099
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1102
class TestTransformMerge(TestCaseInTempDir):
1103
def test_text_merge(self):
1104
root_id = generate_ids.gen_root_id()
1105
base = TransformGroup("base", root_id)
1106
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1107
base.tt.new_file('b', base.root, 'b1', 'b')
1108
base.tt.new_file('c', base.root, 'c', 'c')
1109
base.tt.new_file('d', base.root, 'd', 'd')
1110
base.tt.new_file('e', base.root, 'e', 'e')
1111
base.tt.new_file('f', base.root, 'f', 'f')
1112
base.tt.new_directory('g', base.root, 'g')
1113
base.tt.new_directory('h', base.root, 'h')
1115
other = TransformGroup("other", root_id)
1116
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1117
other.tt.new_file('b', other.root, 'b2', 'b')
1118
other.tt.new_file('c', other.root, 'c2', 'c')
1119
other.tt.new_file('d', other.root, 'd', 'd')
1120
other.tt.new_file('e', other.root, 'e2', 'e')
1121
other.tt.new_file('f', other.root, 'f', 'f')
1122
other.tt.new_file('g', other.root, 'g', 'g')
1123
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1124
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1126
this = TransformGroup("this", root_id)
1127
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1128
this.tt.new_file('b', this.root, 'b', 'b')
1129
this.tt.new_file('c', this.root, 'c', 'c')
1130
this.tt.new_file('d', this.root, 'd2', 'd')
1131
this.tt.new_file('e', this.root, 'e2', 'e')
1132
this.tt.new_file('f', this.root, 'f', 'f')
1133
this.tt.new_file('g', this.root, 'g', 'g')
1134
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1135
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1137
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1139
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1140
# three-way text conflict
1141
self.assertEqual(this.wt.get_file('b').read(),
1142
conflict_text('b', 'b2'))
1144
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1146
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1147
# Ambigious clean merge
1148
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1150
self.assertEqual(this.wt.get_file('f').read(), 'f')
1151
# Correct correct results when THIS == OTHER
1152
self.assertEqual(this.wt.get_file('g').read(), 'g')
1153
# Text conflict when THIS & OTHER are text and BASE is dir
1154
self.assertEqual(this.wt.get_file('h').read(),
1155
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1156
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1158
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1160
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1161
self.assertEqual(this.wt.get_file('i').read(),
1162
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1163
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1165
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1167
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1168
modified = ['a', 'b', 'c', 'h', 'i']
1169
merge_modified = this.wt.merge_modified()
1170
self.assertSubset(merge_modified, modified)
1171
self.assertEqual(len(merge_modified), len(modified))
1172
file(this.wt.id2abspath('a'), 'wb').write('booga')
1174
merge_modified = this.wt.merge_modified()
1175
self.assertSubset(merge_modified, modified)
1176
self.assertEqual(len(merge_modified), len(modified))
1180
def test_file_merge(self):
1181
self.requireFeature(SymlinkFeature)
1182
root_id = generate_ids.gen_root_id()
1183
base = TransformGroup("BASE", root_id)
1184
this = TransformGroup("THIS", root_id)
1185
other = TransformGroup("OTHER", root_id)
1186
for tg in this, base, other:
1187
tg.tt.new_directory('a', tg.root, 'a')
1188
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1189
tg.tt.new_file('c', tg.root, 'c', 'c')
1190
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1191
targets = ((base, 'base-e', 'base-f', None, None),
1192
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1193
(other, 'other-e', None, 'other-g', 'other-h'))
1194
for tg, e_target, f_target, g_target, h_target in targets:
1195
for link, target in (('e', e_target), ('f', f_target),
1196
('g', g_target), ('h', h_target)):
1197
if target is not None:
1198
tg.tt.new_symlink(link, tg.root, target, link)
1200
for tg in this, base, other:
1202
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1203
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1204
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1205
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1206
for suffix in ('THIS', 'BASE', 'OTHER'):
1207
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1208
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1209
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1210
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1211
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1212
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1213
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1214
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1215
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1216
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1217
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1218
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1219
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1220
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1222
def test_filename_merge(self):
1223
root_id = generate_ids.gen_root_id()
1224
base = TransformGroup("BASE", root_id)
1225
this = TransformGroup("THIS", root_id)
1226
other = TransformGroup("OTHER", root_id)
1227
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1228
for t in [base, this, other]]
1229
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1230
for t in [base, this, other]]
1231
base.tt.new_directory('c', base_a, 'c')
1232
this.tt.new_directory('c1', this_a, 'c')
1233
other.tt.new_directory('c', other_b, 'c')
1235
base.tt.new_directory('d', base_a, 'd')
1236
this.tt.new_directory('d1', this_b, 'd')
1237
other.tt.new_directory('d', other_a, 'd')
1239
base.tt.new_directory('e', base_a, 'e')
1240
this.tt.new_directory('e', this_a, 'e')
1241
other.tt.new_directory('e1', other_b, 'e')
1243
base.tt.new_directory('f', base_a, 'f')
1244
this.tt.new_directory('f1', this_b, 'f')
1245
other.tt.new_directory('f1', other_b, 'f')
1247
for tg in [this, base, other]:
1249
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1250
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1251
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1252
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1253
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1255
def test_filename_merge_conflicts(self):
1256
root_id = generate_ids.gen_root_id()
1257
base = TransformGroup("BASE", root_id)
1258
this = TransformGroup("THIS", root_id)
1259
other = TransformGroup("OTHER", root_id)
1260
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1261
for t in [base, this, other]]
1262
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1263
for t in [base, this, other]]
1265
base.tt.new_file('g', base_a, 'g', 'g')
1266
other.tt.new_file('g1', other_b, 'g1', 'g')
1268
base.tt.new_file('h', base_a, 'h', 'h')
1269
this.tt.new_file('h1', this_b, 'h1', 'h')
1271
base.tt.new_file('i', base.root, 'i', 'i')
1272
other.tt.new_directory('i1', this_b, 'i')
1274
for tg in [this, base, other]:
1276
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1278
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1279
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1280
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1281
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1282
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1283
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1284
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1287
class TestBuildTree(tests.TestCaseWithTransport):
1289
def test_build_tree_with_symlinks(self):
1290
self.requireFeature(SymlinkFeature)
1292
a = BzrDir.create_standalone_workingtree('a')
1294
file('a/foo/bar', 'wb').write('contents')
1295
os.symlink('a/foo/bar', 'a/foo/baz')
1296
a.add(['foo', 'foo/bar', 'foo/baz'])
1297
a.commit('initial commit')
1298
b = BzrDir.create_standalone_workingtree('b')
1299
basis = a.basis_tree()
1301
self.addCleanup(basis.unlock)
1302
build_tree(basis, b)
1303
self.assertIs(os.path.isdir('b/foo'), True)
1304
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1305
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1307
def test_build_with_references(self):
1308
tree = self.make_branch_and_tree('source',
1309
format='dirstate-with-subtree')
1310
subtree = self.make_branch_and_tree('source/subtree',
1311
format='dirstate-with-subtree')
1312
tree.add_reference(subtree)
1313
tree.commit('a revision')
1314
tree.branch.create_checkout('target')
1315
self.failUnlessExists('target')
1316
self.failUnlessExists('target/subtree')
1318
def test_file_conflict_handling(self):
1319
"""Ensure that when building trees, conflict handling is done"""
1320
source = self.make_branch_and_tree('source')
1321
target = self.make_branch_and_tree('target')
1322
self.build_tree(['source/file', 'target/file'])
1323
source.add('file', 'new-file')
1324
source.commit('added file')
1325
build_tree(source.basis_tree(), target)
1326
self.assertEqual([DuplicateEntry('Moved existing file to',
1327
'file.moved', 'file', None, 'new-file')],
1329
target2 = self.make_branch_and_tree('target2')
1330
target_file = file('target2/file', 'wb')
1332
source_file = file('source/file', 'rb')
1334
target_file.write(source_file.read())
1339
build_tree(source.basis_tree(), target2)
1340
self.assertEqual([], target2.conflicts())
1342
def test_symlink_conflict_handling(self):
1343
"""Ensure that when building trees, conflict handling is done"""
1344
self.requireFeature(SymlinkFeature)
1345
source = self.make_branch_and_tree('source')
1346
os.symlink('foo', 'source/symlink')
1347
source.add('symlink', 'new-symlink')
1348
source.commit('added file')
1349
target = self.make_branch_and_tree('target')
1350
os.symlink('bar', 'target/symlink')
1351
build_tree(source.basis_tree(), target)
1352
self.assertEqual([DuplicateEntry('Moved existing file to',
1353
'symlink.moved', 'symlink', None, 'new-symlink')],
1355
target = self.make_branch_and_tree('target2')
1356
os.symlink('foo', 'target2/symlink')
1357
build_tree(source.basis_tree(), target)
1358
self.assertEqual([], target.conflicts())
1360
def test_directory_conflict_handling(self):
1361
"""Ensure that when building trees, conflict handling is done"""
1362
source = self.make_branch_and_tree('source')
1363
target = self.make_branch_and_tree('target')
1364
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1365
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1366
source.commit('added file')
1367
build_tree(source.basis_tree(), target)
1368
self.assertEqual([], target.conflicts())
1369
self.failUnlessExists('target/dir1/file')
1371
# Ensure contents are merged
1372
target = self.make_branch_and_tree('target2')
1373
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1374
build_tree(source.basis_tree(), target)
1375
self.assertEqual([], target.conflicts())
1376
self.failUnlessExists('target2/dir1/file2')
1377
self.failUnlessExists('target2/dir1/file')
1379
# Ensure new contents are suppressed for existing branches
1380
target = self.make_branch_and_tree('target3')
1381
self.make_branch('target3/dir1')
1382
self.build_tree(['target3/dir1/file2'])
1383
build_tree(source.basis_tree(), target)
1384
self.failIfExists('target3/dir1/file')
1385
self.failUnlessExists('target3/dir1/file2')
1386
self.failUnlessExists('target3/dir1.diverted/file')
1387
self.assertEqual([DuplicateEntry('Diverted to',
1388
'dir1.diverted', 'dir1', 'new-dir1', None)],
1391
target = self.make_branch_and_tree('target4')
1392
self.build_tree(['target4/dir1/'])
1393
self.make_branch('target4/dir1/file')
1394
build_tree(source.basis_tree(), target)
1395
self.failUnlessExists('target4/dir1/file')
1396
self.assertEqual('directory', file_kind('target4/dir1/file'))
1397
self.failUnlessExists('target4/dir1/file.diverted')
1398
self.assertEqual([DuplicateEntry('Diverted to',
1399
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1402
def test_mixed_conflict_handling(self):
1403
"""Ensure that when building trees, conflict handling is done"""
1404
source = self.make_branch_and_tree('source')
1405
target = self.make_branch_and_tree('target')
1406
self.build_tree(['source/name', 'target/name/'])
1407
source.add('name', 'new-name')
1408
source.commit('added file')
1409
build_tree(source.basis_tree(), target)
1410
self.assertEqual([DuplicateEntry('Moved existing file to',
1411
'name.moved', 'name', None, 'new-name')], target.conflicts())
1413
def test_raises_in_populated(self):
1414
source = self.make_branch_and_tree('source')
1415
self.build_tree(['source/name'])
1417
source.commit('added name')
1418
target = self.make_branch_and_tree('target')
1419
self.build_tree(['target/name'])
1421
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1422
build_tree, source.basis_tree(), target)
1424
def test_build_tree_rename_count(self):
1425
source = self.make_branch_and_tree('source')
1426
self.build_tree(['source/file1', 'source/dir1/'])
1427
source.add(['file1', 'dir1'])
1428
source.commit('add1')
1429
target1 = self.make_branch_and_tree('target1')
1430
transform_result = build_tree(source.basis_tree(), target1)
1431
self.assertEqual(2, transform_result.rename_count)
1433
self.build_tree(['source/dir1/file2'])
1434
source.add(['dir1/file2'])
1435
source.commit('add3')
1436
target2 = self.make_branch_and_tree('target2')
1437
transform_result = build_tree(source.basis_tree(), target2)
1438
# children of non-root directories should not be renamed
1439
self.assertEqual(2, transform_result.rename_count)
1442
class MockTransform(object):
1444
def has_named_child(self, by_parent, parent_id, name):
1445
for child_id in by_parent[parent_id]:
1449
elif name == "name.~%s~" % child_id:
1454
class MockEntry(object):
1456
object.__init__(self)
1459
class TestGetBackupName(TestCase):
1460
def test_get_backup_name(self):
1461
tt = MockTransform()
1462
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1463
self.assertEqual(name, 'name.~1~')
1464
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1465
self.assertEqual(name, 'name.~2~')
1466
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1467
self.assertEqual(name, 'name.~1~')
1468
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1469
self.assertEqual(name, 'name.~1~')
1470
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1471
self.assertEqual(name, 'name.~4~')
1474
class TestFileMover(tests.TestCaseWithTransport):
1476
def test_file_mover(self):
1477
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1478
mover = _FileMover()
1479
mover.rename('a', 'q')
1480
self.failUnlessExists('q')
1481
self.failIfExists('a')
1482
self.failUnlessExists('q/b')
1483
self.failUnlessExists('c')
1484
self.failUnlessExists('c/d')
1486
def test_pre_delete_rollback(self):
1487
self.build_tree(['a/'])
1488
mover = _FileMover()
1489
mover.pre_delete('a', 'q')
1490
self.failUnlessExists('q')
1491
self.failIfExists('a')
1493
self.failIfExists('q')
1494
self.failUnlessExists('a')
1496
def test_apply_deletions(self):
1497
self.build_tree(['a/', 'b/'])
1498
mover = _FileMover()
1499
mover.pre_delete('a', 'q')
1500
mover.pre_delete('b', 'r')
1501
self.failUnlessExists('q')
1502
self.failUnlessExists('r')
1503
self.failIfExists('a')
1504
self.failIfExists('b')
1505
mover.apply_deletions()
1506
self.failIfExists('q')
1507
self.failIfExists('r')
1508
self.failIfExists('a')
1509
self.failIfExists('b')
1511
def test_file_mover_rollback(self):
1512
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1513
mover = _FileMover()
1514
mover.rename('c/d', 'c/f')
1515
mover.rename('c/e', 'c/d')
1517
mover.rename('a', 'c')
1520
self.failUnlessExists('a')
1521
self.failUnlessExists('c/d')
1524
class Bogus(Exception):
1528
class TestTransformRollback(tests.TestCaseWithTransport):
1530
class ExceptionFileMover(_FileMover):
1532
def __init__(self, bad_source=None, bad_target=None):
1533
_FileMover.__init__(self)
1534
self.bad_source = bad_source
1535
self.bad_target = bad_target
1537
def rename(self, source, target):
1538
if (self.bad_source is not None and
1539
source.endswith(self.bad_source)):
1541
elif (self.bad_target is not None and
1542
target.endswith(self.bad_target)):
1545
_FileMover.rename(self, source, target)
1547
def test_rollback_rename(self):
1548
tree = self.make_branch_and_tree('.')
1549
self.build_tree(['a/', 'a/b'])
1550
tt = TreeTransform(tree)
1551
self.addCleanup(tt.finalize)
1552
a_id = tt.trans_id_tree_path('a')
1553
tt.adjust_path('c', tt.root, a_id)
1554
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1555
self.assertRaises(Bogus, tt.apply,
1556
_mover=self.ExceptionFileMover(bad_source='a'))
1557
self.failUnlessExists('a')
1558
self.failUnlessExists('a/b')
1560
self.failUnlessExists('c')
1561
self.failUnlessExists('c/d')
1563
def test_rollback_rename_into_place(self):
1564
tree = self.make_branch_and_tree('.')
1565
self.build_tree(['a/', 'a/b'])
1566
tt = TreeTransform(tree)
1567
self.addCleanup(tt.finalize)
1568
a_id = tt.trans_id_tree_path('a')
1569
tt.adjust_path('c', tt.root, a_id)
1570
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1571
self.assertRaises(Bogus, tt.apply,
1572
_mover=self.ExceptionFileMover(bad_target='c/d'))
1573
self.failUnlessExists('a')
1574
self.failUnlessExists('a/b')
1576
self.failUnlessExists('c')
1577
self.failUnlessExists('c/d')
1579
def test_rollback_deletion(self):
1580
tree = self.make_branch_and_tree('.')
1581
self.build_tree(['a/', 'a/b'])
1582
tt = TreeTransform(tree)
1583
self.addCleanup(tt.finalize)
1584
a_id = tt.trans_id_tree_path('a')
1585
tt.delete_contents(a_id)
1586
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1587
self.assertRaises(Bogus, tt.apply,
1588
_mover=self.ExceptionFileMover(bad_target='d'))
1589
self.failUnlessExists('a')
1590
self.failUnlessExists('a/b')
1592
def test_resolve_no_parent(self):
1593
wt = self.make_branch_and_tree('.')
1594
tt = TreeTransform(wt)
1595
self.addCleanup(tt.finalize)
1596
parent = tt.trans_id_file_id('parent-id')
1597
tt.new_file('file', parent, 'Contents')
1598
resolve_conflicts(tt)