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
ImmortalLimbo, LockError)
35
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
36
from bzrlib.merge import Merge3Merger
37
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
38
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
39
resolve_conflicts, cook_conflicts,
40
find_interesting, build_tree, get_backup_name,
44
class TestTreeTransform(tests.TestCaseWithTransport):
47
super(TestTreeTransform, self).setUp()
48
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
51
def get_transform(self):
52
transform = TreeTransform(self.wt)
53
#self.addCleanup(transform.finalize)
54
return transform, transform.root
56
def test_existing_limbo(self):
57
limbo_name = urlutils.local_path_from_url(
58
self.wt._control_files.controlfilename('limbo'))
59
transform, root = self.get_transform()
60
os.mkdir(pathjoin(limbo_name, 'hehe'))
61
self.assertRaises(ImmortalLimbo, transform.apply)
62
self.assertRaises(LockError, self.wt.unlock)
63
self.assertRaises(ExistingLimbo, self.get_transform)
64
self.assertRaises(LockError, self.wt.unlock)
65
os.rmdir(pathjoin(limbo_name, 'hehe'))
67
transform, root = self.get_transform()
71
transform, root = self.get_transform()
72
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
73
imaginary_id = transform.trans_id_tree_path('imaginary')
74
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
75
self.assertEqual(imaginary_id, imaginary_id2)
76
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
77
self.assertEqual(transform.final_kind(root), 'directory')
78
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
79
trans_id = transform.create_path('name', root)
80
self.assertIs(transform.final_file_id(trans_id), None)
81
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
82
transform.create_file('contents', trans_id)
83
transform.set_executability(True, trans_id)
84
transform.version_file('my_pretties', trans_id)
85
self.assertRaises(DuplicateKey, transform.version_file,
86
'my_pretties', trans_id)
87
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
88
self.assertEqual(transform.final_parent(trans_id), root)
89
self.assertIs(transform.final_parent(root), ROOT_PARENT)
90
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
91
oz_id = transform.create_path('oz', root)
92
transform.create_directory(oz_id)
93
transform.version_file('ozzie', oz_id)
94
trans_id2 = transform.create_path('name2', root)
95
transform.create_file('contents', trans_id2)
96
transform.set_executability(False, trans_id2)
97
transform.version_file('my_pretties2', trans_id2)
98
modified_paths = transform.apply().modified_paths
99
self.assertEqual('contents', self.wt.get_file_byname('name').read())
100
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
101
self.assertIs(self.wt.is_executable('my_pretties'), True)
102
self.assertIs(self.wt.is_executable('my_pretties2'), False)
103
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
104
self.assertEqual(len(modified_paths), 3)
105
tree_mod_paths = [self.wt.id2abspath(f) for f in
106
('ozzie', 'my_pretties', 'my_pretties2')]
107
self.assertSubset(tree_mod_paths, modified_paths)
108
# is it safe to finalize repeatedly?
112
def test_convenience(self):
113
transform, root = self.get_transform()
114
trans_id = transform.new_file('name', root, 'contents',
116
oz = transform.new_directory('oz', root, 'oz-id')
117
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
118
toto = transform.new_file('toto', dorothy, 'toto-contents',
121
self.assertEqual(len(transform.find_conflicts()), 0)
123
self.assertRaises(ReusingTransform, transform.find_conflicts)
124
self.assertEqual('contents', file(self.wt.abspath('name')).read())
125
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
126
self.assertIs(self.wt.is_executable('my_pretties'), True)
127
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
128
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
129
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
131
self.assertEqual('toto-contents',
132
self.wt.get_file_byname('oz/dorothy/toto').read())
133
self.assertIs(self.wt.is_executable('toto-id'), False)
135
def test_tree_reference(self):
136
transform, root = self.get_transform()
137
tree = transform._tree
138
trans_id = transform.new_directory('reference', root, 'subtree-id')
139
transform.set_tree_reference('subtree-revision', trans_id)
142
self.addCleanup(tree.unlock)
143
self.assertEqual('subtree-revision',
144
tree.inventory['subtree-id'].reference_revision)
146
def test_conflicts(self):
147
transform, root = self.get_transform()
148
trans_id = transform.new_file('name', root, 'contents',
150
self.assertEqual(len(transform.find_conflicts()), 0)
151
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
152
self.assertEqual(transform.find_conflicts(),
153
[('duplicate', trans_id, trans_id2, 'name')])
154
self.assertRaises(MalformedTransform, transform.apply)
155
transform.adjust_path('name', trans_id, trans_id2)
156
self.assertEqual(transform.find_conflicts(),
157
[('non-directory parent', trans_id)])
158
tinman_id = transform.trans_id_tree_path('tinman')
159
transform.adjust_path('name', tinman_id, trans_id2)
160
self.assertEqual(transform.find_conflicts(),
161
[('unversioned parent', tinman_id),
162
('missing parent', tinman_id)])
163
lion_id = transform.create_path('lion', root)
164
self.assertEqual(transform.find_conflicts(),
165
[('unversioned parent', tinman_id),
166
('missing parent', tinman_id)])
167
transform.adjust_path('name', lion_id, trans_id2)
168
self.assertEqual(transform.find_conflicts(),
169
[('unversioned parent', lion_id),
170
('missing parent', lion_id)])
171
transform.version_file("Courage", lion_id)
172
self.assertEqual(transform.find_conflicts(),
173
[('missing parent', lion_id),
174
('versioning no contents', lion_id)])
175
transform.adjust_path('name2', root, trans_id2)
176
self.assertEqual(transform.find_conflicts(),
177
[('versioning no contents', lion_id)])
178
transform.create_file('Contents, okay?', lion_id)
179
transform.adjust_path('name2', trans_id2, trans_id2)
180
self.assertEqual(transform.find_conflicts(),
181
[('parent loop', trans_id2),
182
('non-directory parent', trans_id2)])
183
transform.adjust_path('name2', root, trans_id2)
184
oz_id = transform.new_directory('oz', root)
185
transform.set_executability(True, oz_id)
186
self.assertEqual(transform.find_conflicts(),
187
[('unversioned executability', oz_id)])
188
transform.version_file('oz-id', oz_id)
189
self.assertEqual(transform.find_conflicts(),
190
[('non-file executability', oz_id)])
191
transform.set_executability(None, oz_id)
192
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
194
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
195
self.assertEqual('contents', file(self.wt.abspath('name')).read())
196
transform2, root = self.get_transform()
197
oz_id = transform2.trans_id_tree_file_id('oz-id')
198
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
199
result = transform2.find_conflicts()
200
fp = FinalPaths(transform2)
201
self.assert_('oz/tip' in transform2._tree_path_ids)
202
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
203
self.assertEqual(len(result), 2)
204
self.assertEqual((result[0][0], result[0][1]),
205
('duplicate', newtip))
206
self.assertEqual((result[1][0], result[1][2]),
207
('duplicate id', newtip))
208
transform2.finalize()
209
transform3 = TreeTransform(self.wt)
210
self.addCleanup(transform3.finalize)
211
oz_id = transform3.trans_id_tree_file_id('oz-id')
212
transform3.delete_contents(oz_id)
213
self.assertEqual(transform3.find_conflicts(),
214
[('missing parent', oz_id)])
215
root_id = transform3.root
216
tip_id = transform3.trans_id_tree_file_id('tip-id')
217
transform3.adjust_path('tip', root_id, tip_id)
220
def test_add_del(self):
221
start, root = self.get_transform()
222
start.new_directory('a', root, 'a')
224
transform, root = self.get_transform()
225
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
226
transform.new_directory('a', root, 'a')
229
def test_unversioning(self):
230
create_tree, root = self.get_transform()
231
parent_id = create_tree.new_directory('parent', root, 'parent-id')
232
create_tree.new_file('child', parent_id, 'child', 'child-id')
234
unversion = TreeTransform(self.wt)
235
self.addCleanup(unversion.finalize)
236
parent = unversion.trans_id_tree_path('parent')
237
unversion.unversion_file(parent)
238
self.assertEqual(unversion.find_conflicts(),
239
[('unversioned parent', parent_id)])
240
file_id = unversion.trans_id_tree_file_id('child-id')
241
unversion.unversion_file(file_id)
244
def test_name_invariants(self):
245
create_tree, root = self.get_transform()
247
root = create_tree.root
248
create_tree.new_file('name1', root, 'hello1', 'name1')
249
create_tree.new_file('name2', root, 'hello2', 'name2')
250
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
251
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
252
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
253
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
256
mangle_tree,root = self.get_transform()
257
root = mangle_tree.root
259
name1 = mangle_tree.trans_id_tree_file_id('name1')
260
name2 = mangle_tree.trans_id_tree_file_id('name2')
261
mangle_tree.adjust_path('name2', root, name1)
262
mangle_tree.adjust_path('name1', root, name2)
264
#tests for deleting parent directories
265
ddir = mangle_tree.trans_id_tree_file_id('ddir')
266
mangle_tree.delete_contents(ddir)
267
dfile = mangle_tree.trans_id_tree_file_id('dfile')
268
mangle_tree.delete_versioned(dfile)
269
mangle_tree.unversion_file(dfile)
270
mfile = mangle_tree.trans_id_tree_file_id('mfile')
271
mangle_tree.adjust_path('mfile', root, mfile)
273
#tests for adding parent directories
274
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
275
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
276
mangle_tree.adjust_path('mfile2', newdir, mfile2)
277
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
278
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
279
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
280
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
282
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
283
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
284
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
285
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
286
self.assertEqual(file(mfile2_path).read(), 'later2')
287
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
288
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
289
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
290
self.assertEqual(file(newfile_path).read(), 'hello3')
291
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
292
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
293
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
295
def test_both_rename(self):
296
create_tree,root = self.get_transform()
297
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
298
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
300
mangle_tree,root = self.get_transform()
301
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
302
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
303
mangle_tree.adjust_path('test', root, selftest)
304
mangle_tree.adjust_path('test_too_much', root, selftest)
305
mangle_tree.set_executability(True, blackbox)
308
def test_both_rename2(self):
309
create_tree,root = self.get_transform()
310
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
311
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
312
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
313
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
316
mangle_tree,root = self.get_transform()
317
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
318
tests = mangle_tree.trans_id_tree_file_id('tests-id')
319
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
320
mangle_tree.adjust_path('selftest', bzrlib, tests)
321
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
322
mangle_tree.set_executability(True, test_too_much)
325
def test_both_rename3(self):
326
create_tree,root = self.get_transform()
327
tests = create_tree.new_directory('tests', root, 'tests-id')
328
create_tree.new_file('test_too_much.py', tests, 'hello1',
331
mangle_tree,root = self.get_transform()
332
tests = mangle_tree.trans_id_tree_file_id('tests-id')
333
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
334
mangle_tree.adjust_path('selftest', root, tests)
335
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
336
mangle_tree.set_executability(True, test_too_much)
339
def test_move_dangling_ie(self):
340
create_tree, root = self.get_transform()
342
root = create_tree.root
343
create_tree.new_file('name1', root, 'hello1', 'name1')
345
delete_contents, root = self.get_transform()
346
file = delete_contents.trans_id_tree_file_id('name1')
347
delete_contents.delete_contents(file)
348
delete_contents.apply()
349
move_id, root = self.get_transform()
350
name1 = move_id.trans_id_tree_file_id('name1')
351
newdir = move_id.new_directory('dir', root, 'newdir')
352
move_id.adjust_path('name2', newdir, name1)
355
def test_replace_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 = TreeTransform(self.wt)
362
self.addCleanup(delete_contents.finalize)
363
file = delete_contents.trans_id_tree_file_id('name1')
364
delete_contents.delete_contents(file)
365
delete_contents.apply()
366
delete_contents.finalize()
367
replace = TreeTransform(self.wt)
368
self.addCleanup(replace.finalize)
369
name2 = replace.new_file('name2', root, 'hello2', 'name1')
370
conflicts = replace.find_conflicts()
371
name1 = replace.trans_id_tree_file_id('name1')
372
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
373
resolve_conflicts(replace)
376
def test_symlinks(self):
377
if not has_symlinks():
378
raise TestSkipped('Symlinks are not supported on this platform')
379
transform,root = self.get_transform()
380
oz_id = transform.new_directory('oz', root, 'oz-id')
381
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
383
wiz_id = transform.create_path('wizard2', oz_id)
384
transform.create_symlink('behind_curtain', wiz_id)
385
transform.version_file('wiz-id2', wiz_id)
386
transform.set_executability(True, wiz_id)
387
self.assertEqual(transform.find_conflicts(),
388
[('non-file executability', wiz_id)])
389
transform.set_executability(None, wiz_id)
391
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
392
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
393
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
395
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
398
def get_conflicted(self):
399
create,root = self.get_transform()
400
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
401
oz = create.new_directory('oz', root, 'oz-id')
402
create.new_directory('emeraldcity', oz, 'emerald-id')
404
conflicts,root = self.get_transform()
405
# set up duplicate entry, duplicate id
406
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
408
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
409
oz = conflicts.trans_id_tree_file_id('oz-id')
410
# set up DeletedParent parent conflict
411
conflicts.delete_versioned(oz)
412
emerald = conflicts.trans_id_tree_file_id('emerald-id')
413
# set up MissingParent conflict
414
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
415
conflicts.adjust_path('munchkincity', root, munchkincity)
416
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
418
conflicts.adjust_path('emeraldcity', emerald, emerald)
419
return conflicts, emerald, oz, old_dorothy, new_dorothy
421
def test_conflict_resolution(self):
422
conflicts, emerald, oz, old_dorothy, new_dorothy =\
423
self.get_conflicted()
424
resolve_conflicts(conflicts)
425
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
426
self.assertIs(conflicts.final_file_id(old_dorothy), None)
427
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
428
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
429
self.assertEqual(conflicts.final_parent(emerald), oz)
432
def test_cook_conflicts(self):
433
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
434
raw_conflicts = resolve_conflicts(tt)
435
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
436
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
437
'dorothy', None, 'dorothy-id')
438
self.assertEqual(cooked_conflicts[0], duplicate)
439
duplicate_id = DuplicateID('Unversioned existing file',
440
'dorothy.moved', 'dorothy', None,
442
self.assertEqual(cooked_conflicts[1], duplicate_id)
443
missing_parent = MissingParent('Created directory', 'munchkincity',
445
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
446
self.assertEqual(cooked_conflicts[2], missing_parent)
447
unversioned_parent = UnversionedParent('Versioned directory',
450
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
452
self.assertEqual(cooked_conflicts[3], unversioned_parent)
453
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
454
'oz/emeraldcity', 'emerald-id', 'emerald-id')
455
self.assertEqual(cooked_conflicts[4], deleted_parent)
456
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
457
self.assertEqual(cooked_conflicts[6], parent_loop)
458
self.assertEqual(len(cooked_conflicts), 7)
461
def test_string_conflicts(self):
462
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
463
raw_conflicts = resolve_conflicts(tt)
464
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
466
conflicts_s = [str(c) for c in cooked_conflicts]
467
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
468
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
469
'Moved existing file to '
471
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
472
'Unversioned existing file '
474
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
475
' munchkincity. Created directory.')
476
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
477
' versioned, but has versioned'
478
' children. Versioned directory.')
479
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
480
" is not empty. Not deleting.")
481
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
482
' versioned, but has versioned'
483
' children. Versioned directory.')
484
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
485
' oz/emeraldcity. Cancelled move.')
487
def test_moving_versioned_directories(self):
488
create, root = self.get_transform()
489
kansas = create.new_directory('kansas', root, 'kansas-id')
490
create.new_directory('house', kansas, 'house-id')
491
create.new_directory('oz', root, 'oz-id')
493
cyclone, root = self.get_transform()
494
oz = cyclone.trans_id_tree_file_id('oz-id')
495
house = cyclone.trans_id_tree_file_id('house-id')
496
cyclone.adjust_path('house', oz, house)
499
def test_moving_root(self):
500
create, root = self.get_transform()
501
fun = create.new_directory('fun', root, 'fun-id')
502
create.new_directory('sun', root, 'sun-id')
503
create.new_directory('moon', root, 'moon')
505
transform, root = self.get_transform()
506
transform.adjust_root_path('oldroot', fun)
507
new_root=transform.trans_id_tree_path('')
508
transform.version_file('new-root', new_root)
511
def test_renames(self):
512
create, root = self.get_transform()
513
old = create.new_directory('old-parent', root, 'old-id')
514
intermediate = create.new_directory('intermediate', old, 'im-id')
515
myfile = create.new_file('myfile', intermediate, 'myfile-text',
518
rename, root = self.get_transform()
519
old = rename.trans_id_file_id('old-id')
520
rename.adjust_path('new', root, old)
521
myfile = rename.trans_id_file_id('myfile-id')
522
rename.set_executability(True, myfile)
525
def test_find_interesting(self):
526
create, root = self.get_transform()
528
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
529
create.new_file('uvfile', root, 'othertext')
531
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
532
find_interesting, wt, wt, ['vfile'])
533
self.assertEqual(result, set(['myfile-id']))
535
def test_set_executability_order(self):
536
"""Ensure that executability behaves the same, no matter what order.
538
- create file and set executability simultaneously
539
- create file and set executability afterward
540
- unsetting the executability of a file whose executability has not been
541
declared should throw an exception (this may happen when a
542
merge attempts to create a file with a duplicate ID)
544
transform, root = self.get_transform()
546
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
548
sac = transform.new_file('set_after_creation', root,
549
'Set after creation', 'sac')
550
transform.set_executability(True, sac)
551
uws = transform.new_file('unset_without_set', root, 'Unset badly',
553
self.assertRaises(KeyError, transform.set_executability, None, uws)
555
self.assertTrue(wt.is_executable('soc'))
556
self.assertTrue(wt.is_executable('sac'))
558
def test_preserve_mode(self):
559
"""File mode is preserved when replacing content"""
560
if sys.platform == 'win32':
561
raise TestSkipped('chmod has no effect on win32')
562
transform, root = self.get_transform()
563
transform.new_file('file1', root, 'contents', 'file1-id', True)
565
self.assertTrue(self.wt.is_executable('file1-id'))
566
transform, root = self.get_transform()
567
file1_id = transform.trans_id_tree_file_id('file1-id')
568
transform.delete_contents(file1_id)
569
transform.create_file('contents2', file1_id)
571
self.assertTrue(self.wt.is_executable('file1-id'))
573
def test__set_mode_stats_correctly(self):
574
"""_set_mode stats to determine file mode."""
575
if sys.platform == 'win32':
576
raise TestSkipped('chmod has no effect on win32')
580
def instrumented_stat(path):
581
stat_paths.append(path)
582
return real_stat(path)
584
transform, root = self.get_transform()
586
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
587
file_id='bar-id-1', executable=False)
590
transform, root = self.get_transform()
591
bar1_id = transform.trans_id_tree_path('bar')
592
bar2_id = transform.trans_id_tree_path('bar2')
594
os.stat = instrumented_stat
595
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
600
bar1_abspath = self.wt.abspath('bar')
601
self.assertEqual([bar1_abspath], stat_paths)
603
def test_iter_changes(self):
604
self.wt.set_root_id('eert_toor')
605
transform, root = self.get_transform()
606
transform.new_file('old', root, 'blah', 'id-1', True)
608
transform, root = self.get_transform()
610
self.assertEqual([], list(transform._iter_changes()))
611
old = transform.trans_id_tree_file_id('id-1')
612
transform.unversion_file(old)
613
self.assertEqual([('id-1', ('old', None), False, (True, False),
614
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
615
(True, True))], list(transform._iter_changes()))
616
transform.new_directory('new', root, 'id-1')
617
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
618
('eert_toor', 'eert_toor'), ('old', 'new'),
619
('file', 'directory'),
620
(True, False))], list(transform._iter_changes()))
624
def test_iter_changes_new(self):
625
self.wt.set_root_id('eert_toor')
626
transform, root = self.get_transform()
627
transform.new_file('old', root, 'blah')
629
transform, root = self.get_transform()
631
old = transform.trans_id_tree_path('old')
632
transform.version_file('id-1', old)
633
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
634
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
635
(False, False))], list(transform._iter_changes()))
639
def test_iter_changes_modifications(self):
640
self.wt.set_root_id('eert_toor')
641
transform, root = self.get_transform()
642
transform.new_file('old', root, 'blah', 'id-1')
643
transform.new_file('new', root, 'blah')
644
transform.new_directory('subdir', root, 'subdir-id')
646
transform, root = self.get_transform()
648
old = transform.trans_id_tree_path('old')
649
subdir = transform.trans_id_tree_file_id('subdir-id')
650
new = transform.trans_id_tree_path('new')
651
self.assertEqual([], list(transform._iter_changes()))
654
transform.delete_contents(old)
655
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
656
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
657
(False, False))], list(transform._iter_changes()))
660
transform.create_file('blah', old)
661
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
662
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
663
(False, False))], list(transform._iter_changes()))
664
transform.cancel_deletion(old)
665
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
666
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
667
(False, False))], list(transform._iter_changes()))
668
transform.cancel_creation(old)
670
# move file_id to a different file
671
self.assertEqual([], list(transform._iter_changes()))
672
transform.unversion_file(old)
673
transform.version_file('id-1', new)
674
transform.adjust_path('old', root, new)
675
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
676
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
677
(False, False))], list(transform._iter_changes()))
678
transform.cancel_versioning(new)
679
transform._removed_id = set()
682
self.assertEqual([], list(transform._iter_changes()))
683
transform.set_executability(True, old)
684
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
685
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
686
(False, True))], list(transform._iter_changes()))
687
transform.set_executability(None, old)
690
self.assertEqual([], list(transform._iter_changes()))
691
transform.adjust_path('new', root, old)
692
transform._new_parent = {}
693
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
694
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
695
(False, False))], list(transform._iter_changes()))
696
transform._new_name = {}
699
self.assertEqual([], list(transform._iter_changes()))
700
transform.adjust_path('new', subdir, old)
701
transform._new_name = {}
702
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
703
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
704
('file', 'file'), (False, False))],
705
list(transform._iter_changes()))
706
transform._new_path = {}
711
def test_iter_changes_modified_bleed(self):
712
self.wt.set_root_id('eert_toor')
713
"""Modified flag should not bleed from one change to another"""
714
# unfortunately, we have no guarantee that file1 (which is modified)
715
# will be applied before file2. And if it's applied after file2, it
716
# obviously can't bleed into file2's change output. But for now, it
718
transform, root = self.get_transform()
719
transform.new_file('file1', root, 'blah', 'id-1')
720
transform.new_file('file2', root, 'blah', 'id-2')
722
transform, root = self.get_transform()
724
transform.delete_contents(transform.trans_id_file_id('id-1'))
725
transform.set_executability(True,
726
transform.trans_id_file_id('id-2'))
727
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
728
('eert_toor', 'eert_toor'), ('file1', u'file1'),
729
('file', None), (False, False)),
730
('id-2', (u'file2', u'file2'), False, (True, True),
731
('eert_toor', 'eert_toor'), ('file2', u'file2'),
732
('file', 'file'), (False, True))],
733
list(transform._iter_changes()))
737
def test_iter_changes_move_missing(self):
738
"""Test moving ids with no files around"""
739
self.wt.set_root_id('toor_eert')
740
# Need two steps because versioning a non-existant file is a conflict.
741
transform, root = self.get_transform()
742
transform.new_directory('floater', root, 'floater-id')
744
transform, root = self.get_transform()
745
transform.delete_contents(transform.trans_id_tree_path('floater'))
747
transform, root = self.get_transform()
748
floater = transform.trans_id_tree_path('floater')
750
transform.adjust_path('flitter', root, floater)
751
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
752
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
753
(None, None), (False, False))], list(transform._iter_changes()))
757
def test_iter_changes_pointless(self):
758
"""Ensure that no-ops are not treated as modifications"""
759
self.wt.set_root_id('eert_toor')
760
transform, root = self.get_transform()
761
transform.new_file('old', root, 'blah', 'id-1')
762
transform.new_directory('subdir', root, 'subdir-id')
764
transform, root = self.get_transform()
766
old = transform.trans_id_tree_path('old')
767
subdir = transform.trans_id_tree_file_id('subdir-id')
768
self.assertEqual([], list(transform._iter_changes()))
769
transform.delete_contents(subdir)
770
transform.create_directory(subdir)
771
transform.set_executability(False, old)
772
transform.unversion_file(old)
773
transform.version_file('id-1', old)
774
transform.adjust_path('old', root, old)
775
self.assertEqual([], list(transform._iter_changes()))
779
def test_rename_count(self):
780
transform, root = self.get_transform()
781
transform.new_file('name1', root, 'contents')
782
self.assertEqual(transform.rename_count, 0)
784
self.assertEqual(transform.rename_count, 1)
785
transform2, root = self.get_transform()
786
transform2.adjust_path('name2', root,
787
transform2.trans_id_tree_path('name1'))
788
self.assertEqual(transform2.rename_count, 0)
790
self.assertEqual(transform2.rename_count, 2)
792
def test_change_parent(self):
793
"""Ensure that after we change a parent, the results are still right.
795
Renames and parent changes on pending transforms can happen as part
796
of conflict resolution, and are explicitly permitted by the
799
This test ensures they work correctly with the rename-avoidance
802
transform, root = self.get_transform()
803
parent1 = transform.new_directory('parent1', root)
804
child1 = transform.new_file('child1', parent1, 'contents')
805
parent2 = transform.new_directory('parent2', root)
806
transform.adjust_path('child1', parent2, child1)
808
self.failIfExists(self.wt.abspath('parent1/child1'))
809
self.failUnlessExists(self.wt.abspath('parent2/child1'))
810
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
811
# no rename for child1 (counting only renames during apply)
812
self.failUnlessEqual(2, transform.rename_count)
814
def test_cancel_parent(self):
815
"""Cancelling a parent doesn't cause deletion of a non-empty directory
817
This is like the test_change_parent, except that we cancel the parent
818
before adjusting the path. The transform must detect that the
819
directory is non-empty, and move children to safe locations.
821
transform, root = self.get_transform()
822
parent1 = transform.new_directory('parent1', root)
823
child1 = transform.new_file('child1', parent1, 'contents')
824
child2 = transform.new_file('child2', parent1, 'contents')
826
transform.cancel_creation(parent1)
828
self.fail('Failed to move child1 before deleting parent1')
829
transform.cancel_creation(child2)
830
transform.create_directory(parent1)
832
transform.cancel_creation(parent1)
833
# If the transform incorrectly believes that child2 is still in
834
# parent1's limbo directory, it will try to rename it and fail
835
# because was already moved by the first cancel_creation.
837
self.fail('Transform still thinks child2 is a child of parent1')
838
parent2 = transform.new_directory('parent2', root)
839
transform.adjust_path('child1', parent2, child1)
841
self.failIfExists(self.wt.abspath('parent1'))
842
self.failUnlessExists(self.wt.abspath('parent2/child1'))
843
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
844
self.failUnlessEqual(2, transform.rename_count)
846
def test_adjust_and_cancel(self):
847
"""Make sure adjust_path keeps track of limbo children properly"""
848
transform, root = self.get_transform()
849
parent1 = transform.new_directory('parent1', root)
850
child1 = transform.new_file('child1', parent1, 'contents')
851
parent2 = transform.new_directory('parent2', root)
852
transform.adjust_path('child1', parent2, child1)
853
transform.cancel_creation(child1)
855
transform.cancel_creation(parent1)
856
# if the transform thinks child1 is still in parent1's limbo
857
# directory, it will attempt to move it and fail.
859
self.fail('Transform still thinks child1 is a child of parent1')
862
def test_noname_contents(self):
863
"""TreeTransform should permit deferring naming files."""
864
transform, root = self.get_transform()
865
parent = transform.trans_id_file_id('parent-id')
867
transform.create_directory(parent)
869
self.fail("Can't handle contents with no name")
872
def test_noname_contents_nested(self):
873
"""TreeTransform should permit deferring naming files."""
874
transform, root = self.get_transform()
875
parent = transform.trans_id_file_id('parent-id')
877
transform.create_directory(parent)
879
self.fail("Can't handle contents with no name")
880
child = transform.new_directory('child', parent)
881
transform.adjust_path('parent', root, parent)
883
self.failUnlessExists(self.wt.abspath('parent/child'))
884
self.assertEqual(1, transform.rename_count)
886
def test_reuse_name(self):
887
"""Avoid reusing the same limbo name for different files"""
888
transform, root = self.get_transform()
889
parent = transform.new_directory('parent', root)
890
child1 = transform.new_directory('child', parent)
892
child2 = transform.new_directory('child', parent)
894
self.fail('Tranform tried to use the same limbo name twice')
895
transform.adjust_path('child2', parent, child2)
897
# limbo/new-1 => parent, limbo/new-3 => parent/child2
898
# child2 is put into top-level limbo because child1 has already
899
# claimed the direct limbo path when child2 is created. There is no
900
# advantage in renaming files once they're in top-level limbo, except
902
self.assertEqual(2, transform.rename_count)
904
def test_reuse_when_first_moved(self):
905
"""Don't avoid direct paths when it is safe to use them"""
906
transform, root = self.get_transform()
907
parent = transform.new_directory('parent', root)
908
child1 = transform.new_directory('child', parent)
909
transform.adjust_path('child1', parent, child1)
910
child2 = transform.new_directory('child', parent)
912
# limbo/new-1 => parent
913
self.assertEqual(1, transform.rename_count)
915
def test_reuse_after_cancel(self):
916
"""Don't avoid direct paths when it is safe to use them"""
917
transform, root = self.get_transform()
918
parent2 = transform.new_directory('parent2', root)
919
child1 = transform.new_directory('child1', parent2)
920
transform.cancel_creation(parent2)
921
transform.create_directory(parent2)
922
child2 = transform.new_directory('child1', parent2)
923
transform.adjust_path('child2', parent2, child1)
925
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
926
self.assertEqual(2, transform.rename_count)
928
def test_finalize_order(self):
929
"""Finalize must be done in child-to-parent order"""
930
transform, root = self.get_transform()
931
parent = transform.new_directory('parent', root)
932
child = transform.new_directory('child', parent)
936
self.fail('Tried to remove parent before child1')
938
def test_cancel_with_cancelled_child_should_succeed(self):
939
transform, root = self.get_transform()
940
parent = transform.new_directory('parent', root)
941
child = transform.new_directory('child', parent)
942
transform.cancel_creation(child)
943
transform.cancel_creation(parent)
946
def test_change_entry(self):
947
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
948
self.callDeprecated([txt], change_entry, None, None, None, None, None,
952
class TransformGroup(object):
953
def __init__(self, dirname, root_id):
956
self.wt = BzrDir.create_standalone_workingtree(dirname)
957
self.wt.set_root_id(root_id)
958
self.b = self.wt.branch
959
self.tt = TreeTransform(self.wt)
960
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
963
def conflict_text(tree, merge):
964
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
965
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
968
class TestTransformMerge(TestCaseInTempDir):
969
def test_text_merge(self):
970
root_id = generate_ids.gen_root_id()
971
base = TransformGroup("base", root_id)
972
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
973
base.tt.new_file('b', base.root, 'b1', 'b')
974
base.tt.new_file('c', base.root, 'c', 'c')
975
base.tt.new_file('d', base.root, 'd', 'd')
976
base.tt.new_file('e', base.root, 'e', 'e')
977
base.tt.new_file('f', base.root, 'f', 'f')
978
base.tt.new_directory('g', base.root, 'g')
979
base.tt.new_directory('h', base.root, 'h')
981
other = TransformGroup("other", root_id)
982
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
983
other.tt.new_file('b', other.root, 'b2', 'b')
984
other.tt.new_file('c', other.root, 'c2', 'c')
985
other.tt.new_file('d', other.root, 'd', 'd')
986
other.tt.new_file('e', other.root, 'e2', 'e')
987
other.tt.new_file('f', other.root, 'f', 'f')
988
other.tt.new_file('g', other.root, 'g', 'g')
989
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
990
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
992
this = TransformGroup("this", root_id)
993
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
994
this.tt.new_file('b', this.root, 'b', 'b')
995
this.tt.new_file('c', this.root, 'c', 'c')
996
this.tt.new_file('d', this.root, 'd2', 'd')
997
this.tt.new_file('e', this.root, 'e2', 'e')
998
this.tt.new_file('f', this.root, 'f', 'f')
999
this.tt.new_file('g', this.root, 'g', 'g')
1000
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1001
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1003
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1005
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1006
# three-way text conflict
1007
self.assertEqual(this.wt.get_file('b').read(),
1008
conflict_text('b', 'b2'))
1010
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1012
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1013
# Ambigious clean merge
1014
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1016
self.assertEqual(this.wt.get_file('f').read(), 'f')
1017
# Correct correct results when THIS == OTHER
1018
self.assertEqual(this.wt.get_file('g').read(), 'g')
1019
# Text conflict when THIS & OTHER are text and BASE is dir
1020
self.assertEqual(this.wt.get_file('h').read(),
1021
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1022
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1024
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1026
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1027
self.assertEqual(this.wt.get_file('i').read(),
1028
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1029
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1031
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1033
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1034
modified = ['a', 'b', 'c', 'h', 'i']
1035
merge_modified = this.wt.merge_modified()
1036
self.assertSubset(merge_modified, modified)
1037
self.assertEqual(len(merge_modified), len(modified))
1038
file(this.wt.id2abspath('a'), 'wb').write('booga')
1040
merge_modified = this.wt.merge_modified()
1041
self.assertSubset(merge_modified, modified)
1042
self.assertEqual(len(merge_modified), len(modified))
1046
def test_file_merge(self):
1047
if not has_symlinks():
1048
raise TestSkipped('Symlinks are not supported on this platform')
1049
root_id = generate_ids.gen_root_id()
1050
base = TransformGroup("BASE", root_id)
1051
this = TransformGroup("THIS", root_id)
1052
other = TransformGroup("OTHER", root_id)
1053
for tg in this, base, other:
1054
tg.tt.new_directory('a', tg.root, 'a')
1055
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1056
tg.tt.new_file('c', tg.root, 'c', 'c')
1057
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1058
targets = ((base, 'base-e', 'base-f', None, None),
1059
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1060
(other, 'other-e', None, 'other-g', 'other-h'))
1061
for tg, e_target, f_target, g_target, h_target in targets:
1062
for link, target in (('e', e_target), ('f', f_target),
1063
('g', g_target), ('h', h_target)):
1064
if target is not None:
1065
tg.tt.new_symlink(link, tg.root, target, link)
1067
for tg in this, base, other:
1069
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1070
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1071
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1072
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1073
for suffix in ('THIS', 'BASE', 'OTHER'):
1074
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1075
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1076
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1077
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1078
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1079
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1080
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1081
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1082
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1083
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1084
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1085
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1086
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1087
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1089
def test_filename_merge(self):
1090
root_id = generate_ids.gen_root_id()
1091
base = TransformGroup("BASE", root_id)
1092
this = TransformGroup("THIS", root_id)
1093
other = TransformGroup("OTHER", root_id)
1094
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1095
for t in [base, this, other]]
1096
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1097
for t in [base, this, other]]
1098
base.tt.new_directory('c', base_a, 'c')
1099
this.tt.new_directory('c1', this_a, 'c')
1100
other.tt.new_directory('c', other_b, 'c')
1102
base.tt.new_directory('d', base_a, 'd')
1103
this.tt.new_directory('d1', this_b, 'd')
1104
other.tt.new_directory('d', other_a, 'd')
1106
base.tt.new_directory('e', base_a, 'e')
1107
this.tt.new_directory('e', this_a, 'e')
1108
other.tt.new_directory('e1', other_b, 'e')
1110
base.tt.new_directory('f', base_a, 'f')
1111
this.tt.new_directory('f1', this_b, 'f')
1112
other.tt.new_directory('f1', other_b, 'f')
1114
for tg in [this, base, other]:
1116
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1117
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1118
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1119
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1120
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1122
def test_filename_merge_conflicts(self):
1123
root_id = generate_ids.gen_root_id()
1124
base = TransformGroup("BASE", root_id)
1125
this = TransformGroup("THIS", root_id)
1126
other = TransformGroup("OTHER", root_id)
1127
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1128
for t in [base, this, other]]
1129
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1130
for t in [base, this, other]]
1132
base.tt.new_file('g', base_a, 'g', 'g')
1133
other.tt.new_file('g1', other_b, 'g1', 'g')
1135
base.tt.new_file('h', base_a, 'h', 'h')
1136
this.tt.new_file('h1', this_b, 'h1', 'h')
1138
base.tt.new_file('i', base.root, 'i', 'i')
1139
other.tt.new_directory('i1', this_b, 'i')
1141
for tg in [this, base, other]:
1143
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1145
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1146
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1147
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1148
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1149
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1150
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1151
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1154
class TestBuildTree(tests.TestCaseWithTransport):
1156
def test_build_tree(self):
1157
if not has_symlinks():
1158
raise TestSkipped('Test requires symlink support')
1160
a = BzrDir.create_standalone_workingtree('a')
1162
file('a/foo/bar', 'wb').write('contents')
1163
os.symlink('a/foo/bar', 'a/foo/baz')
1164
a.add(['foo', 'foo/bar', 'foo/baz'])
1165
a.commit('initial commit')
1166
b = BzrDir.create_standalone_workingtree('b')
1167
basis = a.basis_tree()
1169
self.addCleanup(basis.unlock)
1170
build_tree(basis, b)
1171
self.assertIs(os.path.isdir('b/foo'), True)
1172
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1173
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1175
def test_build_with_references(self):
1176
tree = self.make_branch_and_tree('source',
1177
format='dirstate-with-subtree')
1178
subtree = self.make_branch_and_tree('source/subtree',
1179
format='dirstate-with-subtree')
1180
tree.add_reference(subtree)
1181
tree.commit('a revision')
1182
tree.branch.create_checkout('target')
1183
self.failUnlessExists('target')
1184
self.failUnlessExists('target/subtree')
1186
def test_file_conflict_handling(self):
1187
"""Ensure that when building trees, conflict handling is done"""
1188
source = self.make_branch_and_tree('source')
1189
target = self.make_branch_and_tree('target')
1190
self.build_tree(['source/file', 'target/file'])
1191
source.add('file', 'new-file')
1192
source.commit('added file')
1193
build_tree(source.basis_tree(), target)
1194
self.assertEqual([DuplicateEntry('Moved existing file to',
1195
'file.moved', 'file', None, 'new-file')],
1197
target2 = self.make_branch_and_tree('target2')
1198
target_file = file('target2/file', 'wb')
1200
source_file = file('source/file', 'rb')
1202
target_file.write(source_file.read())
1207
build_tree(source.basis_tree(), target2)
1208
self.assertEqual([], target2.conflicts())
1210
def test_symlink_conflict_handling(self):
1211
"""Ensure that when building trees, conflict handling is done"""
1212
if not has_symlinks():
1213
raise TestSkipped('Test requires symlink support')
1214
source = self.make_branch_and_tree('source')
1215
os.symlink('foo', 'source/symlink')
1216
source.add('symlink', 'new-symlink')
1217
source.commit('added file')
1218
target = self.make_branch_and_tree('target')
1219
os.symlink('bar', 'target/symlink')
1220
build_tree(source.basis_tree(), target)
1221
self.assertEqual([DuplicateEntry('Moved existing file to',
1222
'symlink.moved', 'symlink', None, 'new-symlink')],
1224
target = self.make_branch_and_tree('target2')
1225
os.symlink('foo', 'target2/symlink')
1226
build_tree(source.basis_tree(), target)
1227
self.assertEqual([], target.conflicts())
1229
def test_directory_conflict_handling(self):
1230
"""Ensure that when building trees, conflict handling is done"""
1231
source = self.make_branch_and_tree('source')
1232
target = self.make_branch_and_tree('target')
1233
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1234
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1235
source.commit('added file')
1236
build_tree(source.basis_tree(), target)
1237
self.assertEqual([], target.conflicts())
1238
self.failUnlessExists('target/dir1/file')
1240
# Ensure contents are merged
1241
target = self.make_branch_and_tree('target2')
1242
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1243
build_tree(source.basis_tree(), target)
1244
self.assertEqual([], target.conflicts())
1245
self.failUnlessExists('target2/dir1/file2')
1246
self.failUnlessExists('target2/dir1/file')
1248
# Ensure new contents are suppressed for existing branches
1249
target = self.make_branch_and_tree('target3')
1250
self.make_branch('target3/dir1')
1251
self.build_tree(['target3/dir1/file2'])
1252
build_tree(source.basis_tree(), target)
1253
self.failIfExists('target3/dir1/file')
1254
self.failUnlessExists('target3/dir1/file2')
1255
self.failUnlessExists('target3/dir1.diverted/file')
1256
self.assertEqual([DuplicateEntry('Diverted to',
1257
'dir1.diverted', 'dir1', 'new-dir1', None)],
1260
target = self.make_branch_and_tree('target4')
1261
self.build_tree(['target4/dir1/'])
1262
self.make_branch('target4/dir1/file')
1263
build_tree(source.basis_tree(), target)
1264
self.failUnlessExists('target4/dir1/file')
1265
self.assertEqual('directory', file_kind('target4/dir1/file'))
1266
self.failUnlessExists('target4/dir1/file.diverted')
1267
self.assertEqual([DuplicateEntry('Diverted to',
1268
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1271
def test_mixed_conflict_handling(self):
1272
"""Ensure that when building trees, conflict handling is done"""
1273
source = self.make_branch_and_tree('source')
1274
target = self.make_branch_and_tree('target')
1275
self.build_tree(['source/name', 'target/name/'])
1276
source.add('name', 'new-name')
1277
source.commit('added file')
1278
build_tree(source.basis_tree(), target)
1279
self.assertEqual([DuplicateEntry('Moved existing file to',
1280
'name.moved', 'name', None, 'new-name')], target.conflicts())
1282
def test_raises_in_populated(self):
1283
source = self.make_branch_and_tree('source')
1284
self.build_tree(['source/name'])
1286
source.commit('added name')
1287
target = self.make_branch_and_tree('target')
1288
self.build_tree(['target/name'])
1290
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1291
build_tree, source.basis_tree(), target)
1293
def test_build_tree_rename_count(self):
1294
source = self.make_branch_and_tree('source')
1295
self.build_tree(['source/file1', 'source/dir1/'])
1296
source.add(['file1', 'dir1'])
1297
source.commit('add1')
1298
target1 = self.make_branch_and_tree('target1')
1299
transform_result = build_tree(source.basis_tree(), target1)
1300
self.assertEqual(2, transform_result.rename_count)
1302
self.build_tree(['source/dir1/file2'])
1303
source.add(['dir1/file2'])
1304
source.commit('add3')
1305
target2 = self.make_branch_and_tree('target2')
1306
transform_result = build_tree(source.basis_tree(), target2)
1307
# children of non-root directories should not be renamed
1308
self.assertEqual(2, transform_result.rename_count)
1311
class MockTransform(object):
1313
def has_named_child(self, by_parent, parent_id, name):
1314
for child_id in by_parent[parent_id]:
1318
elif name == "name.~%s~" % child_id:
1323
class MockEntry(object):
1325
object.__init__(self)
1328
class TestGetBackupName(TestCase):
1329
def test_get_backup_name(self):
1330
tt = MockTransform()
1331
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1332
self.assertEqual(name, 'name.~1~')
1333
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1334
self.assertEqual(name, 'name.~2~')
1335
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1336
self.assertEqual(name, 'name.~1~')
1337
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1338
self.assertEqual(name, 'name.~1~')
1339
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1340
self.assertEqual(name, 'name.~4~')