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)
43
class TestTreeTransform(tests.TestCaseWithTransport):
46
super(TestTreeTransform, self).setUp()
47
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
50
def get_transform(self):
51
transform = TreeTransform(self.wt)
52
#self.addCleanup(transform.finalize)
53
return transform, transform.root
55
def test_existing_limbo(self):
56
limbo_name = urlutils.local_path_from_url(
57
self.wt._control_files.controlfilename('limbo'))
58
transform, root = self.get_transform()
59
os.mkdir(pathjoin(limbo_name, 'hehe'))
60
self.assertRaises(ImmortalLimbo, transform.apply)
61
self.assertRaises(LockError, self.wt.unlock)
62
self.assertRaises(ExistingLimbo, self.get_transform)
63
self.assertRaises(LockError, self.wt.unlock)
64
os.rmdir(pathjoin(limbo_name, 'hehe'))
66
transform, root = self.get_transform()
70
transform, root = self.get_transform()
71
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
72
imaginary_id = transform.trans_id_tree_path('imaginary')
73
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
74
self.assertEqual(imaginary_id, imaginary_id2)
75
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
76
self.assertEqual(transform.final_kind(root), 'directory')
77
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
78
trans_id = transform.create_path('name', root)
79
self.assertIs(transform.final_file_id(trans_id), None)
80
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
81
transform.create_file('contents', trans_id)
82
transform.set_executability(True, trans_id)
83
transform.version_file('my_pretties', trans_id)
84
self.assertRaises(DuplicateKey, transform.version_file,
85
'my_pretties', trans_id)
86
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
87
self.assertEqual(transform.final_parent(trans_id), root)
88
self.assertIs(transform.final_parent(root), ROOT_PARENT)
89
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
90
oz_id = transform.create_path('oz', root)
91
transform.create_directory(oz_id)
92
transform.version_file('ozzie', oz_id)
93
trans_id2 = transform.create_path('name2', root)
94
transform.create_file('contents', trans_id2)
95
transform.set_executability(False, trans_id2)
96
transform.version_file('my_pretties2', trans_id2)
97
modified_paths = transform.apply().modified_paths
98
self.assertEqual('contents', self.wt.get_file_byname('name').read())
99
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
100
self.assertIs(self.wt.is_executable('my_pretties'), True)
101
self.assertIs(self.wt.is_executable('my_pretties2'), False)
102
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
103
self.assertEqual(len(modified_paths), 3)
104
tree_mod_paths = [self.wt.id2abspath(f) for f in
105
('ozzie', 'my_pretties', 'my_pretties2')]
106
self.assertSubset(tree_mod_paths, modified_paths)
107
# is it safe to finalize repeatedly?
111
def test_convenience(self):
112
transform, root = self.get_transform()
113
trans_id = transform.new_file('name', root, 'contents',
115
oz = transform.new_directory('oz', root, 'oz-id')
116
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
117
toto = transform.new_file('toto', dorothy, 'toto-contents',
120
self.assertEqual(len(transform.find_conflicts()), 0)
122
self.assertRaises(ReusingTransform, transform.find_conflicts)
123
self.assertEqual('contents', file(self.wt.abspath('name')).read())
124
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
125
self.assertIs(self.wt.is_executable('my_pretties'), True)
126
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
127
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
128
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
130
self.assertEqual('toto-contents',
131
self.wt.get_file_byname('oz/dorothy/toto').read())
132
self.assertIs(self.wt.is_executable('toto-id'), False)
134
def test_tree_reference(self):
135
transform, root = self.get_transform()
136
tree = transform._tree
137
trans_id = transform.new_directory('reference', root, 'subtree-id')
138
transform.set_tree_reference('subtree-revision', trans_id)
141
self.addCleanup(tree.unlock)
142
self.assertEqual('subtree-revision',
143
tree.inventory['subtree-id'].reference_revision)
145
def test_conflicts(self):
146
transform, root = self.get_transform()
147
trans_id = transform.new_file('name', root, 'contents',
149
self.assertEqual(len(transform.find_conflicts()), 0)
150
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
151
self.assertEqual(transform.find_conflicts(),
152
[('duplicate', trans_id, trans_id2, 'name')])
153
self.assertRaises(MalformedTransform, transform.apply)
154
transform.adjust_path('name', trans_id, trans_id2)
155
self.assertEqual(transform.find_conflicts(),
156
[('non-directory parent', trans_id)])
157
tinman_id = transform.trans_id_tree_path('tinman')
158
transform.adjust_path('name', tinman_id, trans_id2)
159
self.assertEqual(transform.find_conflicts(),
160
[('unversioned parent', tinman_id),
161
('missing parent', tinman_id)])
162
lion_id = transform.create_path('lion', root)
163
self.assertEqual(transform.find_conflicts(),
164
[('unversioned parent', tinman_id),
165
('missing parent', tinman_id)])
166
transform.adjust_path('name', lion_id, trans_id2)
167
self.assertEqual(transform.find_conflicts(),
168
[('unversioned parent', lion_id),
169
('missing parent', lion_id)])
170
transform.version_file("Courage", lion_id)
171
self.assertEqual(transform.find_conflicts(),
172
[('missing parent', lion_id),
173
('versioning no contents', lion_id)])
174
transform.adjust_path('name2', root, trans_id2)
175
self.assertEqual(transform.find_conflicts(),
176
[('versioning no contents', lion_id)])
177
transform.create_file('Contents, okay?', lion_id)
178
transform.adjust_path('name2', trans_id2, trans_id2)
179
self.assertEqual(transform.find_conflicts(),
180
[('parent loop', trans_id2),
181
('non-directory parent', trans_id2)])
182
transform.adjust_path('name2', root, trans_id2)
183
oz_id = transform.new_directory('oz', root)
184
transform.set_executability(True, oz_id)
185
self.assertEqual(transform.find_conflicts(),
186
[('unversioned executability', oz_id)])
187
transform.version_file('oz-id', oz_id)
188
self.assertEqual(transform.find_conflicts(),
189
[('non-file executability', oz_id)])
190
transform.set_executability(None, oz_id)
191
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
193
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
194
self.assertEqual('contents', file(self.wt.abspath('name')).read())
195
transform2, root = self.get_transform()
196
oz_id = transform2.trans_id_tree_file_id('oz-id')
197
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
198
result = transform2.find_conflicts()
199
fp = FinalPaths(transform2)
200
self.assert_('oz/tip' in transform2._tree_path_ids)
201
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
202
self.assertEqual(len(result), 2)
203
self.assertEqual((result[0][0], result[0][1]),
204
('duplicate', newtip))
205
self.assertEqual((result[1][0], result[1][2]),
206
('duplicate id', newtip))
207
transform2.finalize()
208
transform3 = TreeTransform(self.wt)
209
self.addCleanup(transform3.finalize)
210
oz_id = transform3.trans_id_tree_file_id('oz-id')
211
transform3.delete_contents(oz_id)
212
self.assertEqual(transform3.find_conflicts(),
213
[('missing parent', oz_id)])
214
root_id = transform3.root
215
tip_id = transform3.trans_id_tree_file_id('tip-id')
216
transform3.adjust_path('tip', root_id, tip_id)
219
def test_add_del(self):
220
start, root = self.get_transform()
221
start.new_directory('a', root, 'a')
223
transform, root = self.get_transform()
224
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
225
transform.new_directory('a', root, 'a')
228
def test_unversioning(self):
229
create_tree, root = self.get_transform()
230
parent_id = create_tree.new_directory('parent', root, 'parent-id')
231
create_tree.new_file('child', parent_id, 'child', 'child-id')
233
unversion = TreeTransform(self.wt)
234
self.addCleanup(unversion.finalize)
235
parent = unversion.trans_id_tree_path('parent')
236
unversion.unversion_file(parent)
237
self.assertEqual(unversion.find_conflicts(),
238
[('unversioned parent', parent_id)])
239
file_id = unversion.trans_id_tree_file_id('child-id')
240
unversion.unversion_file(file_id)
243
def test_name_invariants(self):
244
create_tree, root = self.get_transform()
246
root = create_tree.root
247
create_tree.new_file('name1', root, 'hello1', 'name1')
248
create_tree.new_file('name2', root, 'hello2', 'name2')
249
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
250
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
251
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
252
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
255
mangle_tree,root = self.get_transform()
256
root = mangle_tree.root
258
name1 = mangle_tree.trans_id_tree_file_id('name1')
259
name2 = mangle_tree.trans_id_tree_file_id('name2')
260
mangle_tree.adjust_path('name2', root, name1)
261
mangle_tree.adjust_path('name1', root, name2)
263
#tests for deleting parent directories
264
ddir = mangle_tree.trans_id_tree_file_id('ddir')
265
mangle_tree.delete_contents(ddir)
266
dfile = mangle_tree.trans_id_tree_file_id('dfile')
267
mangle_tree.delete_versioned(dfile)
268
mangle_tree.unversion_file(dfile)
269
mfile = mangle_tree.trans_id_tree_file_id('mfile')
270
mangle_tree.adjust_path('mfile', root, mfile)
272
#tests for adding parent directories
273
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
274
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
275
mangle_tree.adjust_path('mfile2', newdir, mfile2)
276
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
277
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
278
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
279
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
281
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
282
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
283
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
284
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
285
self.assertEqual(file(mfile2_path).read(), 'later2')
286
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
287
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
288
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
289
self.assertEqual(file(newfile_path).read(), 'hello3')
290
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
291
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
292
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
294
def test_both_rename(self):
295
create_tree,root = self.get_transform()
296
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
297
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
299
mangle_tree,root = self.get_transform()
300
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
301
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
302
mangle_tree.adjust_path('test', root, selftest)
303
mangle_tree.adjust_path('test_too_much', root, selftest)
304
mangle_tree.set_executability(True, blackbox)
307
def test_both_rename2(self):
308
create_tree,root = self.get_transform()
309
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
310
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
311
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
312
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
315
mangle_tree,root = self.get_transform()
316
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
317
tests = mangle_tree.trans_id_tree_file_id('tests-id')
318
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
319
mangle_tree.adjust_path('selftest', bzrlib, tests)
320
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
321
mangle_tree.set_executability(True, test_too_much)
324
def test_both_rename3(self):
325
create_tree,root = self.get_transform()
326
tests = create_tree.new_directory('tests', root, 'tests-id')
327
create_tree.new_file('test_too_much.py', tests, 'hello1',
330
mangle_tree,root = self.get_transform()
331
tests = mangle_tree.trans_id_tree_file_id('tests-id')
332
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
333
mangle_tree.adjust_path('selftest', root, tests)
334
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
335
mangle_tree.set_executability(True, test_too_much)
338
def test_move_dangling_ie(self):
339
create_tree, root = self.get_transform()
341
root = create_tree.root
342
create_tree.new_file('name1', root, 'hello1', 'name1')
344
delete_contents, root = self.get_transform()
345
file = delete_contents.trans_id_tree_file_id('name1')
346
delete_contents.delete_contents(file)
347
delete_contents.apply()
348
move_id, root = self.get_transform()
349
name1 = move_id.trans_id_tree_file_id('name1')
350
newdir = move_id.new_directory('dir', root, 'newdir')
351
move_id.adjust_path('name2', newdir, name1)
354
def test_replace_dangling_ie(self):
355
create_tree, root = self.get_transform()
357
root = create_tree.root
358
create_tree.new_file('name1', root, 'hello1', 'name1')
360
delete_contents = TreeTransform(self.wt)
361
self.addCleanup(delete_contents.finalize)
362
file = delete_contents.trans_id_tree_file_id('name1')
363
delete_contents.delete_contents(file)
364
delete_contents.apply()
365
delete_contents.finalize()
366
replace = TreeTransform(self.wt)
367
self.addCleanup(replace.finalize)
368
name2 = replace.new_file('name2', root, 'hello2', 'name1')
369
conflicts = replace.find_conflicts()
370
name1 = replace.trans_id_tree_file_id('name1')
371
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
372
resolve_conflicts(replace)
375
def test_symlinks(self):
376
if not has_symlinks():
377
raise TestSkipped('Symlinks are not supported on this platform')
378
transform,root = self.get_transform()
379
oz_id = transform.new_directory('oz', root, 'oz-id')
380
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
382
wiz_id = transform.create_path('wizard2', oz_id)
383
transform.create_symlink('behind_curtain', wiz_id)
384
transform.version_file('wiz-id2', wiz_id)
385
transform.set_executability(True, wiz_id)
386
self.assertEqual(transform.find_conflicts(),
387
[('non-file executability', wiz_id)])
388
transform.set_executability(None, wiz_id)
390
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
391
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
392
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
394
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
397
def get_conflicted(self):
398
create,root = self.get_transform()
399
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
400
oz = create.new_directory('oz', root, 'oz-id')
401
create.new_directory('emeraldcity', oz, 'emerald-id')
403
conflicts,root = self.get_transform()
404
# set up duplicate entry, duplicate id
405
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
407
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
408
oz = conflicts.trans_id_tree_file_id('oz-id')
409
# set up DeletedParent parent conflict
410
conflicts.delete_versioned(oz)
411
emerald = conflicts.trans_id_tree_file_id('emerald-id')
412
# set up MissingParent conflict
413
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
414
conflicts.adjust_path('munchkincity', root, munchkincity)
415
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
417
conflicts.adjust_path('emeraldcity', emerald, emerald)
418
return conflicts, emerald, oz, old_dorothy, new_dorothy
420
def test_conflict_resolution(self):
421
conflicts, emerald, oz, old_dorothy, new_dorothy =\
422
self.get_conflicted()
423
resolve_conflicts(conflicts)
424
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
425
self.assertIs(conflicts.final_file_id(old_dorothy), None)
426
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
427
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
428
self.assertEqual(conflicts.final_parent(emerald), oz)
431
def test_cook_conflicts(self):
432
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
433
raw_conflicts = resolve_conflicts(tt)
434
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
435
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
436
'dorothy', None, 'dorothy-id')
437
self.assertEqual(cooked_conflicts[0], duplicate)
438
duplicate_id = DuplicateID('Unversioned existing file',
439
'dorothy.moved', 'dorothy', None,
441
self.assertEqual(cooked_conflicts[1], duplicate_id)
442
missing_parent = MissingParent('Created directory', 'munchkincity',
444
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
445
self.assertEqual(cooked_conflicts[2], missing_parent)
446
unversioned_parent = UnversionedParent('Versioned directory',
449
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
451
self.assertEqual(cooked_conflicts[3], unversioned_parent)
452
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
453
'oz/emeraldcity', 'emerald-id', 'emerald-id')
454
self.assertEqual(cooked_conflicts[4], deleted_parent)
455
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
456
self.assertEqual(cooked_conflicts[6], parent_loop)
457
self.assertEqual(len(cooked_conflicts), 7)
460
def test_string_conflicts(self):
461
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
462
raw_conflicts = resolve_conflicts(tt)
463
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
465
conflicts_s = [str(c) for c in cooked_conflicts]
466
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
467
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
468
'Moved existing file to '
470
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
471
'Unversioned existing file '
473
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
474
' munchkincity. Created directory.')
475
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
476
' versioned, but has versioned'
477
' children. Versioned directory.')
478
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
479
" is not empty. Not deleting.")
480
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
481
' versioned, but has versioned'
482
' children. Versioned directory.')
483
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
484
' oz/emeraldcity. Cancelled move.')
486
def test_moving_versioned_directories(self):
487
create, root = self.get_transform()
488
kansas = create.new_directory('kansas', root, 'kansas-id')
489
create.new_directory('house', kansas, 'house-id')
490
create.new_directory('oz', root, 'oz-id')
492
cyclone, root = self.get_transform()
493
oz = cyclone.trans_id_tree_file_id('oz-id')
494
house = cyclone.trans_id_tree_file_id('house-id')
495
cyclone.adjust_path('house', oz, house)
498
def test_moving_root(self):
499
create, root = self.get_transform()
500
fun = create.new_directory('fun', root, 'fun-id')
501
create.new_directory('sun', root, 'sun-id')
502
create.new_directory('moon', root, 'moon')
504
transform, root = self.get_transform()
505
transform.adjust_root_path('oldroot', fun)
506
new_root=transform.trans_id_tree_path('')
507
transform.version_file('new-root', new_root)
510
def test_renames(self):
511
create, root = self.get_transform()
512
old = create.new_directory('old-parent', root, 'old-id')
513
intermediate = create.new_directory('intermediate', old, 'im-id')
514
myfile = create.new_file('myfile', intermediate, 'myfile-text',
517
rename, root = self.get_transform()
518
old = rename.trans_id_file_id('old-id')
519
rename.adjust_path('new', root, old)
520
myfile = rename.trans_id_file_id('myfile-id')
521
rename.set_executability(True, myfile)
524
def test_find_interesting(self):
525
create, root = self.get_transform()
527
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
528
create.new_file('uvfile', root, 'othertext')
530
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
531
find_interesting, wt, wt, ['vfile'])
532
self.assertEqual(result, set(['myfile-id']))
534
def test_set_executability_order(self):
535
"""Ensure that executability behaves the same, no matter what order.
537
- create file and set executability simultaneously
538
- create file and set executability afterward
539
- unsetting the executability of a file whose executability has not been
540
declared should throw an exception (this may happen when a
541
merge attempts to create a file with a duplicate ID)
543
transform, root = self.get_transform()
545
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
547
sac = transform.new_file('set_after_creation', root,
548
'Set after creation', 'sac')
549
transform.set_executability(True, sac)
550
uws = transform.new_file('unset_without_set', root, 'Unset badly',
552
self.assertRaises(KeyError, transform.set_executability, None, uws)
554
self.assertTrue(wt.is_executable('soc'))
555
self.assertTrue(wt.is_executable('sac'))
557
def test_preserve_mode(self):
558
"""File mode is preserved when replacing content"""
559
if sys.platform == 'win32':
560
raise TestSkipped('chmod has no effect on win32')
561
transform, root = self.get_transform()
562
transform.new_file('file1', root, 'contents', 'file1-id', True)
564
self.assertTrue(self.wt.is_executable('file1-id'))
565
transform, root = self.get_transform()
566
file1_id = transform.trans_id_tree_file_id('file1-id')
567
transform.delete_contents(file1_id)
568
transform.create_file('contents2', file1_id)
570
self.assertTrue(self.wt.is_executable('file1-id'))
572
def test__set_mode_stats_correctly(self):
573
"""_set_mode stats to determine file mode."""
574
if sys.platform == 'win32':
575
raise TestSkipped('chmod has no effect on win32')
579
def instrumented_stat(path):
580
stat_paths.append(path)
581
return real_stat(path)
583
transform, root = self.get_transform()
585
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
586
file_id='bar-id-1', executable=False)
589
transform, root = self.get_transform()
590
bar1_id = transform.trans_id_tree_path('bar')
591
bar2_id = transform.trans_id_tree_path('bar2')
593
os.stat = instrumented_stat
594
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
599
bar1_abspath = self.wt.abspath('bar')
600
self.assertEqual([bar1_abspath], stat_paths)
602
def test_iter_changes(self):
603
self.wt.set_root_id('eert_toor')
604
transform, root = self.get_transform()
605
transform.new_file('old', root, 'blah', 'id-1', True)
607
transform, root = self.get_transform()
609
self.assertEqual([], list(transform._iter_changes()))
610
old = transform.trans_id_tree_file_id('id-1')
611
transform.unversion_file(old)
612
self.assertEqual([('id-1', ('old', None), False, (True, False),
613
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
614
(True, True))], list(transform._iter_changes()))
615
transform.new_directory('new', root, 'id-1')
616
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
617
('eert_toor', 'eert_toor'), ('old', 'new'),
618
('file', 'directory'),
619
(True, False))], list(transform._iter_changes()))
623
def test_iter_changes_new(self):
624
self.wt.set_root_id('eert_toor')
625
transform, root = self.get_transform()
626
transform.new_file('old', root, 'blah')
628
transform, root = self.get_transform()
630
old = transform.trans_id_tree_path('old')
631
transform.version_file('id-1', old)
632
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
633
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
634
(False, False))], list(transform._iter_changes()))
638
def test_iter_changes_modifications(self):
639
self.wt.set_root_id('eert_toor')
640
transform, root = self.get_transform()
641
transform.new_file('old', root, 'blah', 'id-1')
642
transform.new_file('new', root, 'blah')
643
transform.new_directory('subdir', root, 'subdir-id')
645
transform, root = self.get_transform()
647
old = transform.trans_id_tree_path('old')
648
subdir = transform.trans_id_tree_file_id('subdir-id')
649
new = transform.trans_id_tree_path('new')
650
self.assertEqual([], list(transform._iter_changes()))
653
transform.delete_contents(old)
654
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
655
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
656
(False, False))], list(transform._iter_changes()))
659
transform.create_file('blah', old)
660
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
661
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
662
(False, False))], list(transform._iter_changes()))
663
transform.cancel_deletion(old)
664
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
665
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
666
(False, False))], list(transform._iter_changes()))
667
transform.cancel_creation(old)
669
# move file_id to a different file
670
self.assertEqual([], list(transform._iter_changes()))
671
transform.unversion_file(old)
672
transform.version_file('id-1', new)
673
transform.adjust_path('old', root, new)
674
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
675
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
676
(False, False))], list(transform._iter_changes()))
677
transform.cancel_versioning(new)
678
transform._removed_id = set()
681
self.assertEqual([], list(transform._iter_changes()))
682
transform.set_executability(True, old)
683
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
684
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
685
(False, True))], list(transform._iter_changes()))
686
transform.set_executability(None, old)
689
self.assertEqual([], list(transform._iter_changes()))
690
transform.adjust_path('new', root, old)
691
transform._new_parent = {}
692
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
693
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
694
(False, False))], list(transform._iter_changes()))
695
transform._new_name = {}
698
self.assertEqual([], list(transform._iter_changes()))
699
transform.adjust_path('new', subdir, old)
700
transform._new_name = {}
701
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
702
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
703
('file', 'file'), (False, False))],
704
list(transform._iter_changes()))
705
transform._new_path = {}
710
def test_iter_changes_modified_bleed(self):
711
self.wt.set_root_id('eert_toor')
712
"""Modified flag should not bleed from one change to another"""
713
# unfortunately, we have no guarantee that file1 (which is modified)
714
# will be applied before file2. And if it's applied after file2, it
715
# obviously can't bleed into file2's change output. But for now, it
717
transform, root = self.get_transform()
718
transform.new_file('file1', root, 'blah', 'id-1')
719
transform.new_file('file2', root, 'blah', 'id-2')
721
transform, root = self.get_transform()
723
transform.delete_contents(transform.trans_id_file_id('id-1'))
724
transform.set_executability(True,
725
transform.trans_id_file_id('id-2'))
726
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
727
('eert_toor', 'eert_toor'), ('file1', u'file1'),
728
('file', None), (False, False)),
729
('id-2', (u'file2', u'file2'), False, (True, True),
730
('eert_toor', 'eert_toor'), ('file2', u'file2'),
731
('file', 'file'), (False, True))],
732
list(transform._iter_changes()))
736
def test_iter_changes_move_missing(self):
737
"""Test moving ids with no files around"""
738
self.wt.set_root_id('toor_eert')
739
# Need two steps because versioning a non-existant file is a conflict.
740
transform, root = self.get_transform()
741
transform.new_directory('floater', root, 'floater-id')
743
transform, root = self.get_transform()
744
transform.delete_contents(transform.trans_id_tree_path('floater'))
746
transform, root = self.get_transform()
747
floater = transform.trans_id_tree_path('floater')
749
transform.adjust_path('flitter', root, floater)
750
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
751
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
752
(None, None), (False, False))], list(transform._iter_changes()))
756
def test_iter_changes_pointless(self):
757
"""Ensure that no-ops are not treated as modifications"""
758
self.wt.set_root_id('eert_toor')
759
transform, root = self.get_transform()
760
transform.new_file('old', root, 'blah', 'id-1')
761
transform.new_directory('subdir', root, 'subdir-id')
763
transform, root = self.get_transform()
765
old = transform.trans_id_tree_path('old')
766
subdir = transform.trans_id_tree_file_id('subdir-id')
767
self.assertEqual([], list(transform._iter_changes()))
768
transform.delete_contents(subdir)
769
transform.create_directory(subdir)
770
transform.set_executability(False, old)
771
transform.unversion_file(old)
772
transform.version_file('id-1', old)
773
transform.adjust_path('old', root, old)
774
self.assertEqual([], list(transform._iter_changes()))
778
def test_rename_count(self):
779
transform, root = self.get_transform()
780
transform.new_file('name1', root, 'contents')
781
self.assertEqual(transform.rename_count, 0)
783
self.assertEqual(transform.rename_count, 1)
784
transform2, root = self.get_transform()
785
transform2.adjust_path('name2', root,
786
transform2.trans_id_tree_path('name1'))
787
self.assertEqual(transform2.rename_count, 0)
789
self.assertEqual(transform2.rename_count, 2)
791
def test_change_parent(self):
792
"""Ensure that after we change a parent, the results are still right.
794
Renames and parent changes on pending transforms can happen as part
795
of conflict resolution, and are explicitly permitted by the
798
This test ensures they work correctly with the rename-avoidance
801
transform, root = self.get_transform()
802
parent1 = transform.new_directory('parent1', root)
803
child1 = transform.new_file('child1', parent1, 'contents')
804
parent2 = transform.new_directory('parent2', root)
805
transform.adjust_path('child1', parent2, child1)
807
self.failIfExists(self.wt.abspath('parent1/child1'))
808
self.failUnlessExists(self.wt.abspath('parent2/child1'))
809
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
810
# no rename for child1 (counting only renames during apply)
811
self.failUnlessEqual(2, transform.rename_count)
813
def test_cancel_parent(self):
814
"""Cancelling a parent doesn't cause deletion of a non-empty directory
816
This is like the test_change_parent, except that we cancel the parent
817
before adjusting the path. The transform must detect that the
818
directory is non-empty, and move children to safe locations.
820
transform, root = self.get_transform()
821
parent1 = transform.new_directory('parent1', root)
822
child1 = transform.new_file('child1', parent1, 'contents')
823
child2 = transform.new_file('child2', parent1, 'contents')
825
transform.cancel_creation(parent1)
827
self.fail('Failed to move child1 before deleting parent1')
828
transform.cancel_creation(child2)
829
transform.create_directory(parent1)
831
transform.cancel_creation(parent1)
832
# If the transform incorrectly believes that child2 is still in
833
# parent1's limbo directory, it will try to rename it and fail
834
# because was already moved by the first cancel_creation.
836
self.fail('Transform still thinks child2 is a child of parent1')
837
parent2 = transform.new_directory('parent2', root)
838
transform.adjust_path('child1', parent2, child1)
840
self.failIfExists(self.wt.abspath('parent1'))
841
self.failUnlessExists(self.wt.abspath('parent2/child1'))
842
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
843
self.failUnlessEqual(2, transform.rename_count)
845
def test_adjust_and_cancel(self):
846
"""Make sure adjust_path keeps track of limbo children properly"""
847
transform, root = self.get_transform()
848
parent1 = transform.new_directory('parent1', root)
849
child1 = transform.new_file('child1', parent1, 'contents')
850
parent2 = transform.new_directory('parent2', root)
851
transform.adjust_path('child1', parent2, child1)
852
transform.cancel_creation(child1)
854
transform.cancel_creation(parent1)
855
# if the transform thinks child1 is still in parent1's limbo
856
# directory, it will attempt to move it and fail.
858
self.fail('Transform still thinks child1 is a child of parent1')
861
def test_noname_contents(self):
862
"""TreeTransform should permit deferring naming files."""
863
transform, root = self.get_transform()
864
parent = transform.trans_id_file_id('parent-id')
866
transform.create_directory(parent)
868
self.fail("Can't handle contents with no name")
871
def test_noname_contents_nested(self):
872
"""TreeTransform should permit deferring naming files."""
873
transform, root = self.get_transform()
874
parent = transform.trans_id_file_id('parent-id')
876
transform.create_directory(parent)
878
self.fail("Can't handle contents with no name")
879
child = transform.new_directory('child', parent)
880
transform.adjust_path('parent', root, parent)
882
self.failUnlessExists(self.wt.abspath('parent/child'))
883
self.assertEqual(1, transform.rename_count)
885
def test_reuse_name(self):
886
"""Avoid reusing the same limbo name for different files"""
887
transform, root = self.get_transform()
888
parent = transform.new_directory('parent', root)
889
child1 = transform.new_directory('child', parent)
891
child2 = transform.new_directory('child', parent)
893
self.fail('Tranform tried to use the same limbo name twice')
894
transform.adjust_path('child2', parent, child2)
896
# limbo/new-1 => parent, limbo/new-3 => parent/child2
897
# child2 is put into top-level limbo because child1 has already
898
# claimed the direct limbo path when child2 is created. There is no
899
# advantage in renaming files once they're in top-level limbo, except
901
self.assertEqual(2, transform.rename_count)
903
def test_reuse_when_first_moved(self):
904
"""Don't avoid direct paths when it is safe to use them"""
905
transform, root = self.get_transform()
906
parent = transform.new_directory('parent', root)
907
child1 = transform.new_directory('child', parent)
908
transform.adjust_path('child1', parent, child1)
909
child2 = transform.new_directory('child', parent)
911
# limbo/new-1 => parent
912
self.assertEqual(1, transform.rename_count)
914
def test_reuse_after_cancel(self):
915
"""Don't avoid direct paths when it is safe to use them"""
916
transform, root = self.get_transform()
917
parent2 = transform.new_directory('parent2', root)
918
child1 = transform.new_directory('child1', parent2)
919
transform.cancel_creation(parent2)
920
transform.create_directory(parent2)
921
child2 = transform.new_directory('child1', parent2)
922
transform.adjust_path('child2', parent2, child1)
924
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
925
self.assertEqual(2, transform.rename_count)
927
def test_finalize_order(self):
928
"""Finalize must be done in child-to-parent order"""
929
transform, root = self.get_transform()
930
parent = transform.new_directory('parent', root)
931
child = transform.new_directory('child', parent)
935
self.fail('Tried to remove parent before child1')
937
def test_cancel_with_cancelled_child_should_succeed(self):
938
transform, root = self.get_transform()
939
parent = transform.new_directory('parent', root)
940
child = transform.new_directory('child', parent)
941
transform.cancel_creation(child)
942
transform.cancel_creation(parent)
946
class TransformGroup(object):
947
def __init__(self, dirname, root_id):
950
self.wt = BzrDir.create_standalone_workingtree(dirname)
951
self.wt.set_root_id(root_id)
952
self.b = self.wt.branch
953
self.tt = TreeTransform(self.wt)
954
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
957
def conflict_text(tree, merge):
958
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
959
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
962
class TestTransformMerge(TestCaseInTempDir):
963
def test_text_merge(self):
964
root_id = generate_ids.gen_root_id()
965
base = TransformGroup("base", root_id)
966
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
967
base.tt.new_file('b', base.root, 'b1', 'b')
968
base.tt.new_file('c', base.root, 'c', 'c')
969
base.tt.new_file('d', base.root, 'd', 'd')
970
base.tt.new_file('e', base.root, 'e', 'e')
971
base.tt.new_file('f', base.root, 'f', 'f')
972
base.tt.new_directory('g', base.root, 'g')
973
base.tt.new_directory('h', base.root, 'h')
975
other = TransformGroup("other", root_id)
976
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
977
other.tt.new_file('b', other.root, 'b2', 'b')
978
other.tt.new_file('c', other.root, 'c2', 'c')
979
other.tt.new_file('d', other.root, 'd', 'd')
980
other.tt.new_file('e', other.root, 'e2', 'e')
981
other.tt.new_file('f', other.root, 'f', 'f')
982
other.tt.new_file('g', other.root, 'g', 'g')
983
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
984
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
986
this = TransformGroup("this", root_id)
987
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
988
this.tt.new_file('b', this.root, 'b', 'b')
989
this.tt.new_file('c', this.root, 'c', 'c')
990
this.tt.new_file('d', this.root, 'd2', 'd')
991
this.tt.new_file('e', this.root, 'e2', 'e')
992
this.tt.new_file('f', this.root, 'f', 'f')
993
this.tt.new_file('g', this.root, 'g', 'g')
994
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
995
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
997
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
999
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1000
# three-way text conflict
1001
self.assertEqual(this.wt.get_file('b').read(),
1002
conflict_text('b', 'b2'))
1004
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1006
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1007
# Ambigious clean merge
1008
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1010
self.assertEqual(this.wt.get_file('f').read(), 'f')
1011
# Correct correct results when THIS == OTHER
1012
self.assertEqual(this.wt.get_file('g').read(), 'g')
1013
# Text conflict when THIS & OTHER are text and BASE is dir
1014
self.assertEqual(this.wt.get_file('h').read(),
1015
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1016
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1018
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1020
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1021
self.assertEqual(this.wt.get_file('i').read(),
1022
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1023
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1025
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1027
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1028
modified = ['a', 'b', 'c', 'h', 'i']
1029
merge_modified = this.wt.merge_modified()
1030
self.assertSubset(merge_modified, modified)
1031
self.assertEqual(len(merge_modified), len(modified))
1032
file(this.wt.id2abspath('a'), 'wb').write('booga')
1034
merge_modified = this.wt.merge_modified()
1035
self.assertSubset(merge_modified, modified)
1036
self.assertEqual(len(merge_modified), len(modified))
1040
def test_file_merge(self):
1041
if not has_symlinks():
1042
raise TestSkipped('Symlinks are not supported on this platform')
1043
root_id = generate_ids.gen_root_id()
1044
base = TransformGroup("BASE", root_id)
1045
this = TransformGroup("THIS", root_id)
1046
other = TransformGroup("OTHER", root_id)
1047
for tg in this, base, other:
1048
tg.tt.new_directory('a', tg.root, 'a')
1049
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1050
tg.tt.new_file('c', tg.root, 'c', 'c')
1051
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1052
targets = ((base, 'base-e', 'base-f', None, None),
1053
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1054
(other, 'other-e', None, 'other-g', 'other-h'))
1055
for tg, e_target, f_target, g_target, h_target in targets:
1056
for link, target in (('e', e_target), ('f', f_target),
1057
('g', g_target), ('h', h_target)):
1058
if target is not None:
1059
tg.tt.new_symlink(link, tg.root, target, link)
1061
for tg in this, base, other:
1063
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1064
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1065
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1066
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1067
for suffix in ('THIS', 'BASE', 'OTHER'):
1068
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1069
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1070
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1071
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1072
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1073
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1074
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1075
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1076
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1077
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1078
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1079
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1080
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1081
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1083
def test_filename_merge(self):
1084
root_id = generate_ids.gen_root_id()
1085
base = TransformGroup("BASE", root_id)
1086
this = TransformGroup("THIS", root_id)
1087
other = TransformGroup("OTHER", root_id)
1088
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1089
for t in [base, this, other]]
1090
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1091
for t in [base, this, other]]
1092
base.tt.new_directory('c', base_a, 'c')
1093
this.tt.new_directory('c1', this_a, 'c')
1094
other.tt.new_directory('c', other_b, 'c')
1096
base.tt.new_directory('d', base_a, 'd')
1097
this.tt.new_directory('d1', this_b, 'd')
1098
other.tt.new_directory('d', other_a, 'd')
1100
base.tt.new_directory('e', base_a, 'e')
1101
this.tt.new_directory('e', this_a, 'e')
1102
other.tt.new_directory('e1', other_b, 'e')
1104
base.tt.new_directory('f', base_a, 'f')
1105
this.tt.new_directory('f1', this_b, 'f')
1106
other.tt.new_directory('f1', other_b, 'f')
1108
for tg in [this, base, other]:
1110
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1111
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1112
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1113
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1114
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1116
def test_filename_merge_conflicts(self):
1117
root_id = generate_ids.gen_root_id()
1118
base = TransformGroup("BASE", root_id)
1119
this = TransformGroup("THIS", root_id)
1120
other = TransformGroup("OTHER", root_id)
1121
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1122
for t in [base, this, other]]
1123
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1124
for t in [base, this, other]]
1126
base.tt.new_file('g', base_a, 'g', 'g')
1127
other.tt.new_file('g1', other_b, 'g1', 'g')
1129
base.tt.new_file('h', base_a, 'h', 'h')
1130
this.tt.new_file('h1', this_b, 'h1', 'h')
1132
base.tt.new_file('i', base.root, 'i', 'i')
1133
other.tt.new_directory('i1', this_b, 'i')
1135
for tg in [this, base, other]:
1137
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1139
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1140
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1141
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1142
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1143
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1144
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1145
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1148
class TestBuildTree(tests.TestCaseWithTransport):
1150
def test_build_tree(self):
1151
if not has_symlinks():
1152
raise TestSkipped('Test requires symlink support')
1154
a = BzrDir.create_standalone_workingtree('a')
1156
file('a/foo/bar', 'wb').write('contents')
1157
os.symlink('a/foo/bar', 'a/foo/baz')
1158
a.add(['foo', 'foo/bar', 'foo/baz'])
1159
a.commit('initial commit')
1160
b = BzrDir.create_standalone_workingtree('b')
1161
basis = a.basis_tree()
1163
self.addCleanup(basis.unlock)
1164
build_tree(basis, b)
1165
self.assertIs(os.path.isdir('b/foo'), True)
1166
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1167
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1169
def test_build_with_references(self):
1170
tree = self.make_branch_and_tree('source',
1171
format='dirstate-with-subtree')
1172
subtree = self.make_branch_and_tree('source/subtree',
1173
format='dirstate-with-subtree')
1174
tree.add_reference(subtree)
1175
tree.commit('a revision')
1176
tree.branch.create_checkout('target')
1177
self.failUnlessExists('target')
1178
self.failUnlessExists('target/subtree')
1180
def test_file_conflict_handling(self):
1181
"""Ensure that when building trees, conflict handling is done"""
1182
source = self.make_branch_and_tree('source')
1183
target = self.make_branch_and_tree('target')
1184
self.build_tree(['source/file', 'target/file'])
1185
source.add('file', 'new-file')
1186
source.commit('added file')
1187
build_tree(source.basis_tree(), target)
1188
self.assertEqual([DuplicateEntry('Moved existing file to',
1189
'file.moved', 'file', None, 'new-file')],
1191
target2 = self.make_branch_and_tree('target2')
1192
target_file = file('target2/file', 'wb')
1194
source_file = file('source/file', 'rb')
1196
target_file.write(source_file.read())
1201
build_tree(source.basis_tree(), target2)
1202
self.assertEqual([], target2.conflicts())
1204
def test_symlink_conflict_handling(self):
1205
"""Ensure that when building trees, conflict handling is done"""
1206
if not has_symlinks():
1207
raise TestSkipped('Test requires symlink support')
1208
source = self.make_branch_and_tree('source')
1209
os.symlink('foo', 'source/symlink')
1210
source.add('symlink', 'new-symlink')
1211
source.commit('added file')
1212
target = self.make_branch_and_tree('target')
1213
os.symlink('bar', 'target/symlink')
1214
build_tree(source.basis_tree(), target)
1215
self.assertEqual([DuplicateEntry('Moved existing file to',
1216
'symlink.moved', 'symlink', None, 'new-symlink')],
1218
target = self.make_branch_and_tree('target2')
1219
os.symlink('foo', 'target2/symlink')
1220
build_tree(source.basis_tree(), target)
1221
self.assertEqual([], target.conflicts())
1223
def test_directory_conflict_handling(self):
1224
"""Ensure that when building trees, conflict handling is done"""
1225
source = self.make_branch_and_tree('source')
1226
target = self.make_branch_and_tree('target')
1227
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1228
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1229
source.commit('added file')
1230
build_tree(source.basis_tree(), target)
1231
self.assertEqual([], target.conflicts())
1232
self.failUnlessExists('target/dir1/file')
1234
# Ensure contents are merged
1235
target = self.make_branch_and_tree('target2')
1236
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1237
build_tree(source.basis_tree(), target)
1238
self.assertEqual([], target.conflicts())
1239
self.failUnlessExists('target2/dir1/file2')
1240
self.failUnlessExists('target2/dir1/file')
1242
# Ensure new contents are suppressed for existing branches
1243
target = self.make_branch_and_tree('target3')
1244
self.make_branch('target3/dir1')
1245
self.build_tree(['target3/dir1/file2'])
1246
build_tree(source.basis_tree(), target)
1247
self.failIfExists('target3/dir1/file')
1248
self.failUnlessExists('target3/dir1/file2')
1249
self.failUnlessExists('target3/dir1.diverted/file')
1250
self.assertEqual([DuplicateEntry('Diverted to',
1251
'dir1.diverted', 'dir1', 'new-dir1', None)],
1254
target = self.make_branch_and_tree('target4')
1255
self.build_tree(['target4/dir1/'])
1256
self.make_branch('target4/dir1/file')
1257
build_tree(source.basis_tree(), target)
1258
self.failUnlessExists('target4/dir1/file')
1259
self.assertEqual('directory', file_kind('target4/dir1/file'))
1260
self.failUnlessExists('target4/dir1/file.diverted')
1261
self.assertEqual([DuplicateEntry('Diverted to',
1262
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1265
def test_mixed_conflict_handling(self):
1266
"""Ensure that when building trees, conflict handling is done"""
1267
source = self.make_branch_and_tree('source')
1268
target = self.make_branch_and_tree('target')
1269
self.build_tree(['source/name', 'target/name/'])
1270
source.add('name', 'new-name')
1271
source.commit('added file')
1272
build_tree(source.basis_tree(), target)
1273
self.assertEqual([DuplicateEntry('Moved existing file to',
1274
'name.moved', 'name', None, 'new-name')], target.conflicts())
1276
def test_raises_in_populated(self):
1277
source = self.make_branch_and_tree('source')
1278
self.build_tree(['source/name'])
1280
source.commit('added name')
1281
target = self.make_branch_and_tree('target')
1282
self.build_tree(['target/name'])
1284
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1285
build_tree, source.basis_tree(), target)
1287
def test_build_tree_rename_count(self):
1288
source = self.make_branch_and_tree('source')
1289
self.build_tree(['source/file1', 'source/dir1/'])
1290
source.add(['file1', 'dir1'])
1291
source.commit('add1')
1292
target1 = self.make_branch_and_tree('target1')
1293
transform_result = build_tree(source.basis_tree(), target1)
1294
self.assertEqual(2, transform_result.rename_count)
1296
self.build_tree(['source/dir1/file2'])
1297
source.add(['dir1/file2'])
1298
source.commit('add3')
1299
target2 = self.make_branch_and_tree('target2')
1300
transform_result = build_tree(source.basis_tree(), target2)
1301
# children of non-root directories should not be renamed
1302
self.assertEqual(2, transform_result.rename_count)
1305
class MockTransform(object):
1307
def has_named_child(self, by_parent, parent_id, name):
1308
for child_id in by_parent[parent_id]:
1312
elif name == "name.~%s~" % child_id:
1317
class MockEntry(object):
1319
object.__init__(self)
1322
class TestGetBackupName(TestCase):
1323
def test_get_backup_name(self):
1324
tt = MockTransform()
1325
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1326
self.assertEqual(name, 'name.~1~')
1327
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1328
self.assertEqual(name, 'name.~2~')
1329
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1330
self.assertEqual(name, 'name.~1~')
1331
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1332
self.assertEqual(name, 'name.~1~')
1333
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1334
self.assertEqual(name, 'name.~4~')