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)
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_add_del(self):
237
start, root = self.get_transform()
238
start.new_directory('a', root, 'a')
240
transform, root = self.get_transform()
241
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
242
transform.new_directory('a', root, 'a')
245
def test_unversioning(self):
246
create_tree, root = self.get_transform()
247
parent_id = create_tree.new_directory('parent', root, 'parent-id')
248
create_tree.new_file('child', parent_id, 'child', 'child-id')
250
unversion = TreeTransform(self.wt)
251
self.addCleanup(unversion.finalize)
252
parent = unversion.trans_id_tree_path('parent')
253
unversion.unversion_file(parent)
254
self.assertEqual(unversion.find_conflicts(),
255
[('unversioned parent', parent_id)])
256
file_id = unversion.trans_id_tree_file_id('child-id')
257
unversion.unversion_file(file_id)
260
def test_name_invariants(self):
261
create_tree, root = self.get_transform()
263
root = create_tree.root
264
create_tree.new_file('name1', root, 'hello1', 'name1')
265
create_tree.new_file('name2', root, 'hello2', 'name2')
266
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
267
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
268
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
269
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
272
mangle_tree,root = self.get_transform()
273
root = mangle_tree.root
275
name1 = mangle_tree.trans_id_tree_file_id('name1')
276
name2 = mangle_tree.trans_id_tree_file_id('name2')
277
mangle_tree.adjust_path('name2', root, name1)
278
mangle_tree.adjust_path('name1', root, name2)
280
#tests for deleting parent directories
281
ddir = mangle_tree.trans_id_tree_file_id('ddir')
282
mangle_tree.delete_contents(ddir)
283
dfile = mangle_tree.trans_id_tree_file_id('dfile')
284
mangle_tree.delete_versioned(dfile)
285
mangle_tree.unversion_file(dfile)
286
mfile = mangle_tree.trans_id_tree_file_id('mfile')
287
mangle_tree.adjust_path('mfile', root, mfile)
289
#tests for adding parent directories
290
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
291
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
292
mangle_tree.adjust_path('mfile2', newdir, mfile2)
293
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
294
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
295
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
296
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
298
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
299
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
300
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
301
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
302
self.assertEqual(file(mfile2_path).read(), 'later2')
303
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
304
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
305
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
306
self.assertEqual(file(newfile_path).read(), 'hello3')
307
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
308
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
309
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
311
def test_both_rename(self):
312
create_tree,root = self.get_transform()
313
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
314
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
316
mangle_tree,root = self.get_transform()
317
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
318
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
319
mangle_tree.adjust_path('test', root, selftest)
320
mangle_tree.adjust_path('test_too_much', root, selftest)
321
mangle_tree.set_executability(True, blackbox)
324
def test_both_rename2(self):
325
create_tree,root = self.get_transform()
326
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
327
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
328
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
329
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
332
mangle_tree,root = self.get_transform()
333
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
334
tests = mangle_tree.trans_id_tree_file_id('tests-id')
335
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
336
mangle_tree.adjust_path('selftest', bzrlib, tests)
337
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
338
mangle_tree.set_executability(True, test_too_much)
341
def test_both_rename3(self):
342
create_tree,root = self.get_transform()
343
tests = create_tree.new_directory('tests', root, 'tests-id')
344
create_tree.new_file('test_too_much.py', tests, 'hello1',
347
mangle_tree,root = self.get_transform()
348
tests = mangle_tree.trans_id_tree_file_id('tests-id')
349
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
350
mangle_tree.adjust_path('selftest', root, tests)
351
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
352
mangle_tree.set_executability(True, test_too_much)
355
def test_move_dangling_ie(self):
356
create_tree, root = self.get_transform()
358
root = create_tree.root
359
create_tree.new_file('name1', root, 'hello1', 'name1')
361
delete_contents, root = self.get_transform()
362
file = delete_contents.trans_id_tree_file_id('name1')
363
delete_contents.delete_contents(file)
364
delete_contents.apply()
365
move_id, root = self.get_transform()
366
name1 = move_id.trans_id_tree_file_id('name1')
367
newdir = move_id.new_directory('dir', root, 'newdir')
368
move_id.adjust_path('name2', newdir, name1)
371
def test_replace_dangling_ie(self):
372
create_tree, root = self.get_transform()
374
root = create_tree.root
375
create_tree.new_file('name1', root, 'hello1', 'name1')
377
delete_contents = TreeTransform(self.wt)
378
self.addCleanup(delete_contents.finalize)
379
file = delete_contents.trans_id_tree_file_id('name1')
380
delete_contents.delete_contents(file)
381
delete_contents.apply()
382
delete_contents.finalize()
383
replace = TreeTransform(self.wt)
384
self.addCleanup(replace.finalize)
385
name2 = replace.new_file('name2', root, 'hello2', 'name1')
386
conflicts = replace.find_conflicts()
387
name1 = replace.trans_id_tree_file_id('name1')
388
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
389
resolve_conflicts(replace)
392
def test_symlinks(self):
393
self.requireFeature(SymlinkFeature)
394
transform,root = self.get_transform()
395
oz_id = transform.new_directory('oz', root, 'oz-id')
396
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
398
wiz_id = transform.create_path('wizard2', oz_id)
399
transform.create_symlink('behind_curtain', wiz_id)
400
transform.version_file('wiz-id2', wiz_id)
401
transform.set_executability(True, wiz_id)
402
self.assertEqual(transform.find_conflicts(),
403
[('non-file executability', wiz_id)])
404
transform.set_executability(None, wiz_id)
406
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
407
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
408
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
410
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
413
def test_unable_create_symlink(self):
415
wt = self.make_branch_and_tree('.')
416
tt = TreeTransform(wt) # TreeTransform obtains write lock
418
tt.new_symlink('foo', tt.root, 'bar')
422
os_symlink = getattr(os, 'symlink', None)
425
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
427
"Unable to create symlink 'foo' on this platform",
431
os.symlink = os_symlink
433
def get_conflicted(self):
434
create,root = self.get_transform()
435
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
436
oz = create.new_directory('oz', root, 'oz-id')
437
create.new_directory('emeraldcity', oz, 'emerald-id')
439
conflicts,root = self.get_transform()
440
# set up duplicate entry, duplicate id
441
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
443
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
444
oz = conflicts.trans_id_tree_file_id('oz-id')
445
# set up DeletedParent parent conflict
446
conflicts.delete_versioned(oz)
447
emerald = conflicts.trans_id_tree_file_id('emerald-id')
448
# set up MissingParent conflict
449
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
450
conflicts.adjust_path('munchkincity', root, munchkincity)
451
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
453
conflicts.adjust_path('emeraldcity', emerald, emerald)
454
return conflicts, emerald, oz, old_dorothy, new_dorothy
456
def test_conflict_resolution(self):
457
conflicts, emerald, oz, old_dorothy, new_dorothy =\
458
self.get_conflicted()
459
resolve_conflicts(conflicts)
460
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
461
self.assertIs(conflicts.final_file_id(old_dorothy), None)
462
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
463
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
464
self.assertEqual(conflicts.final_parent(emerald), oz)
467
def test_cook_conflicts(self):
468
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
469
raw_conflicts = resolve_conflicts(tt)
470
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
471
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
472
'dorothy', None, 'dorothy-id')
473
self.assertEqual(cooked_conflicts[0], duplicate)
474
duplicate_id = DuplicateID('Unversioned existing file',
475
'dorothy.moved', 'dorothy', None,
477
self.assertEqual(cooked_conflicts[1], duplicate_id)
478
missing_parent = MissingParent('Created directory', 'munchkincity',
480
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
481
self.assertEqual(cooked_conflicts[2], missing_parent)
482
unversioned_parent = UnversionedParent('Versioned directory',
485
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
487
self.assertEqual(cooked_conflicts[3], unversioned_parent)
488
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
489
'oz/emeraldcity', 'emerald-id', 'emerald-id')
490
self.assertEqual(cooked_conflicts[4], deleted_parent)
491
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
492
self.assertEqual(cooked_conflicts[6], parent_loop)
493
self.assertEqual(len(cooked_conflicts), 7)
496
def test_string_conflicts(self):
497
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
498
raw_conflicts = resolve_conflicts(tt)
499
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
501
conflicts_s = [str(c) for c in cooked_conflicts]
502
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
503
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
504
'Moved existing file to '
506
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
507
'Unversioned existing file '
509
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
510
' munchkincity. Created directory.')
511
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
512
' versioned, but has versioned'
513
' children. Versioned directory.')
514
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
515
" is not empty. Not deleting.")
516
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
517
' versioned, but has versioned'
518
' children. Versioned directory.')
519
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
520
' oz/emeraldcity. Cancelled move.')
522
def test_moving_versioned_directories(self):
523
create, root = self.get_transform()
524
kansas = create.new_directory('kansas', root, 'kansas-id')
525
create.new_directory('house', kansas, 'house-id')
526
create.new_directory('oz', root, 'oz-id')
528
cyclone, root = self.get_transform()
529
oz = cyclone.trans_id_tree_file_id('oz-id')
530
house = cyclone.trans_id_tree_file_id('house-id')
531
cyclone.adjust_path('house', oz, house)
534
def test_moving_root(self):
535
create, root = self.get_transform()
536
fun = create.new_directory('fun', root, 'fun-id')
537
create.new_directory('sun', root, 'sun-id')
538
create.new_directory('moon', root, 'moon')
540
transform, root = self.get_transform()
541
transform.adjust_root_path('oldroot', fun)
542
new_root=transform.trans_id_tree_path('')
543
transform.version_file('new-root', new_root)
546
def test_renames(self):
547
create, root = self.get_transform()
548
old = create.new_directory('old-parent', root, 'old-id')
549
intermediate = create.new_directory('intermediate', old, 'im-id')
550
myfile = create.new_file('myfile', intermediate, 'myfile-text',
553
rename, root = self.get_transform()
554
old = rename.trans_id_file_id('old-id')
555
rename.adjust_path('new', root, old)
556
myfile = rename.trans_id_file_id('myfile-id')
557
rename.set_executability(True, myfile)
560
def test_find_interesting(self):
561
create, root = self.get_transform()
563
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
564
create.new_file('uvfile', root, 'othertext')
566
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
567
find_interesting, wt, wt, ['vfile'])
568
self.assertEqual(result, set(['myfile-id']))
570
def test_set_executability_order(self):
571
"""Ensure that executability behaves the same, no matter what order.
573
- create file and set executability simultaneously
574
- create file and set executability afterward
575
- unsetting the executability of a file whose executability has not been
576
declared should throw an exception (this may happen when a
577
merge attempts to create a file with a duplicate ID)
579
transform, root = self.get_transform()
581
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
583
sac = transform.new_file('set_after_creation', root,
584
'Set after creation', 'sac')
585
transform.set_executability(True, sac)
586
uws = transform.new_file('unset_without_set', root, 'Unset badly',
588
self.assertRaises(KeyError, transform.set_executability, None, uws)
590
self.assertTrue(wt.is_executable('soc'))
591
self.assertTrue(wt.is_executable('sac'))
593
def test_preserve_mode(self):
594
"""File mode is preserved when replacing content"""
595
if sys.platform == 'win32':
596
raise TestSkipped('chmod has no effect on win32')
597
transform, root = self.get_transform()
598
transform.new_file('file1', root, 'contents', 'file1-id', True)
600
self.assertTrue(self.wt.is_executable('file1-id'))
601
transform, root = self.get_transform()
602
file1_id = transform.trans_id_tree_file_id('file1-id')
603
transform.delete_contents(file1_id)
604
transform.create_file('contents2', file1_id)
606
self.assertTrue(self.wt.is_executable('file1-id'))
608
def test__set_mode_stats_correctly(self):
609
"""_set_mode stats to determine file mode."""
610
if sys.platform == 'win32':
611
raise TestSkipped('chmod has no effect on win32')
615
def instrumented_stat(path):
616
stat_paths.append(path)
617
return real_stat(path)
619
transform, root = self.get_transform()
621
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
622
file_id='bar-id-1', executable=False)
625
transform, root = self.get_transform()
626
bar1_id = transform.trans_id_tree_path('bar')
627
bar2_id = transform.trans_id_tree_path('bar2')
629
os.stat = instrumented_stat
630
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
635
bar1_abspath = self.wt.abspath('bar')
636
self.assertEqual([bar1_abspath], stat_paths)
638
def test_iter_changes(self):
639
self.wt.set_root_id('eert_toor')
640
transform, root = self.get_transform()
641
transform.new_file('old', root, 'blah', 'id-1', True)
643
transform, root = self.get_transform()
645
self.assertEqual([], list(transform._iter_changes()))
646
old = transform.trans_id_tree_file_id('id-1')
647
transform.unversion_file(old)
648
self.assertEqual([('id-1', ('old', None), False, (True, False),
649
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
650
(True, True))], list(transform._iter_changes()))
651
transform.new_directory('new', root, 'id-1')
652
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
653
('eert_toor', 'eert_toor'), ('old', 'new'),
654
('file', 'directory'),
655
(True, False))], list(transform._iter_changes()))
659
def test_iter_changes_new(self):
660
self.wt.set_root_id('eert_toor')
661
transform, root = self.get_transform()
662
transform.new_file('old', root, 'blah')
664
transform, root = self.get_transform()
666
old = transform.trans_id_tree_path('old')
667
transform.version_file('id-1', old)
668
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
669
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
670
(False, False))], list(transform._iter_changes()))
674
def test_iter_changes_modifications(self):
675
self.wt.set_root_id('eert_toor')
676
transform, root = self.get_transform()
677
transform.new_file('old', root, 'blah', 'id-1')
678
transform.new_file('new', root, 'blah')
679
transform.new_directory('subdir', root, 'subdir-id')
681
transform, root = self.get_transform()
683
old = transform.trans_id_tree_path('old')
684
subdir = transform.trans_id_tree_file_id('subdir-id')
685
new = transform.trans_id_tree_path('new')
686
self.assertEqual([], list(transform._iter_changes()))
689
transform.delete_contents(old)
690
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
691
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
692
(False, False))], list(transform._iter_changes()))
695
transform.create_file('blah', old)
696
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
697
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
698
(False, False))], list(transform._iter_changes()))
699
transform.cancel_deletion(old)
700
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
701
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
702
(False, False))], list(transform._iter_changes()))
703
transform.cancel_creation(old)
705
# move file_id to a different file
706
self.assertEqual([], list(transform._iter_changes()))
707
transform.unversion_file(old)
708
transform.version_file('id-1', new)
709
transform.adjust_path('old', root, new)
710
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
711
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
712
(False, False))], list(transform._iter_changes()))
713
transform.cancel_versioning(new)
714
transform._removed_id = set()
717
self.assertEqual([], list(transform._iter_changes()))
718
transform.set_executability(True, old)
719
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
720
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
721
(False, True))], list(transform._iter_changes()))
722
transform.set_executability(None, old)
725
self.assertEqual([], list(transform._iter_changes()))
726
transform.adjust_path('new', root, old)
727
transform._new_parent = {}
728
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
729
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
730
(False, False))], list(transform._iter_changes()))
731
transform._new_name = {}
734
self.assertEqual([], list(transform._iter_changes()))
735
transform.adjust_path('new', subdir, old)
736
transform._new_name = {}
737
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
738
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
739
('file', 'file'), (False, False))],
740
list(transform._iter_changes()))
741
transform._new_path = {}
746
def test_iter_changes_modified_bleed(self):
747
self.wt.set_root_id('eert_toor')
748
"""Modified flag should not bleed from one change to another"""
749
# unfortunately, we have no guarantee that file1 (which is modified)
750
# will be applied before file2. And if it's applied after file2, it
751
# obviously can't bleed into file2's change output. But for now, it
753
transform, root = self.get_transform()
754
transform.new_file('file1', root, 'blah', 'id-1')
755
transform.new_file('file2', root, 'blah', 'id-2')
757
transform, root = self.get_transform()
759
transform.delete_contents(transform.trans_id_file_id('id-1'))
760
transform.set_executability(True,
761
transform.trans_id_file_id('id-2'))
762
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
763
('eert_toor', 'eert_toor'), ('file1', u'file1'),
764
('file', None), (False, False)),
765
('id-2', (u'file2', u'file2'), False, (True, True),
766
('eert_toor', 'eert_toor'), ('file2', u'file2'),
767
('file', 'file'), (False, True))],
768
list(transform._iter_changes()))
772
def test_iter_changes_move_missing(self):
773
"""Test moving ids with no files around"""
774
self.wt.set_root_id('toor_eert')
775
# Need two steps because versioning a non-existant file is a conflict.
776
transform, root = self.get_transform()
777
transform.new_directory('floater', root, 'floater-id')
779
transform, root = self.get_transform()
780
transform.delete_contents(transform.trans_id_tree_path('floater'))
782
transform, root = self.get_transform()
783
floater = transform.trans_id_tree_path('floater')
785
transform.adjust_path('flitter', root, floater)
786
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
787
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
788
(None, None), (False, False))], list(transform._iter_changes()))
792
def test_iter_changes_pointless(self):
793
"""Ensure that no-ops are not treated as modifications"""
794
self.wt.set_root_id('eert_toor')
795
transform, root = self.get_transform()
796
transform.new_file('old', root, 'blah', 'id-1')
797
transform.new_directory('subdir', root, 'subdir-id')
799
transform, root = self.get_transform()
801
old = transform.trans_id_tree_path('old')
802
subdir = transform.trans_id_tree_file_id('subdir-id')
803
self.assertEqual([], list(transform._iter_changes()))
804
transform.delete_contents(subdir)
805
transform.create_directory(subdir)
806
transform.set_executability(False, old)
807
transform.unversion_file(old)
808
transform.version_file('id-1', old)
809
transform.adjust_path('old', root, old)
810
self.assertEqual([], list(transform._iter_changes()))
814
def test_rename_count(self):
815
transform, root = self.get_transform()
816
transform.new_file('name1', root, 'contents')
817
self.assertEqual(transform.rename_count, 0)
819
self.assertEqual(transform.rename_count, 1)
820
transform2, root = self.get_transform()
821
transform2.adjust_path('name2', root,
822
transform2.trans_id_tree_path('name1'))
823
self.assertEqual(transform2.rename_count, 0)
825
self.assertEqual(transform2.rename_count, 2)
827
def test_change_parent(self):
828
"""Ensure that after we change a parent, the results are still right.
830
Renames and parent changes on pending transforms can happen as part
831
of conflict resolution, and are explicitly permitted by the
834
This test ensures they work correctly with the rename-avoidance
837
transform, root = self.get_transform()
838
parent1 = transform.new_directory('parent1', root)
839
child1 = transform.new_file('child1', parent1, 'contents')
840
parent2 = transform.new_directory('parent2', root)
841
transform.adjust_path('child1', parent2, child1)
843
self.failIfExists(self.wt.abspath('parent1/child1'))
844
self.failUnlessExists(self.wt.abspath('parent2/child1'))
845
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
846
# no rename for child1 (counting only renames during apply)
847
self.failUnlessEqual(2, transform.rename_count)
849
def test_cancel_parent(self):
850
"""Cancelling a parent doesn't cause deletion of a non-empty directory
852
This is like the test_change_parent, except that we cancel the parent
853
before adjusting the path. The transform must detect that the
854
directory is non-empty, and move children to safe locations.
856
transform, root = self.get_transform()
857
parent1 = transform.new_directory('parent1', root)
858
child1 = transform.new_file('child1', parent1, 'contents')
859
child2 = transform.new_file('child2', parent1, 'contents')
861
transform.cancel_creation(parent1)
863
self.fail('Failed to move child1 before deleting parent1')
864
transform.cancel_creation(child2)
865
transform.create_directory(parent1)
867
transform.cancel_creation(parent1)
868
# If the transform incorrectly believes that child2 is still in
869
# parent1's limbo directory, it will try to rename it and fail
870
# because was already moved by the first cancel_creation.
872
self.fail('Transform still thinks child2 is a child of parent1')
873
parent2 = transform.new_directory('parent2', root)
874
transform.adjust_path('child1', parent2, child1)
876
self.failIfExists(self.wt.abspath('parent1'))
877
self.failUnlessExists(self.wt.abspath('parent2/child1'))
878
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
879
self.failUnlessEqual(2, transform.rename_count)
881
def test_adjust_and_cancel(self):
882
"""Make sure adjust_path keeps track of limbo children properly"""
883
transform, root = self.get_transform()
884
parent1 = transform.new_directory('parent1', root)
885
child1 = transform.new_file('child1', parent1, 'contents')
886
parent2 = transform.new_directory('parent2', root)
887
transform.adjust_path('child1', parent2, child1)
888
transform.cancel_creation(child1)
890
transform.cancel_creation(parent1)
891
# if the transform thinks child1 is still in parent1's limbo
892
# directory, it will attempt to move it and fail.
894
self.fail('Transform still thinks child1 is a child of parent1')
897
def test_noname_contents(self):
898
"""TreeTransform should permit deferring naming files."""
899
transform, root = self.get_transform()
900
parent = transform.trans_id_file_id('parent-id')
902
transform.create_directory(parent)
904
self.fail("Can't handle contents with no name")
907
def test_noname_contents_nested(self):
908
"""TreeTransform should permit deferring naming files."""
909
transform, root = self.get_transform()
910
parent = transform.trans_id_file_id('parent-id')
912
transform.create_directory(parent)
914
self.fail("Can't handle contents with no name")
915
child = transform.new_directory('child', parent)
916
transform.adjust_path('parent', root, parent)
918
self.failUnlessExists(self.wt.abspath('parent/child'))
919
self.assertEqual(1, transform.rename_count)
921
def test_reuse_name(self):
922
"""Avoid reusing the same limbo name for different files"""
923
transform, root = self.get_transform()
924
parent = transform.new_directory('parent', root)
925
child1 = transform.new_directory('child', parent)
927
child2 = transform.new_directory('child', parent)
929
self.fail('Tranform tried to use the same limbo name twice')
930
transform.adjust_path('child2', parent, child2)
932
# limbo/new-1 => parent, limbo/new-3 => parent/child2
933
# child2 is put into top-level limbo because child1 has already
934
# claimed the direct limbo path when child2 is created. There is no
935
# advantage in renaming files once they're in top-level limbo, except
937
self.assertEqual(2, transform.rename_count)
939
def test_reuse_when_first_moved(self):
940
"""Don't avoid direct paths when it is safe to use them"""
941
transform, root = self.get_transform()
942
parent = transform.new_directory('parent', root)
943
child1 = transform.new_directory('child', parent)
944
transform.adjust_path('child1', parent, child1)
945
child2 = transform.new_directory('child', parent)
947
# limbo/new-1 => parent
948
self.assertEqual(1, transform.rename_count)
950
def test_reuse_after_cancel(self):
951
"""Don't avoid direct paths when it is safe to use them"""
952
transform, root = self.get_transform()
953
parent2 = transform.new_directory('parent2', root)
954
child1 = transform.new_directory('child1', parent2)
955
transform.cancel_creation(parent2)
956
transform.create_directory(parent2)
957
child2 = transform.new_directory('child1', parent2)
958
transform.adjust_path('child2', parent2, child1)
960
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
961
self.assertEqual(2, transform.rename_count)
963
def test_finalize_order(self):
964
"""Finalize must be done in child-to-parent order"""
965
transform, root = self.get_transform()
966
parent = transform.new_directory('parent', root)
967
child = transform.new_directory('child', parent)
971
self.fail('Tried to remove parent before child1')
973
def test_cancel_with_cancelled_child_should_succeed(self):
974
transform, root = self.get_transform()
975
parent = transform.new_directory('parent', root)
976
child = transform.new_directory('child', parent)
977
transform.cancel_creation(child)
978
transform.cancel_creation(parent)
981
def test_change_entry(self):
982
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
983
self.callDeprecated([txt], change_entry, None, None, None, None, None,
987
class TransformGroup(object):
988
def __init__(self, dirname, root_id):
991
self.wt = BzrDir.create_standalone_workingtree(dirname)
992
self.wt.set_root_id(root_id)
993
self.b = self.wt.branch
994
self.tt = TreeTransform(self.wt)
995
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
998
def conflict_text(tree, merge):
999
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1000
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1003
class TestTransformMerge(TestCaseInTempDir):
1004
def test_text_merge(self):
1005
root_id = generate_ids.gen_root_id()
1006
base = TransformGroup("base", root_id)
1007
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1008
base.tt.new_file('b', base.root, 'b1', 'b')
1009
base.tt.new_file('c', base.root, 'c', 'c')
1010
base.tt.new_file('d', base.root, 'd', 'd')
1011
base.tt.new_file('e', base.root, 'e', 'e')
1012
base.tt.new_file('f', base.root, 'f', 'f')
1013
base.tt.new_directory('g', base.root, 'g')
1014
base.tt.new_directory('h', base.root, 'h')
1016
other = TransformGroup("other", root_id)
1017
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1018
other.tt.new_file('b', other.root, 'b2', 'b')
1019
other.tt.new_file('c', other.root, 'c2', 'c')
1020
other.tt.new_file('d', other.root, 'd', 'd')
1021
other.tt.new_file('e', other.root, 'e2', 'e')
1022
other.tt.new_file('f', other.root, 'f', 'f')
1023
other.tt.new_file('g', other.root, 'g', 'g')
1024
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1025
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1027
this = TransformGroup("this", root_id)
1028
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1029
this.tt.new_file('b', this.root, 'b', 'b')
1030
this.tt.new_file('c', this.root, 'c', 'c')
1031
this.tt.new_file('d', this.root, 'd2', 'd')
1032
this.tt.new_file('e', this.root, 'e2', 'e')
1033
this.tt.new_file('f', this.root, 'f', 'f')
1034
this.tt.new_file('g', this.root, 'g', 'g')
1035
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1036
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1038
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1040
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1041
# three-way text conflict
1042
self.assertEqual(this.wt.get_file('b').read(),
1043
conflict_text('b', 'b2'))
1045
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1047
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1048
# Ambigious clean merge
1049
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1051
self.assertEqual(this.wt.get_file('f').read(), 'f')
1052
# Correct correct results when THIS == OTHER
1053
self.assertEqual(this.wt.get_file('g').read(), 'g')
1054
# Text conflict when THIS & OTHER are text and BASE is dir
1055
self.assertEqual(this.wt.get_file('h').read(),
1056
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1057
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1059
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1061
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1062
self.assertEqual(this.wt.get_file('i').read(),
1063
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1064
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1066
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1068
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1069
modified = ['a', 'b', 'c', 'h', 'i']
1070
merge_modified = this.wt.merge_modified()
1071
self.assertSubset(merge_modified, modified)
1072
self.assertEqual(len(merge_modified), len(modified))
1073
file(this.wt.id2abspath('a'), 'wb').write('booga')
1075
merge_modified = this.wt.merge_modified()
1076
self.assertSubset(merge_modified, modified)
1077
self.assertEqual(len(merge_modified), len(modified))
1081
def test_file_merge(self):
1082
self.requireFeature(SymlinkFeature)
1083
root_id = generate_ids.gen_root_id()
1084
base = TransformGroup("BASE", root_id)
1085
this = TransformGroup("THIS", root_id)
1086
other = TransformGroup("OTHER", root_id)
1087
for tg in this, base, other:
1088
tg.tt.new_directory('a', tg.root, 'a')
1089
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1090
tg.tt.new_file('c', tg.root, 'c', 'c')
1091
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1092
targets = ((base, 'base-e', 'base-f', None, None),
1093
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1094
(other, 'other-e', None, 'other-g', 'other-h'))
1095
for tg, e_target, f_target, g_target, h_target in targets:
1096
for link, target in (('e', e_target), ('f', f_target),
1097
('g', g_target), ('h', h_target)):
1098
if target is not None:
1099
tg.tt.new_symlink(link, tg.root, target, link)
1101
for tg in this, base, other:
1103
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1104
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1105
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1106
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1107
for suffix in ('THIS', 'BASE', 'OTHER'):
1108
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1109
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1110
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1111
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1112
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1113
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1114
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1115
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1116
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1117
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1118
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1119
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1120
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1121
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1123
def test_filename_merge(self):
1124
root_id = generate_ids.gen_root_id()
1125
base = TransformGroup("BASE", root_id)
1126
this = TransformGroup("THIS", root_id)
1127
other = TransformGroup("OTHER", root_id)
1128
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1129
for t in [base, this, other]]
1130
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1131
for t in [base, this, other]]
1132
base.tt.new_directory('c', base_a, 'c')
1133
this.tt.new_directory('c1', this_a, 'c')
1134
other.tt.new_directory('c', other_b, 'c')
1136
base.tt.new_directory('d', base_a, 'd')
1137
this.tt.new_directory('d1', this_b, 'd')
1138
other.tt.new_directory('d', other_a, 'd')
1140
base.tt.new_directory('e', base_a, 'e')
1141
this.tt.new_directory('e', this_a, 'e')
1142
other.tt.new_directory('e1', other_b, 'e')
1144
base.tt.new_directory('f', base_a, 'f')
1145
this.tt.new_directory('f1', this_b, 'f')
1146
other.tt.new_directory('f1', other_b, 'f')
1148
for tg in [this, base, other]:
1150
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1151
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1152
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1153
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1154
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1156
def test_filename_merge_conflicts(self):
1157
root_id = generate_ids.gen_root_id()
1158
base = TransformGroup("BASE", root_id)
1159
this = TransformGroup("THIS", root_id)
1160
other = TransformGroup("OTHER", root_id)
1161
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1162
for t in [base, this, other]]
1163
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1164
for t in [base, this, other]]
1166
base.tt.new_file('g', base_a, 'g', 'g')
1167
other.tt.new_file('g1', other_b, 'g1', 'g')
1169
base.tt.new_file('h', base_a, 'h', 'h')
1170
this.tt.new_file('h1', this_b, 'h1', 'h')
1172
base.tt.new_file('i', base.root, 'i', 'i')
1173
other.tt.new_directory('i1', this_b, 'i')
1175
for tg in [this, base, other]:
1177
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1179
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1180
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1181
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1182
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1183
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1184
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1185
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1188
class TestBuildTree(tests.TestCaseWithTransport):
1190
def test_build_tree_with_symlinks(self):
1191
self.requireFeature(SymlinkFeature)
1193
a = BzrDir.create_standalone_workingtree('a')
1195
file('a/foo/bar', 'wb').write('contents')
1196
os.symlink('a/foo/bar', 'a/foo/baz')
1197
a.add(['foo', 'foo/bar', 'foo/baz'])
1198
a.commit('initial commit')
1199
b = BzrDir.create_standalone_workingtree('b')
1200
basis = a.basis_tree()
1202
self.addCleanup(basis.unlock)
1203
build_tree(basis, b)
1204
self.assertIs(os.path.isdir('b/foo'), True)
1205
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1206
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1208
def test_build_with_references(self):
1209
tree = self.make_branch_and_tree('source',
1210
format='dirstate-with-subtree')
1211
subtree = self.make_branch_and_tree('source/subtree',
1212
format='dirstate-with-subtree')
1213
tree.add_reference(subtree)
1214
tree.commit('a revision')
1215
tree.branch.create_checkout('target')
1216
self.failUnlessExists('target')
1217
self.failUnlessExists('target/subtree')
1219
def test_file_conflict_handling(self):
1220
"""Ensure that when building trees, conflict handling is done"""
1221
source = self.make_branch_and_tree('source')
1222
target = self.make_branch_and_tree('target')
1223
self.build_tree(['source/file', 'target/file'])
1224
source.add('file', 'new-file')
1225
source.commit('added file')
1226
build_tree(source.basis_tree(), target)
1227
self.assertEqual([DuplicateEntry('Moved existing file to',
1228
'file.moved', 'file', None, 'new-file')],
1230
target2 = self.make_branch_and_tree('target2')
1231
target_file = file('target2/file', 'wb')
1233
source_file = file('source/file', 'rb')
1235
target_file.write(source_file.read())
1240
build_tree(source.basis_tree(), target2)
1241
self.assertEqual([], target2.conflicts())
1243
def test_symlink_conflict_handling(self):
1244
"""Ensure that when building trees, conflict handling is done"""
1245
self.requireFeature(SymlinkFeature)
1246
source = self.make_branch_and_tree('source')
1247
os.symlink('foo', 'source/symlink')
1248
source.add('symlink', 'new-symlink')
1249
source.commit('added file')
1250
target = self.make_branch_and_tree('target')
1251
os.symlink('bar', 'target/symlink')
1252
build_tree(source.basis_tree(), target)
1253
self.assertEqual([DuplicateEntry('Moved existing file to',
1254
'symlink.moved', 'symlink', None, 'new-symlink')],
1256
target = self.make_branch_and_tree('target2')
1257
os.symlink('foo', 'target2/symlink')
1258
build_tree(source.basis_tree(), target)
1259
self.assertEqual([], target.conflicts())
1261
def test_directory_conflict_handling(self):
1262
"""Ensure that when building trees, conflict handling is done"""
1263
source = self.make_branch_and_tree('source')
1264
target = self.make_branch_and_tree('target')
1265
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1266
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1267
source.commit('added file')
1268
build_tree(source.basis_tree(), target)
1269
self.assertEqual([], target.conflicts())
1270
self.failUnlessExists('target/dir1/file')
1272
# Ensure contents are merged
1273
target = self.make_branch_and_tree('target2')
1274
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1275
build_tree(source.basis_tree(), target)
1276
self.assertEqual([], target.conflicts())
1277
self.failUnlessExists('target2/dir1/file2')
1278
self.failUnlessExists('target2/dir1/file')
1280
# Ensure new contents are suppressed for existing branches
1281
target = self.make_branch_and_tree('target3')
1282
self.make_branch('target3/dir1')
1283
self.build_tree(['target3/dir1/file2'])
1284
build_tree(source.basis_tree(), target)
1285
self.failIfExists('target3/dir1/file')
1286
self.failUnlessExists('target3/dir1/file2')
1287
self.failUnlessExists('target3/dir1.diverted/file')
1288
self.assertEqual([DuplicateEntry('Diverted to',
1289
'dir1.diverted', 'dir1', 'new-dir1', None)],
1292
target = self.make_branch_and_tree('target4')
1293
self.build_tree(['target4/dir1/'])
1294
self.make_branch('target4/dir1/file')
1295
build_tree(source.basis_tree(), target)
1296
self.failUnlessExists('target4/dir1/file')
1297
self.assertEqual('directory', file_kind('target4/dir1/file'))
1298
self.failUnlessExists('target4/dir1/file.diverted')
1299
self.assertEqual([DuplicateEntry('Diverted to',
1300
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1303
def test_mixed_conflict_handling(self):
1304
"""Ensure that when building trees, conflict handling is done"""
1305
source = self.make_branch_and_tree('source')
1306
target = self.make_branch_and_tree('target')
1307
self.build_tree(['source/name', 'target/name/'])
1308
source.add('name', 'new-name')
1309
source.commit('added file')
1310
build_tree(source.basis_tree(), target)
1311
self.assertEqual([DuplicateEntry('Moved existing file to',
1312
'name.moved', 'name', None, 'new-name')], target.conflicts())
1314
def test_raises_in_populated(self):
1315
source = self.make_branch_and_tree('source')
1316
self.build_tree(['source/name'])
1318
source.commit('added name')
1319
target = self.make_branch_and_tree('target')
1320
self.build_tree(['target/name'])
1322
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1323
build_tree, source.basis_tree(), target)
1325
def test_build_tree_rename_count(self):
1326
source = self.make_branch_and_tree('source')
1327
self.build_tree(['source/file1', 'source/dir1/'])
1328
source.add(['file1', 'dir1'])
1329
source.commit('add1')
1330
target1 = self.make_branch_and_tree('target1')
1331
transform_result = build_tree(source.basis_tree(), target1)
1332
self.assertEqual(2, transform_result.rename_count)
1334
self.build_tree(['source/dir1/file2'])
1335
source.add(['dir1/file2'])
1336
source.commit('add3')
1337
target2 = self.make_branch_and_tree('target2')
1338
transform_result = build_tree(source.basis_tree(), target2)
1339
# children of non-root directories should not be renamed
1340
self.assertEqual(2, transform_result.rename_count)
1343
class MockTransform(object):
1345
def has_named_child(self, by_parent, parent_id, name):
1346
for child_id in by_parent[parent_id]:
1350
elif name == "name.~%s~" % child_id:
1355
class MockEntry(object):
1357
object.__init__(self)
1360
class TestGetBackupName(TestCase):
1361
def test_get_backup_name(self):
1362
tt = MockTransform()
1363
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1364
self.assertEqual(name, 'name.~1~')
1365
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1366
self.assertEqual(name, 'name.~2~')
1367
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1368
self.assertEqual(name, 'name.~1~')
1369
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1370
self.assertEqual(name, 'name.~1~')
1371
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1372
self.assertEqual(name, 'name.~4~')
1375
class TestFileMover(tests.TestCaseWithTransport):
1377
def test_file_mover(self):
1378
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1379
mover = _FileMover()
1380
mover.rename('a', 'q')
1381
self.failUnlessExists('q')
1382
self.failIfExists('a')
1383
self.failUnlessExists('q/b')
1384
self.failUnlessExists('c')
1385
self.failUnlessExists('c/d')
1387
def test_pre_delete_rollback(self):
1388
self.build_tree(['a/'])
1389
mover = _FileMover()
1390
mover.pre_delete('a', 'q')
1391
self.failUnlessExists('q')
1392
self.failIfExists('a')
1394
self.failIfExists('q')
1395
self.failUnlessExists('a')
1397
def test_apply_deletions(self):
1398
self.build_tree(['a/', 'b/'])
1399
mover = _FileMover()
1400
mover.pre_delete('a', 'q')
1401
mover.pre_delete('b', 'r')
1402
self.failUnlessExists('q')
1403
self.failUnlessExists('r')
1404
self.failIfExists('a')
1405
self.failIfExists('b')
1406
mover.apply_deletions()
1407
self.failIfExists('q')
1408
self.failIfExists('r')
1409
self.failIfExists('a')
1410
self.failIfExists('b')
1412
def test_file_mover_rollback(self):
1413
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1414
mover = _FileMover()
1415
mover.rename('c/d', 'c/f')
1416
mover.rename('c/e', 'c/d')
1418
mover.rename('a', 'c')
1421
self.failUnlessExists('a')
1422
self.failUnlessExists('c/d')
1425
class Bogus(Exception):
1429
class TestTransformRollback(tests.TestCaseWithTransport):
1431
class ExceptionFileMover(_FileMover):
1433
def __init__(self, bad_source=None, bad_target=None):
1434
_FileMover.__init__(self)
1435
self.bad_source = bad_source
1436
self.bad_target = bad_target
1438
def rename(self, source, target):
1439
if (self.bad_source is not None and
1440
source.endswith(self.bad_source)):
1442
elif (self.bad_target is not None and
1443
target.endswith(self.bad_target)):
1446
_FileMover.rename(self, source, target)
1448
def test_rollback_rename(self):
1449
tree = self.make_branch_and_tree('.')
1450
self.build_tree(['a/', 'a/b'])
1451
tt = TreeTransform(tree)
1452
self.addCleanup(tt.finalize)
1453
a_id = tt.trans_id_tree_path('a')
1454
tt.adjust_path('c', tt.root, a_id)
1455
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1456
self.assertRaises(Bogus, tt.apply,
1457
_mover=self.ExceptionFileMover(bad_source='a'))
1458
self.failUnlessExists('a')
1459
self.failUnlessExists('a/b')
1461
self.failUnlessExists('c')
1462
self.failUnlessExists('c/d')
1464
def test_rollback_rename_into_place(self):
1465
tree = self.make_branch_and_tree('.')
1466
self.build_tree(['a/', 'a/b'])
1467
tt = TreeTransform(tree)
1468
self.addCleanup(tt.finalize)
1469
a_id = tt.trans_id_tree_path('a')
1470
tt.adjust_path('c', tt.root, a_id)
1471
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1472
self.assertRaises(Bogus, tt.apply,
1473
_mover=self.ExceptionFileMover(bad_target='c/d'))
1474
self.failUnlessExists('a')
1475
self.failUnlessExists('a/b')
1477
self.failUnlessExists('c')
1478
self.failUnlessExists('c/d')
1480
def test_rollback_deletion(self):
1481
tree = self.make_branch_and_tree('.')
1482
self.build_tree(['a/', 'a/b'])
1483
tt = TreeTransform(tree)
1484
self.addCleanup(tt.finalize)
1485
a_id = tt.trans_id_tree_path('a')
1486
tt.delete_contents(a_id)
1487
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1488
self.assertRaises(Bogus, tt.apply,
1489
_mover=self.ExceptionFileMover(bad_target='d'))
1490
self.failUnlessExists('a')
1491
self.failUnlessExists('a/b')