1
# Copyright (C) 2006, 2007, 2008 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
19
from StringIO import StringIO
27
revision as _mod_revision,
32
from bzrlib.bzrdir import BzrDir
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
UnversionedParent, ParentLoop, DeletingParent,
36
from bzrlib.diff import show_diff_trees
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
39
PathsNotVersionedError, ExistingLimbo,
40
ExistingPendingDeletion, ImmortalLimbo,
41
ImmortalPendingDeletion, LockError)
42
from bzrlib.osutils import file_kind, pathjoin
43
from bzrlib.merge import Merge3Merger, Merger
44
from bzrlib.tests import (
45
CaseInsensitiveFilesystemFeature,
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
resolve_conflicts, cook_conflicts,
54
build_tree, get_backup_name,
55
_FileMover, resolve_checkout,
56
TransformPreview, create_from_tree)
58
class TestTreeTransform(tests.TestCaseWithTransport):
61
super(TestTreeTransform, self).setUp()
62
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
65
def get_transform(self):
66
transform = TreeTransform(self.wt)
67
self.addCleanup(transform.finalize)
68
return transform, transform.root
70
def test_existing_limbo(self):
71
transform, root = self.get_transform()
72
limbo_name = transform._limbodir
73
deletion_path = transform._deletiondir
74
os.mkdir(pathjoin(limbo_name, 'hehe'))
75
self.assertRaises(ImmortalLimbo, transform.apply)
76
self.assertRaises(LockError, self.wt.unlock)
77
self.assertRaises(ExistingLimbo, self.get_transform)
78
self.assertRaises(LockError, self.wt.unlock)
79
os.rmdir(pathjoin(limbo_name, 'hehe'))
81
os.rmdir(deletion_path)
82
transform, root = self.get_transform()
85
def test_existing_pending_deletion(self):
86
transform, root = self.get_transform()
87
deletion_path = self._limbodir = urlutils.local_path_from_url(
88
transform._tree._transport.abspath('pending-deletion'))
89
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
90
self.assertRaises(ImmortalPendingDeletion, transform.apply)
91
self.assertRaises(LockError, self.wt.unlock)
92
self.assertRaises(ExistingPendingDeletion, self.get_transform)
95
transform, root = self.get_transform()
96
self.wt.lock_tree_write()
97
self.addCleanup(self.wt.unlock)
98
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
99
imaginary_id = transform.trans_id_tree_path('imaginary')
100
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
101
self.assertEqual(imaginary_id, imaginary_id2)
102
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
103
self.assertEqual(transform.final_kind(root), 'directory')
104
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
105
trans_id = transform.create_path('name', root)
106
self.assertIs(transform.final_file_id(trans_id), None)
107
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
108
transform.create_file('contents', trans_id)
109
transform.set_executability(True, trans_id)
110
transform.version_file('my_pretties', trans_id)
111
self.assertRaises(DuplicateKey, transform.version_file,
112
'my_pretties', trans_id)
113
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
114
self.assertEqual(transform.final_parent(trans_id), root)
115
self.assertIs(transform.final_parent(root), ROOT_PARENT)
116
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
117
oz_id = transform.create_path('oz', root)
118
transform.create_directory(oz_id)
119
transform.version_file('ozzie', oz_id)
120
trans_id2 = transform.create_path('name2', root)
121
transform.create_file('contents', trans_id2)
122
transform.set_executability(False, trans_id2)
123
transform.version_file('my_pretties2', trans_id2)
124
modified_paths = transform.apply().modified_paths
125
self.assertEqual('contents', self.wt.get_file_byname('name').read())
126
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
127
self.assertIs(self.wt.is_executable('my_pretties'), True)
128
self.assertIs(self.wt.is_executable('my_pretties2'), False)
129
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
130
self.assertEqual(len(modified_paths), 3)
131
tree_mod_paths = [self.wt.id2abspath(f) for f in
132
('ozzie', 'my_pretties', 'my_pretties2')]
133
self.assertSubset(tree_mod_paths, modified_paths)
134
# is it safe to finalize repeatedly?
138
def test_hardlink(self):
139
self.requireFeature(HardlinkFeature)
140
transform, root = self.get_transform()
141
transform.new_file('file1', root, 'contents')
143
target = self.make_branch_and_tree('target')
144
target_transform = TreeTransform(target)
145
trans_id = target_transform.create_path('file1', target_transform.root)
146
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
147
target_transform.apply()
148
self.failUnlessExists('target/file1')
149
source_stat = os.stat(self.wt.abspath('file1'))
150
target_stat = os.stat('target/file1')
151
self.assertEqual(source_stat, target_stat)
153
def test_convenience(self):
154
transform, root = self.get_transform()
155
self.wt.lock_tree_write()
156
self.addCleanup(self.wt.unlock)
157
trans_id = transform.new_file('name', root, 'contents',
159
oz = transform.new_directory('oz', root, 'oz-id')
160
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
161
toto = transform.new_file('toto', dorothy, 'toto-contents',
164
self.assertEqual(len(transform.find_conflicts()), 0)
166
self.assertRaises(ReusingTransform, transform.find_conflicts)
167
self.assertEqual('contents', file(self.wt.abspath('name')).read())
168
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
169
self.assertIs(self.wt.is_executable('my_pretties'), True)
170
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
171
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
172
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
174
self.assertEqual('toto-contents',
175
self.wt.get_file_byname('oz/dorothy/toto').read())
176
self.assertIs(self.wt.is_executable('toto-id'), False)
178
def test_tree_reference(self):
179
transform, root = self.get_transform()
180
tree = transform._tree
181
trans_id = transform.new_directory('reference', root, 'subtree-id')
182
transform.set_tree_reference('subtree-revision', trans_id)
185
self.addCleanup(tree.unlock)
186
self.assertEqual('subtree-revision',
187
tree.inventory['subtree-id'].reference_revision)
189
def test_conflicts(self):
190
transform, root = self.get_transform()
191
trans_id = transform.new_file('name', root, 'contents',
193
self.assertEqual(len(transform.find_conflicts()), 0)
194
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
195
self.assertEqual(transform.find_conflicts(),
196
[('duplicate', trans_id, trans_id2, 'name')])
197
self.assertRaises(MalformedTransform, transform.apply)
198
transform.adjust_path('name', trans_id, trans_id2)
199
self.assertEqual(transform.find_conflicts(),
200
[('non-directory parent', trans_id)])
201
tinman_id = transform.trans_id_tree_path('tinman')
202
transform.adjust_path('name', tinman_id, trans_id2)
203
self.assertEqual(transform.find_conflicts(),
204
[('unversioned parent', tinman_id),
205
('missing parent', tinman_id)])
206
lion_id = transform.create_path('lion', root)
207
self.assertEqual(transform.find_conflicts(),
208
[('unversioned parent', tinman_id),
209
('missing parent', tinman_id)])
210
transform.adjust_path('name', lion_id, trans_id2)
211
self.assertEqual(transform.find_conflicts(),
212
[('unversioned parent', lion_id),
213
('missing parent', lion_id)])
214
transform.version_file("Courage", lion_id)
215
self.assertEqual(transform.find_conflicts(),
216
[('missing parent', lion_id),
217
('versioning no contents', lion_id)])
218
transform.adjust_path('name2', root, trans_id2)
219
self.assertEqual(transform.find_conflicts(),
220
[('versioning no contents', lion_id)])
221
transform.create_file('Contents, okay?', lion_id)
222
transform.adjust_path('name2', trans_id2, trans_id2)
223
self.assertEqual(transform.find_conflicts(),
224
[('parent loop', trans_id2),
225
('non-directory parent', trans_id2)])
226
transform.adjust_path('name2', root, trans_id2)
227
oz_id = transform.new_directory('oz', root)
228
transform.set_executability(True, oz_id)
229
self.assertEqual(transform.find_conflicts(),
230
[('unversioned executability', oz_id)])
231
transform.version_file('oz-id', oz_id)
232
self.assertEqual(transform.find_conflicts(),
233
[('non-file executability', oz_id)])
234
transform.set_executability(None, oz_id)
235
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
237
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
238
self.assertEqual('contents', file(self.wt.abspath('name')).read())
239
transform2, root = self.get_transform()
240
oz_id = transform2.trans_id_tree_file_id('oz-id')
241
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
242
result = transform2.find_conflicts()
243
fp = FinalPaths(transform2)
244
self.assert_('oz/tip' in transform2._tree_path_ids)
245
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
246
self.assertEqual(len(result), 2)
247
self.assertEqual((result[0][0], result[0][1]),
248
('duplicate', newtip))
249
self.assertEqual((result[1][0], result[1][2]),
250
('duplicate id', newtip))
251
transform2.finalize()
252
transform3 = TreeTransform(self.wt)
253
self.addCleanup(transform3.finalize)
254
oz_id = transform3.trans_id_tree_file_id('oz-id')
255
transform3.delete_contents(oz_id)
256
self.assertEqual(transform3.find_conflicts(),
257
[('missing parent', oz_id)])
258
root_id = transform3.root
259
tip_id = transform3.trans_id_tree_file_id('tip-id')
260
transform3.adjust_path('tip', root_id, tip_id)
263
def test_conflict_on_case_insensitive(self):
264
tree = self.make_branch_and_tree('tree')
265
# Don't try this at home, kids!
266
# Force the tree to report that it is case sensitive, for conflict
268
tree.case_sensitive = True
269
transform = TreeTransform(tree)
270
self.addCleanup(transform.finalize)
271
transform.new_file('file', transform.root, 'content')
272
transform.new_file('FiLe', transform.root, 'content')
273
result = transform.find_conflicts()
274
self.assertEqual([], result)
276
# Force the tree to report that it is case insensitive, for conflict
278
tree.case_sensitive = False
279
transform = TreeTransform(tree)
280
self.addCleanup(transform.finalize)
281
transform.new_file('file', transform.root, 'content')
282
transform.new_file('FiLe', transform.root, 'content')
283
result = transform.find_conflicts()
284
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
286
def test_conflict_on_case_insensitive_existing(self):
287
tree = self.make_branch_and_tree('tree')
288
self.build_tree(['tree/FiLe'])
289
# Don't try this at home, kids!
290
# Force the tree to report that it is case sensitive, for conflict
292
tree.case_sensitive = True
293
transform = TreeTransform(tree)
294
self.addCleanup(transform.finalize)
295
transform.new_file('file', transform.root, 'content')
296
result = transform.find_conflicts()
297
self.assertEqual([], result)
299
# Force the tree to report that it is case insensitive, for conflict
301
tree.case_sensitive = False
302
transform = TreeTransform(tree)
303
self.addCleanup(transform.finalize)
304
transform.new_file('file', transform.root, 'content')
305
result = transform.find_conflicts()
306
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
308
def test_resolve_case_insensitive_conflict(self):
309
tree = self.make_branch_and_tree('tree')
310
# Don't try this at home, kids!
311
# Force the tree to report that it is case insensitive, for conflict
313
tree.case_sensitive = False
314
transform = TreeTransform(tree)
315
self.addCleanup(transform.finalize)
316
transform.new_file('file', transform.root, 'content')
317
transform.new_file('FiLe', transform.root, 'content')
318
resolve_conflicts(transform)
320
self.failUnlessExists('tree/file')
321
self.failUnlessExists('tree/FiLe.moved')
323
def test_resolve_checkout_case_conflict(self):
324
tree = self.make_branch_and_tree('tree')
325
# Don't try this at home, kids!
326
# Force the tree to report that it is case insensitive, for conflict
328
tree.case_sensitive = False
329
transform = TreeTransform(tree)
330
self.addCleanup(transform.finalize)
331
transform.new_file('file', transform.root, 'content')
332
transform.new_file('FiLe', transform.root, 'content')
333
resolve_conflicts(transform,
334
pass_func=lambda t, c: resolve_checkout(t, c, []))
336
self.failUnlessExists('tree/file')
337
self.failUnlessExists('tree/FiLe.moved')
339
def test_apply_case_conflict(self):
340
"""Ensure that a transform with case conflicts can always be applied"""
341
tree = self.make_branch_and_tree('tree')
342
transform = TreeTransform(tree)
343
self.addCleanup(transform.finalize)
344
transform.new_file('file', transform.root, 'content')
345
transform.new_file('FiLe', transform.root, 'content')
346
dir = transform.new_directory('dir', transform.root)
347
transform.new_file('dirfile', dir, 'content')
348
transform.new_file('dirFiLe', dir, 'content')
349
resolve_conflicts(transform)
351
self.failUnlessExists('tree/file')
352
if not os.path.exists('tree/FiLe.moved'):
353
self.failUnlessExists('tree/FiLe')
354
self.failUnlessExists('tree/dir/dirfile')
355
if not os.path.exists('tree/dir/dirFiLe.moved'):
356
self.failUnlessExists('tree/dir/dirFiLe')
358
def test_case_insensitive_limbo(self):
359
tree = self.make_branch_and_tree('tree')
360
# Don't try this at home, kids!
361
# Force the tree to report that it is case insensitive
362
tree.case_sensitive = False
363
transform = TreeTransform(tree)
364
self.addCleanup(transform.finalize)
365
dir = transform.new_directory('dir', transform.root)
366
first = transform.new_file('file', dir, 'content')
367
second = transform.new_file('FiLe', dir, 'content')
368
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
369
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
371
def test_add_del(self):
372
start, root = self.get_transform()
373
start.new_directory('a', root, 'a')
375
transform, root = self.get_transform()
376
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
377
transform.new_directory('a', root, 'a')
380
def test_unversioning(self):
381
create_tree, root = self.get_transform()
382
parent_id = create_tree.new_directory('parent', root, 'parent-id')
383
create_tree.new_file('child', parent_id, 'child', 'child-id')
385
unversion = TreeTransform(self.wt)
386
self.addCleanup(unversion.finalize)
387
parent = unversion.trans_id_tree_path('parent')
388
unversion.unversion_file(parent)
389
self.assertEqual(unversion.find_conflicts(),
390
[('unversioned parent', parent_id)])
391
file_id = unversion.trans_id_tree_file_id('child-id')
392
unversion.unversion_file(file_id)
395
def test_name_invariants(self):
396
create_tree, root = self.get_transform()
398
root = create_tree.root
399
create_tree.new_file('name1', root, 'hello1', 'name1')
400
create_tree.new_file('name2', root, 'hello2', 'name2')
401
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
402
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
403
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
404
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
407
mangle_tree,root = self.get_transform()
408
root = mangle_tree.root
410
name1 = mangle_tree.trans_id_tree_file_id('name1')
411
name2 = mangle_tree.trans_id_tree_file_id('name2')
412
mangle_tree.adjust_path('name2', root, name1)
413
mangle_tree.adjust_path('name1', root, name2)
415
#tests for deleting parent directories
416
ddir = mangle_tree.trans_id_tree_file_id('ddir')
417
mangle_tree.delete_contents(ddir)
418
dfile = mangle_tree.trans_id_tree_file_id('dfile')
419
mangle_tree.delete_versioned(dfile)
420
mangle_tree.unversion_file(dfile)
421
mfile = mangle_tree.trans_id_tree_file_id('mfile')
422
mangle_tree.adjust_path('mfile', root, mfile)
424
#tests for adding parent directories
425
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
426
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
427
mangle_tree.adjust_path('mfile2', newdir, mfile2)
428
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
429
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
430
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
431
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
433
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
434
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
435
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
436
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
437
self.assertEqual(file(mfile2_path).read(), 'later2')
438
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
439
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
440
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
441
self.assertEqual(file(newfile_path).read(), 'hello3')
442
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
443
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
444
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
446
def test_both_rename(self):
447
create_tree,root = self.get_transform()
448
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
449
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
451
mangle_tree,root = self.get_transform()
452
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
453
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
454
mangle_tree.adjust_path('test', root, selftest)
455
mangle_tree.adjust_path('test_too_much', root, selftest)
456
mangle_tree.set_executability(True, blackbox)
459
def test_both_rename2(self):
460
create_tree,root = self.get_transform()
461
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
462
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
463
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
464
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
467
mangle_tree,root = self.get_transform()
468
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
469
tests = mangle_tree.trans_id_tree_file_id('tests-id')
470
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
471
mangle_tree.adjust_path('selftest', bzrlib, tests)
472
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
473
mangle_tree.set_executability(True, test_too_much)
476
def test_both_rename3(self):
477
create_tree,root = self.get_transform()
478
tests = create_tree.new_directory('tests', root, 'tests-id')
479
create_tree.new_file('test_too_much.py', tests, 'hello1',
482
mangle_tree,root = self.get_transform()
483
tests = mangle_tree.trans_id_tree_file_id('tests-id')
484
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
485
mangle_tree.adjust_path('selftest', root, tests)
486
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
487
mangle_tree.set_executability(True, test_too_much)
490
def test_move_dangling_ie(self):
491
create_tree, root = self.get_transform()
493
root = create_tree.root
494
create_tree.new_file('name1', root, 'hello1', 'name1')
496
delete_contents, root = self.get_transform()
497
file = delete_contents.trans_id_tree_file_id('name1')
498
delete_contents.delete_contents(file)
499
delete_contents.apply()
500
move_id, root = self.get_transform()
501
name1 = move_id.trans_id_tree_file_id('name1')
502
newdir = move_id.new_directory('dir', root, 'newdir')
503
move_id.adjust_path('name2', newdir, name1)
506
def test_replace_dangling_ie(self):
507
create_tree, root = self.get_transform()
509
root = create_tree.root
510
create_tree.new_file('name1', root, 'hello1', 'name1')
512
delete_contents = TreeTransform(self.wt)
513
self.addCleanup(delete_contents.finalize)
514
file = delete_contents.trans_id_tree_file_id('name1')
515
delete_contents.delete_contents(file)
516
delete_contents.apply()
517
delete_contents.finalize()
518
replace = TreeTransform(self.wt)
519
self.addCleanup(replace.finalize)
520
name2 = replace.new_file('name2', root, 'hello2', 'name1')
521
conflicts = replace.find_conflicts()
522
name1 = replace.trans_id_tree_file_id('name1')
523
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
524
resolve_conflicts(replace)
527
def test_symlinks(self):
528
self.requireFeature(SymlinkFeature)
529
transform,root = self.get_transform()
530
oz_id = transform.new_directory('oz', root, 'oz-id')
531
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
533
wiz_id = transform.create_path('wizard2', oz_id)
534
transform.create_symlink('behind_curtain', wiz_id)
535
transform.version_file('wiz-id2', wiz_id)
536
transform.set_executability(True, wiz_id)
537
self.assertEqual(transform.find_conflicts(),
538
[('non-file executability', wiz_id)])
539
transform.set_executability(None, wiz_id)
541
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
542
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
543
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
545
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
548
def test_unable_create_symlink(self):
550
wt = self.make_branch_and_tree('.')
551
tt = TreeTransform(wt) # TreeTransform obtains write lock
553
tt.new_symlink('foo', tt.root, 'bar')
557
os_symlink = getattr(os, 'symlink', None)
560
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
562
"Unable to create symlink 'foo' on this platform",
566
os.symlink = os_symlink
568
def get_conflicted(self):
569
create,root = self.get_transform()
570
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
571
oz = create.new_directory('oz', root, 'oz-id')
572
create.new_directory('emeraldcity', oz, 'emerald-id')
574
conflicts,root = self.get_transform()
575
# set up duplicate entry, duplicate id
576
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
578
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
579
oz = conflicts.trans_id_tree_file_id('oz-id')
580
# set up DeletedParent parent conflict
581
conflicts.delete_versioned(oz)
582
emerald = conflicts.trans_id_tree_file_id('emerald-id')
583
# set up MissingParent conflict
584
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
585
conflicts.adjust_path('munchkincity', root, munchkincity)
586
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
588
conflicts.adjust_path('emeraldcity', emerald, emerald)
589
return conflicts, emerald, oz, old_dorothy, new_dorothy
591
def test_conflict_resolution(self):
592
conflicts, emerald, oz, old_dorothy, new_dorothy =\
593
self.get_conflicted()
594
resolve_conflicts(conflicts)
595
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
596
self.assertIs(conflicts.final_file_id(old_dorothy), None)
597
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
598
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
599
self.assertEqual(conflicts.final_parent(emerald), oz)
602
def test_cook_conflicts(self):
603
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
604
raw_conflicts = resolve_conflicts(tt)
605
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
606
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
607
'dorothy', None, 'dorothy-id')
608
self.assertEqual(cooked_conflicts[0], duplicate)
609
duplicate_id = DuplicateID('Unversioned existing file',
610
'dorothy.moved', 'dorothy', None,
612
self.assertEqual(cooked_conflicts[1], duplicate_id)
613
missing_parent = MissingParent('Created directory', 'munchkincity',
615
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
616
self.assertEqual(cooked_conflicts[2], missing_parent)
617
unversioned_parent = UnversionedParent('Versioned directory',
620
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
622
self.assertEqual(cooked_conflicts[3], unversioned_parent)
623
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
624
'oz/emeraldcity', 'emerald-id', 'emerald-id')
625
self.assertEqual(cooked_conflicts[4], deleted_parent)
626
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
627
self.assertEqual(cooked_conflicts[6], parent_loop)
628
self.assertEqual(len(cooked_conflicts), 7)
631
def test_string_conflicts(self):
632
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
633
raw_conflicts = resolve_conflicts(tt)
634
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
636
conflicts_s = [str(c) for c in cooked_conflicts]
637
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
638
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
639
'Moved existing file to '
641
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
642
'Unversioned existing file '
644
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
645
' munchkincity. Created directory.')
646
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
647
' versioned, but has versioned'
648
' children. Versioned directory.')
649
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
650
" is not empty. Not deleting.")
651
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
652
' versioned, but has versioned'
653
' children. Versioned directory.')
654
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
655
' oz/emeraldcity. Cancelled move.')
657
def prepare_wrong_parent_kind(self):
658
tt, root = self.get_transform()
659
tt.new_file('parent', root, 'contents', 'parent-id')
661
tt, root = self.get_transform()
662
parent_id = tt.trans_id_file_id('parent-id')
663
tt.new_file('child,', parent_id, 'contents2', 'file-id')
666
def test_find_conflicts_wrong_parent_kind(self):
667
tt = self.prepare_wrong_parent_kind()
670
def test_resolve_conflicts_wrong_existing_parent_kind(self):
671
tt = self.prepare_wrong_parent_kind()
672
raw_conflicts = resolve_conflicts(tt)
673
self.assertEqual(set([('non-directory parent', 'Created directory',
674
'new-3')]), raw_conflicts)
675
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
676
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
677
'parent-id')], cooked_conflicts)
679
self.assertEqual(None, self.wt.path2id('parent'))
680
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
682
def test_resolve_conflicts_wrong_new_parent_kind(self):
683
tt, root = self.get_transform()
684
parent_id = tt.new_directory('parent', root, 'parent-id')
685
tt.new_file('child,', parent_id, 'contents2', 'file-id')
687
tt, root = self.get_transform()
688
parent_id = tt.trans_id_file_id('parent-id')
689
tt.delete_contents(parent_id)
690
tt.create_file('contents', parent_id)
691
raw_conflicts = resolve_conflicts(tt)
692
self.assertEqual(set([('non-directory parent', 'Created directory',
693
'new-3')]), raw_conflicts)
695
self.assertEqual(None, self.wt.path2id('parent'))
696
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
698
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
699
tt, root = self.get_transform()
700
parent_id = tt.new_directory('parent', root)
701
tt.new_file('child,', parent_id, 'contents2')
703
tt, root = self.get_transform()
704
parent_id = tt.trans_id_tree_path('parent')
705
tt.delete_contents(parent_id)
706
tt.create_file('contents', parent_id)
707
resolve_conflicts(tt)
709
self.assertIs(None, self.wt.path2id('parent'))
710
self.assertIs(None, self.wt.path2id('parent.new'))
712
def test_moving_versioned_directories(self):
713
create, root = self.get_transform()
714
kansas = create.new_directory('kansas', root, 'kansas-id')
715
create.new_directory('house', kansas, 'house-id')
716
create.new_directory('oz', root, 'oz-id')
718
cyclone, root = self.get_transform()
719
oz = cyclone.trans_id_tree_file_id('oz-id')
720
house = cyclone.trans_id_tree_file_id('house-id')
721
cyclone.adjust_path('house', oz, house)
724
def test_moving_root(self):
725
create, root = self.get_transform()
726
fun = create.new_directory('fun', root, 'fun-id')
727
create.new_directory('sun', root, 'sun-id')
728
create.new_directory('moon', root, 'moon')
730
transform, root = self.get_transform()
731
transform.adjust_root_path('oldroot', fun)
732
new_root=transform.trans_id_tree_path('')
733
transform.version_file('new-root', new_root)
736
def test_renames(self):
737
create, root = self.get_transform()
738
old = create.new_directory('old-parent', root, 'old-id')
739
intermediate = create.new_directory('intermediate', old, 'im-id')
740
myfile = create.new_file('myfile', intermediate, 'myfile-text',
743
rename, root = self.get_transform()
744
old = rename.trans_id_file_id('old-id')
745
rename.adjust_path('new', root, old)
746
myfile = rename.trans_id_file_id('myfile-id')
747
rename.set_executability(True, myfile)
750
def test_set_executability_order(self):
751
"""Ensure that executability behaves the same, no matter what order.
753
- create file and set executability simultaneously
754
- create file and set executability afterward
755
- unsetting the executability of a file whose executability has not been
756
declared should throw an exception (this may happen when a
757
merge attempts to create a file with a duplicate ID)
759
transform, root = self.get_transform()
762
self.addCleanup(wt.unlock)
763
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
765
sac = transform.new_file('set_after_creation', root,
766
'Set after creation', 'sac')
767
transform.set_executability(True, sac)
768
uws = transform.new_file('unset_without_set', root, 'Unset badly',
770
self.assertRaises(KeyError, transform.set_executability, None, uws)
772
self.assertTrue(wt.is_executable('soc'))
773
self.assertTrue(wt.is_executable('sac'))
775
def test_preserve_mode(self):
776
"""File mode is preserved when replacing content"""
777
if sys.platform == 'win32':
778
raise TestSkipped('chmod has no effect on win32')
779
transform, root = self.get_transform()
780
transform.new_file('file1', root, 'contents', 'file1-id', True)
783
self.addCleanup(self.wt.unlock)
784
self.assertTrue(self.wt.is_executable('file1-id'))
785
transform, root = self.get_transform()
786
file1_id = transform.trans_id_tree_file_id('file1-id')
787
transform.delete_contents(file1_id)
788
transform.create_file('contents2', file1_id)
790
self.assertTrue(self.wt.is_executable('file1-id'))
792
def test__set_mode_stats_correctly(self):
793
"""_set_mode stats to determine file mode."""
794
if sys.platform == 'win32':
795
raise TestSkipped('chmod has no effect on win32')
799
def instrumented_stat(path):
800
stat_paths.append(path)
801
return real_stat(path)
803
transform, root = self.get_transform()
805
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
806
file_id='bar-id-1', executable=False)
809
transform, root = self.get_transform()
810
bar1_id = transform.trans_id_tree_path('bar')
811
bar2_id = transform.trans_id_tree_path('bar2')
813
os.stat = instrumented_stat
814
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
819
bar1_abspath = self.wt.abspath('bar')
820
self.assertEqual([bar1_abspath], stat_paths)
822
def test_iter_changes(self):
823
self.wt.set_root_id('eert_toor')
824
transform, root = self.get_transform()
825
transform.new_file('old', root, 'blah', 'id-1', True)
827
transform, root = self.get_transform()
829
self.assertEqual([], list(transform.iter_changes()))
830
old = transform.trans_id_tree_file_id('id-1')
831
transform.unversion_file(old)
832
self.assertEqual([('id-1', ('old', None), False, (True, False),
833
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
834
(True, True))], list(transform.iter_changes()))
835
transform.new_directory('new', root, 'id-1')
836
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
837
('eert_toor', 'eert_toor'), ('old', 'new'),
838
('file', 'directory'),
839
(True, False))], list(transform.iter_changes()))
843
def test_iter_changes_new(self):
844
self.wt.set_root_id('eert_toor')
845
transform, root = self.get_transform()
846
transform.new_file('old', root, 'blah')
848
transform, root = self.get_transform()
850
old = transform.trans_id_tree_path('old')
851
transform.version_file('id-1', old)
852
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
853
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
854
(False, False))], list(transform.iter_changes()))
858
def test_iter_changes_modifications(self):
859
self.wt.set_root_id('eert_toor')
860
transform, root = self.get_transform()
861
transform.new_file('old', root, 'blah', 'id-1')
862
transform.new_file('new', root, 'blah')
863
transform.new_directory('subdir', root, 'subdir-id')
865
transform, root = self.get_transform()
867
old = transform.trans_id_tree_path('old')
868
subdir = transform.trans_id_tree_file_id('subdir-id')
869
new = transform.trans_id_tree_path('new')
870
self.assertEqual([], list(transform.iter_changes()))
873
transform.delete_contents(old)
874
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
875
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
876
(False, False))], list(transform.iter_changes()))
879
transform.create_file('blah', old)
880
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
881
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
882
(False, False))], list(transform.iter_changes()))
883
transform.cancel_deletion(old)
884
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
885
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
886
(False, False))], list(transform.iter_changes()))
887
transform.cancel_creation(old)
889
# move file_id to a different file
890
self.assertEqual([], list(transform.iter_changes()))
891
transform.unversion_file(old)
892
transform.version_file('id-1', new)
893
transform.adjust_path('old', root, new)
894
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
895
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
896
(False, False))], list(transform.iter_changes()))
897
transform.cancel_versioning(new)
898
transform._removed_id = set()
901
self.assertEqual([], list(transform.iter_changes()))
902
transform.set_executability(True, old)
903
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
904
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
905
(False, True))], list(transform.iter_changes()))
906
transform.set_executability(None, old)
909
self.assertEqual([], list(transform.iter_changes()))
910
transform.adjust_path('new', root, old)
911
transform._new_parent = {}
912
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
913
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
914
(False, False))], list(transform.iter_changes()))
915
transform._new_name = {}
918
self.assertEqual([], list(transform.iter_changes()))
919
transform.adjust_path('new', subdir, old)
920
transform._new_name = {}
921
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
922
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
923
('file', 'file'), (False, False))],
924
list(transform.iter_changes()))
925
transform._new_path = {}
930
def test_iter_changes_modified_bleed(self):
931
self.wt.set_root_id('eert_toor')
932
"""Modified flag should not bleed from one change to another"""
933
# unfortunately, we have no guarantee that file1 (which is modified)
934
# will be applied before file2. And if it's applied after file2, it
935
# obviously can't bleed into file2's change output. But for now, it
937
transform, root = self.get_transform()
938
transform.new_file('file1', root, 'blah', 'id-1')
939
transform.new_file('file2', root, 'blah', 'id-2')
941
transform, root = self.get_transform()
943
transform.delete_contents(transform.trans_id_file_id('id-1'))
944
transform.set_executability(True,
945
transform.trans_id_file_id('id-2'))
946
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
947
('eert_toor', 'eert_toor'), ('file1', u'file1'),
948
('file', None), (False, False)),
949
('id-2', (u'file2', u'file2'), False, (True, True),
950
('eert_toor', 'eert_toor'), ('file2', u'file2'),
951
('file', 'file'), (False, True))],
952
list(transform.iter_changes()))
956
def test_iter_changes_move_missing(self):
957
"""Test moving ids with no files around"""
958
self.wt.set_root_id('toor_eert')
959
# Need two steps because versioning a non-existant file is a conflict.
960
transform, root = self.get_transform()
961
transform.new_directory('floater', root, 'floater-id')
963
transform, root = self.get_transform()
964
transform.delete_contents(transform.trans_id_tree_path('floater'))
966
transform, root = self.get_transform()
967
floater = transform.trans_id_tree_path('floater')
969
transform.adjust_path('flitter', root, floater)
970
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
971
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
972
(None, None), (False, False))], list(transform.iter_changes()))
976
def test_iter_changes_pointless(self):
977
"""Ensure that no-ops are not treated as modifications"""
978
self.wt.set_root_id('eert_toor')
979
transform, root = self.get_transform()
980
transform.new_file('old', root, 'blah', 'id-1')
981
transform.new_directory('subdir', root, 'subdir-id')
983
transform, root = self.get_transform()
985
old = transform.trans_id_tree_path('old')
986
subdir = transform.trans_id_tree_file_id('subdir-id')
987
self.assertEqual([], list(transform.iter_changes()))
988
transform.delete_contents(subdir)
989
transform.create_directory(subdir)
990
transform.set_executability(False, old)
991
transform.unversion_file(old)
992
transform.version_file('id-1', old)
993
transform.adjust_path('old', root, old)
994
self.assertEqual([], list(transform.iter_changes()))
998
def test_rename_count(self):
999
transform, root = self.get_transform()
1000
transform.new_file('name1', root, 'contents')
1001
self.assertEqual(transform.rename_count, 0)
1003
self.assertEqual(transform.rename_count, 1)
1004
transform2, root = self.get_transform()
1005
transform2.adjust_path('name2', root,
1006
transform2.trans_id_tree_path('name1'))
1007
self.assertEqual(transform2.rename_count, 0)
1009
self.assertEqual(transform2.rename_count, 2)
1011
def test_change_parent(self):
1012
"""Ensure that after we change a parent, the results are still right.
1014
Renames and parent changes on pending transforms can happen as part
1015
of conflict resolution, and are explicitly permitted by the
1018
This test ensures they work correctly with the rename-avoidance
1021
transform, root = self.get_transform()
1022
parent1 = transform.new_directory('parent1', root)
1023
child1 = transform.new_file('child1', parent1, 'contents')
1024
parent2 = transform.new_directory('parent2', root)
1025
transform.adjust_path('child1', parent2, child1)
1027
self.failIfExists(self.wt.abspath('parent1/child1'))
1028
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1029
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1030
# no rename for child1 (counting only renames during apply)
1031
self.failUnlessEqual(2, transform.rename_count)
1033
def test_cancel_parent(self):
1034
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1036
This is like the test_change_parent, except that we cancel the parent
1037
before adjusting the path. The transform must detect that the
1038
directory is non-empty, and move children to safe locations.
1040
transform, root = self.get_transform()
1041
parent1 = transform.new_directory('parent1', root)
1042
child1 = transform.new_file('child1', parent1, 'contents')
1043
child2 = transform.new_file('child2', parent1, 'contents')
1045
transform.cancel_creation(parent1)
1047
self.fail('Failed to move child1 before deleting parent1')
1048
transform.cancel_creation(child2)
1049
transform.create_directory(parent1)
1051
transform.cancel_creation(parent1)
1052
# If the transform incorrectly believes that child2 is still in
1053
# parent1's limbo directory, it will try to rename it and fail
1054
# because was already moved by the first cancel_creation.
1056
self.fail('Transform still thinks child2 is a child of parent1')
1057
parent2 = transform.new_directory('parent2', root)
1058
transform.adjust_path('child1', parent2, child1)
1060
self.failIfExists(self.wt.abspath('parent1'))
1061
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1062
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1063
self.failUnlessEqual(2, transform.rename_count)
1065
def test_adjust_and_cancel(self):
1066
"""Make sure adjust_path keeps track of limbo children properly"""
1067
transform, root = self.get_transform()
1068
parent1 = transform.new_directory('parent1', root)
1069
child1 = transform.new_file('child1', parent1, 'contents')
1070
parent2 = transform.new_directory('parent2', root)
1071
transform.adjust_path('child1', parent2, child1)
1072
transform.cancel_creation(child1)
1074
transform.cancel_creation(parent1)
1075
# if the transform thinks child1 is still in parent1's limbo
1076
# directory, it will attempt to move it and fail.
1078
self.fail('Transform still thinks child1 is a child of parent1')
1079
transform.finalize()
1081
def test_noname_contents(self):
1082
"""TreeTransform should permit deferring naming files."""
1083
transform, root = self.get_transform()
1084
parent = transform.trans_id_file_id('parent-id')
1086
transform.create_directory(parent)
1088
self.fail("Can't handle contents with no name")
1089
transform.finalize()
1091
def test_noname_contents_nested(self):
1092
"""TreeTransform should permit deferring naming files."""
1093
transform, root = self.get_transform()
1094
parent = transform.trans_id_file_id('parent-id')
1096
transform.create_directory(parent)
1098
self.fail("Can't handle contents with no name")
1099
child = transform.new_directory('child', parent)
1100
transform.adjust_path('parent', root, parent)
1102
self.failUnlessExists(self.wt.abspath('parent/child'))
1103
self.assertEqual(1, transform.rename_count)
1105
def test_reuse_name(self):
1106
"""Avoid reusing the same limbo name for different files"""
1107
transform, root = self.get_transform()
1108
parent = transform.new_directory('parent', root)
1109
child1 = transform.new_directory('child', parent)
1111
child2 = transform.new_directory('child', parent)
1113
self.fail('Tranform tried to use the same limbo name twice')
1114
transform.adjust_path('child2', parent, child2)
1116
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1117
# child2 is put into top-level limbo because child1 has already
1118
# claimed the direct limbo path when child2 is created. There is no
1119
# advantage in renaming files once they're in top-level limbo, except
1121
self.assertEqual(2, transform.rename_count)
1123
def test_reuse_when_first_moved(self):
1124
"""Don't avoid direct paths when it is safe to use them"""
1125
transform, root = self.get_transform()
1126
parent = transform.new_directory('parent', root)
1127
child1 = transform.new_directory('child', parent)
1128
transform.adjust_path('child1', parent, child1)
1129
child2 = transform.new_directory('child', parent)
1131
# limbo/new-1 => parent
1132
self.assertEqual(1, transform.rename_count)
1134
def test_reuse_after_cancel(self):
1135
"""Don't avoid direct paths when it is safe to use them"""
1136
transform, root = self.get_transform()
1137
parent2 = transform.new_directory('parent2', root)
1138
child1 = transform.new_directory('child1', parent2)
1139
transform.cancel_creation(parent2)
1140
transform.create_directory(parent2)
1141
child2 = transform.new_directory('child1', parent2)
1142
transform.adjust_path('child2', parent2, child1)
1144
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1145
self.assertEqual(2, transform.rename_count)
1147
def test_finalize_order(self):
1148
"""Finalize must be done in child-to-parent order"""
1149
transform, root = self.get_transform()
1150
parent = transform.new_directory('parent', root)
1151
child = transform.new_directory('child', parent)
1153
transform.finalize()
1155
self.fail('Tried to remove parent before child1')
1157
def test_cancel_with_cancelled_child_should_succeed(self):
1158
transform, root = self.get_transform()
1159
parent = transform.new_directory('parent', root)
1160
child = transform.new_directory('child', parent)
1161
transform.cancel_creation(child)
1162
transform.cancel_creation(parent)
1163
transform.finalize()
1165
def test_rollback_on_directory_clash(self):
1167
wt = self.make_branch_and_tree('.')
1168
tt = TreeTransform(wt) # TreeTransform obtains write lock
1170
foo = tt.new_directory('foo', tt.root)
1171
tt.new_file('bar', foo, 'foobar')
1172
baz = tt.new_directory('baz', tt.root)
1173
tt.new_file('qux', baz, 'quux')
1174
# Ask for a rename 'foo' -> 'baz'
1175
tt.adjust_path('baz', tt.root, foo)
1176
# Lie to tt that we've already resolved all conflicts.
1177
tt.apply(no_conflicts=True)
1181
# The rename will fail because the target directory is not empty (but
1182
# raises FileExists anyway).
1183
err = self.assertRaises(errors.FileExists, tt_helper)
1184
self.assertContainsRe(str(err),
1185
"^File exists: .+/baz")
1187
def test_two_directories_clash(self):
1189
wt = self.make_branch_and_tree('.')
1190
tt = TreeTransform(wt) # TreeTransform obtains write lock
1192
foo_1 = tt.new_directory('foo', tt.root)
1193
tt.new_directory('bar', foo_1)
1194
# Adding the same directory with a different content
1195
foo_2 = tt.new_directory('foo', tt.root)
1196
tt.new_directory('baz', foo_2)
1197
# Lie to tt that we've already resolved all conflicts.
1198
tt.apply(no_conflicts=True)
1202
err = self.assertRaises(errors.FileExists, tt_helper)
1203
self.assertContainsRe(str(err),
1204
"^File exists: .+/foo")
1206
def test_two_directories_clash_finalize(self):
1208
wt = self.make_branch_and_tree('.')
1209
tt = TreeTransform(wt) # TreeTransform obtains write lock
1211
foo_1 = tt.new_directory('foo', tt.root)
1212
tt.new_directory('bar', foo_1)
1213
# Adding the same directory with a different content
1214
foo_2 = tt.new_directory('foo', tt.root)
1215
tt.new_directory('baz', foo_2)
1216
# Lie to tt that we've already resolved all conflicts.
1217
tt.apply(no_conflicts=True)
1221
err = self.assertRaises(errors.FileExists, tt_helper)
1222
self.assertContainsRe(str(err),
1223
"^File exists: .+/foo")
1225
def test_file_to_directory(self):
1226
wt = self.make_branch_and_tree('.')
1227
self.build_tree(['foo'])
1230
tt = TreeTransform(wt)
1231
self.addCleanup(tt.finalize)
1232
foo_trans_id = tt.trans_id_tree_path("foo")
1233
tt.delete_contents(foo_trans_id)
1234
tt.create_directory(foo_trans_id)
1235
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1236
tt.create_file(["aa\n"], bar_trans_id)
1237
tt.version_file("bar-1", bar_trans_id)
1239
self.failUnlessExists("foo/bar")
1242
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1247
changes = wt.changes_from(wt.basis_tree())
1248
self.assertFalse(changes.has_changed(), changes)
1250
def test_file_to_symlink(self):
1251
self.requireFeature(SymlinkFeature)
1252
wt = self.make_branch_and_tree('.')
1253
self.build_tree(['foo'])
1256
tt = TreeTransform(wt)
1257
self.addCleanup(tt.finalize)
1258
foo_trans_id = tt.trans_id_tree_path("foo")
1259
tt.delete_contents(foo_trans_id)
1260
tt.create_symlink("bar", foo_trans_id)
1262
self.failUnlessExists("foo")
1264
self.addCleanup(wt.unlock)
1265
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1268
def test_dir_to_file(self):
1269
wt = self.make_branch_and_tree('.')
1270
self.build_tree(['foo/', 'foo/bar'])
1271
wt.add(['foo', 'foo/bar'])
1273
tt = TreeTransform(wt)
1274
self.addCleanup(tt.finalize)
1275
foo_trans_id = tt.trans_id_tree_path("foo")
1276
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1277
tt.delete_contents(foo_trans_id)
1278
tt.delete_versioned(bar_trans_id)
1279
tt.create_file(["aa\n"], foo_trans_id)
1281
self.failUnlessExists("foo")
1283
self.addCleanup(wt.unlock)
1284
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1287
def test_dir_to_hardlink(self):
1288
self.requireFeature(HardlinkFeature)
1289
wt = self.make_branch_and_tree('.')
1290
self.build_tree(['foo/', 'foo/bar'])
1291
wt.add(['foo', 'foo/bar'])
1293
tt = TreeTransform(wt)
1294
self.addCleanup(tt.finalize)
1295
foo_trans_id = tt.trans_id_tree_path("foo")
1296
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1297
tt.delete_contents(foo_trans_id)
1298
tt.delete_versioned(bar_trans_id)
1299
self.build_tree(['baz'])
1300
tt.create_hardlink("baz", foo_trans_id)
1302
self.failUnlessExists("foo")
1303
self.failUnlessExists("baz")
1305
self.addCleanup(wt.unlock)
1306
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1309
def test_no_final_path(self):
1310
transform, root = self.get_transform()
1311
trans_id = transform.trans_id_file_id('foo')
1312
transform.create_file('bar', trans_id)
1313
transform.cancel_creation(trans_id)
1316
def test_create_from_tree(self):
1317
tree1 = self.make_branch_and_tree('tree1')
1318
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1319
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1320
tree2 = self.make_branch_and_tree('tree2')
1321
tt = TreeTransform(tree2)
1322
foo_trans_id = tt.create_path('foo', tt.root)
1323
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1324
bar_trans_id = tt.create_path('bar', tt.root)
1325
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1327
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1328
self.assertFileEqual('baz', 'tree2/bar')
1330
def test_create_from_tree_bytes(self):
1331
"""Provided lines are used instead of tree content."""
1332
tree1 = self.make_branch_and_tree('tree1')
1333
self.build_tree_contents([('tree1/foo', 'bar'),])
1334
tree1.add('foo', 'foo-id')
1335
tree2 = self.make_branch_and_tree('tree2')
1336
tt = TreeTransform(tree2)
1337
foo_trans_id = tt.create_path('foo', tt.root)
1338
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1340
self.assertFileEqual('qux', 'tree2/foo')
1342
def test_create_from_tree_symlink(self):
1343
self.requireFeature(SymlinkFeature)
1344
tree1 = self.make_branch_and_tree('tree1')
1345
os.symlink('bar', 'tree1/foo')
1346
tree1.add('foo', 'foo-id')
1347
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1348
foo_trans_id = tt.create_path('foo', tt.root)
1349
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1351
self.assertEqual('bar', os.readlink('tree2/foo'))
1354
class TransformGroup(object):
1356
def __init__(self, dirname, root_id):
1359
self.wt = BzrDir.create_standalone_workingtree(dirname)
1360
self.wt.set_root_id(root_id)
1361
self.b = self.wt.branch
1362
self.tt = TreeTransform(self.wt)
1363
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1366
def conflict_text(tree, merge):
1367
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1368
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1371
class TestTransformMerge(TestCaseInTempDir):
1373
def test_text_merge(self):
1374
root_id = generate_ids.gen_root_id()
1375
base = TransformGroup("base", root_id)
1376
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1377
base.tt.new_file('b', base.root, 'b1', 'b')
1378
base.tt.new_file('c', base.root, 'c', 'c')
1379
base.tt.new_file('d', base.root, 'd', 'd')
1380
base.tt.new_file('e', base.root, 'e', 'e')
1381
base.tt.new_file('f', base.root, 'f', 'f')
1382
base.tt.new_directory('g', base.root, 'g')
1383
base.tt.new_directory('h', base.root, 'h')
1385
other = TransformGroup("other", root_id)
1386
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1387
other.tt.new_file('b', other.root, 'b2', 'b')
1388
other.tt.new_file('c', other.root, 'c2', 'c')
1389
other.tt.new_file('d', other.root, 'd', 'd')
1390
other.tt.new_file('e', other.root, 'e2', 'e')
1391
other.tt.new_file('f', other.root, 'f', 'f')
1392
other.tt.new_file('g', other.root, 'g', 'g')
1393
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1394
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1396
this = TransformGroup("this", root_id)
1397
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1398
this.tt.new_file('b', this.root, 'b', 'b')
1399
this.tt.new_file('c', this.root, 'c', 'c')
1400
this.tt.new_file('d', this.root, 'd2', 'd')
1401
this.tt.new_file('e', this.root, 'e2', 'e')
1402
this.tt.new_file('f', this.root, 'f', 'f')
1403
this.tt.new_file('g', this.root, 'g', 'g')
1404
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1405
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1407
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1410
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1411
# three-way text conflict
1412
self.assertEqual(this.wt.get_file('b').read(),
1413
conflict_text('b', 'b2'))
1415
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1417
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1418
# Ambigious clean merge
1419
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1421
self.assertEqual(this.wt.get_file('f').read(), 'f')
1422
# Correct correct results when THIS == OTHER
1423
self.assertEqual(this.wt.get_file('g').read(), 'g')
1424
# Text conflict when THIS & OTHER are text and BASE is dir
1425
self.assertEqual(this.wt.get_file('h').read(),
1426
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1427
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1429
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1431
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1432
self.assertEqual(this.wt.get_file('i').read(),
1433
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1434
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1436
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1438
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1439
modified = ['a', 'b', 'c', 'h', 'i']
1440
merge_modified = this.wt.merge_modified()
1441
self.assertSubset(merge_modified, modified)
1442
self.assertEqual(len(merge_modified), len(modified))
1443
file(this.wt.id2abspath('a'), 'wb').write('booga')
1445
merge_modified = this.wt.merge_modified()
1446
self.assertSubset(merge_modified, modified)
1447
self.assertEqual(len(merge_modified), len(modified))
1451
def test_file_merge(self):
1452
self.requireFeature(SymlinkFeature)
1453
root_id = generate_ids.gen_root_id()
1454
base = TransformGroup("BASE", root_id)
1455
this = TransformGroup("THIS", root_id)
1456
other = TransformGroup("OTHER", root_id)
1457
for tg in this, base, other:
1458
tg.tt.new_directory('a', tg.root, 'a')
1459
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1460
tg.tt.new_file('c', tg.root, 'c', 'c')
1461
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1462
targets = ((base, 'base-e', 'base-f', None, None),
1463
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1464
(other, 'other-e', None, 'other-g', 'other-h'))
1465
for tg, e_target, f_target, g_target, h_target in targets:
1466
for link, target in (('e', e_target), ('f', f_target),
1467
('g', g_target), ('h', h_target)):
1468
if target is not None:
1469
tg.tt.new_symlink(link, tg.root, target, link)
1471
for tg in this, base, other:
1473
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1474
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1475
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1476
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1477
for suffix in ('THIS', 'BASE', 'OTHER'):
1478
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1479
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1480
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1481
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1482
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1483
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1484
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1485
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1486
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1487
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1488
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1489
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1490
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1491
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1493
def test_filename_merge(self):
1494
root_id = generate_ids.gen_root_id()
1495
base = TransformGroup("BASE", root_id)
1496
this = TransformGroup("THIS", root_id)
1497
other = TransformGroup("OTHER", root_id)
1498
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1499
for t in [base, this, other]]
1500
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1501
for t in [base, this, other]]
1502
base.tt.new_directory('c', base_a, 'c')
1503
this.tt.new_directory('c1', this_a, 'c')
1504
other.tt.new_directory('c', other_b, 'c')
1506
base.tt.new_directory('d', base_a, 'd')
1507
this.tt.new_directory('d1', this_b, 'd')
1508
other.tt.new_directory('d', other_a, 'd')
1510
base.tt.new_directory('e', base_a, 'e')
1511
this.tt.new_directory('e', this_a, 'e')
1512
other.tt.new_directory('e1', other_b, 'e')
1514
base.tt.new_directory('f', base_a, 'f')
1515
this.tt.new_directory('f1', this_b, 'f')
1516
other.tt.new_directory('f1', other_b, 'f')
1518
for tg in [this, base, other]:
1520
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1521
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1522
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1523
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1524
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1526
def test_filename_merge_conflicts(self):
1527
root_id = generate_ids.gen_root_id()
1528
base = TransformGroup("BASE", root_id)
1529
this = TransformGroup("THIS", root_id)
1530
other = TransformGroup("OTHER", root_id)
1531
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1532
for t in [base, this, other]]
1533
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1534
for t in [base, this, other]]
1536
base.tt.new_file('g', base_a, 'g', 'g')
1537
other.tt.new_file('g1', other_b, 'g1', 'g')
1539
base.tt.new_file('h', base_a, 'h', 'h')
1540
this.tt.new_file('h1', this_b, 'h1', 'h')
1542
base.tt.new_file('i', base.root, 'i', 'i')
1543
other.tt.new_directory('i1', this_b, 'i')
1545
for tg in [this, base, other]:
1547
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1549
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1550
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1551
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1552
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1553
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1554
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1555
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1558
class TestBuildTree(tests.TestCaseWithTransport):
1560
def test_build_tree_with_symlinks(self):
1561
self.requireFeature(SymlinkFeature)
1563
a = BzrDir.create_standalone_workingtree('a')
1565
file('a/foo/bar', 'wb').write('contents')
1566
os.symlink('a/foo/bar', 'a/foo/baz')
1567
a.add(['foo', 'foo/bar', 'foo/baz'])
1568
a.commit('initial commit')
1569
b = BzrDir.create_standalone_workingtree('b')
1570
basis = a.basis_tree()
1572
self.addCleanup(basis.unlock)
1573
build_tree(basis, b)
1574
self.assertIs(os.path.isdir('b/foo'), True)
1575
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1576
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1578
def test_build_with_references(self):
1579
tree = self.make_branch_and_tree('source',
1580
format='dirstate-with-subtree')
1581
subtree = self.make_branch_and_tree('source/subtree',
1582
format='dirstate-with-subtree')
1583
tree.add_reference(subtree)
1584
tree.commit('a revision')
1585
tree.branch.create_checkout('target')
1586
self.failUnlessExists('target')
1587
self.failUnlessExists('target/subtree')
1589
def test_file_conflict_handling(self):
1590
"""Ensure that when building trees, conflict handling is done"""
1591
source = self.make_branch_and_tree('source')
1592
target = self.make_branch_and_tree('target')
1593
self.build_tree(['source/file', 'target/file'])
1594
source.add('file', 'new-file')
1595
source.commit('added file')
1596
build_tree(source.basis_tree(), target)
1597
self.assertEqual([DuplicateEntry('Moved existing file to',
1598
'file.moved', 'file', None, 'new-file')],
1600
target2 = self.make_branch_and_tree('target2')
1601
target_file = file('target2/file', 'wb')
1603
source_file = file('source/file', 'rb')
1605
target_file.write(source_file.read())
1610
build_tree(source.basis_tree(), target2)
1611
self.assertEqual([], target2.conflicts())
1613
def test_symlink_conflict_handling(self):
1614
"""Ensure that when building trees, conflict handling is done"""
1615
self.requireFeature(SymlinkFeature)
1616
source = self.make_branch_and_tree('source')
1617
os.symlink('foo', 'source/symlink')
1618
source.add('symlink', 'new-symlink')
1619
source.commit('added file')
1620
target = self.make_branch_and_tree('target')
1621
os.symlink('bar', 'target/symlink')
1622
build_tree(source.basis_tree(), target)
1623
self.assertEqual([DuplicateEntry('Moved existing file to',
1624
'symlink.moved', 'symlink', None, 'new-symlink')],
1626
target = self.make_branch_and_tree('target2')
1627
os.symlink('foo', 'target2/symlink')
1628
build_tree(source.basis_tree(), target)
1629
self.assertEqual([], target.conflicts())
1631
def test_directory_conflict_handling(self):
1632
"""Ensure that when building trees, conflict handling is done"""
1633
source = self.make_branch_and_tree('source')
1634
target = self.make_branch_and_tree('target')
1635
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1636
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1637
source.commit('added file')
1638
build_tree(source.basis_tree(), target)
1639
self.assertEqual([], target.conflicts())
1640
self.failUnlessExists('target/dir1/file')
1642
# Ensure contents are merged
1643
target = self.make_branch_and_tree('target2')
1644
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1645
build_tree(source.basis_tree(), target)
1646
self.assertEqual([], target.conflicts())
1647
self.failUnlessExists('target2/dir1/file2')
1648
self.failUnlessExists('target2/dir1/file')
1650
# Ensure new contents are suppressed for existing branches
1651
target = self.make_branch_and_tree('target3')
1652
self.make_branch('target3/dir1')
1653
self.build_tree(['target3/dir1/file2'])
1654
build_tree(source.basis_tree(), target)
1655
self.failIfExists('target3/dir1/file')
1656
self.failUnlessExists('target3/dir1/file2')
1657
self.failUnlessExists('target3/dir1.diverted/file')
1658
self.assertEqual([DuplicateEntry('Diverted to',
1659
'dir1.diverted', 'dir1', 'new-dir1', None)],
1662
target = self.make_branch_and_tree('target4')
1663
self.build_tree(['target4/dir1/'])
1664
self.make_branch('target4/dir1/file')
1665
build_tree(source.basis_tree(), target)
1666
self.failUnlessExists('target4/dir1/file')
1667
self.assertEqual('directory', file_kind('target4/dir1/file'))
1668
self.failUnlessExists('target4/dir1/file.diverted')
1669
self.assertEqual([DuplicateEntry('Diverted to',
1670
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1673
def test_mixed_conflict_handling(self):
1674
"""Ensure that when building trees, conflict handling is done"""
1675
source = self.make_branch_and_tree('source')
1676
target = self.make_branch_and_tree('target')
1677
self.build_tree(['source/name', 'target/name/'])
1678
source.add('name', 'new-name')
1679
source.commit('added file')
1680
build_tree(source.basis_tree(), target)
1681
self.assertEqual([DuplicateEntry('Moved existing file to',
1682
'name.moved', 'name', None, 'new-name')], target.conflicts())
1684
def test_raises_in_populated(self):
1685
source = self.make_branch_and_tree('source')
1686
self.build_tree(['source/name'])
1688
source.commit('added name')
1689
target = self.make_branch_and_tree('target')
1690
self.build_tree(['target/name'])
1692
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1693
build_tree, source.basis_tree(), target)
1695
def test_build_tree_rename_count(self):
1696
source = self.make_branch_and_tree('source')
1697
self.build_tree(['source/file1', 'source/dir1/'])
1698
source.add(['file1', 'dir1'])
1699
source.commit('add1')
1700
target1 = self.make_branch_and_tree('target1')
1701
transform_result = build_tree(source.basis_tree(), target1)
1702
self.assertEqual(2, transform_result.rename_count)
1704
self.build_tree(['source/dir1/file2'])
1705
source.add(['dir1/file2'])
1706
source.commit('add3')
1707
target2 = self.make_branch_and_tree('target2')
1708
transform_result = build_tree(source.basis_tree(), target2)
1709
# children of non-root directories should not be renamed
1710
self.assertEqual(2, transform_result.rename_count)
1712
def create_ab_tree(self):
1713
"""Create a committed test tree with two files"""
1714
source = self.make_branch_and_tree('source')
1715
self.build_tree_contents([('source/file1', 'A')])
1716
self.build_tree_contents([('source/file2', 'B')])
1717
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1718
source.commit('commit files')
1720
self.addCleanup(source.unlock)
1723
def test_build_tree_accelerator_tree(self):
1724
source = self.create_ab_tree()
1725
self.build_tree_contents([('source/file2', 'C')])
1727
real_source_get_file = source.get_file
1728
def get_file(file_id, path=None):
1729
calls.append(file_id)
1730
return real_source_get_file(file_id, path)
1731
source.get_file = get_file
1732
target = self.make_branch_and_tree('target')
1733
revision_tree = source.basis_tree()
1734
revision_tree.lock_read()
1735
self.addCleanup(revision_tree.unlock)
1736
build_tree(revision_tree, target, source)
1737
self.assertEqual(['file1-id'], calls)
1739
self.addCleanup(target.unlock)
1740
self.assertEqual([], list(target.iter_changes(revision_tree)))
1742
def test_build_tree_accelerator_tree_missing_file(self):
1743
source = self.create_ab_tree()
1744
os.unlink('source/file1')
1745
source.remove(['file2'])
1746
target = self.make_branch_and_tree('target')
1747
revision_tree = source.basis_tree()
1748
revision_tree.lock_read()
1749
self.addCleanup(revision_tree.unlock)
1750
build_tree(revision_tree, target, source)
1752
self.addCleanup(target.unlock)
1753
self.assertEqual([], list(target.iter_changes(revision_tree)))
1755
def test_build_tree_accelerator_wrong_kind(self):
1756
self.requireFeature(SymlinkFeature)
1757
source = self.make_branch_and_tree('source')
1758
self.build_tree_contents([('source/file1', '')])
1759
self.build_tree_contents([('source/file2', '')])
1760
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1761
source.commit('commit files')
1762
os.unlink('source/file2')
1763
self.build_tree_contents([('source/file2/', 'C')])
1764
os.unlink('source/file1')
1765
os.symlink('file2', 'source/file1')
1767
real_source_get_file = source.get_file
1768
def get_file(file_id, path=None):
1769
calls.append(file_id)
1770
return real_source_get_file(file_id, path)
1771
source.get_file = get_file
1772
target = self.make_branch_and_tree('target')
1773
revision_tree = source.basis_tree()
1774
revision_tree.lock_read()
1775
self.addCleanup(revision_tree.unlock)
1776
build_tree(revision_tree, target, source)
1777
self.assertEqual([], calls)
1779
self.addCleanup(target.unlock)
1780
self.assertEqual([], list(target.iter_changes(revision_tree)))
1782
def test_build_tree_hardlink(self):
1783
self.requireFeature(HardlinkFeature)
1784
source = self.create_ab_tree()
1785
target = self.make_branch_and_tree('target')
1786
revision_tree = source.basis_tree()
1787
revision_tree.lock_read()
1788
self.addCleanup(revision_tree.unlock)
1789
build_tree(revision_tree, target, source, hardlink=True)
1791
self.addCleanup(target.unlock)
1792
self.assertEqual([], list(target.iter_changes(revision_tree)))
1793
source_stat = os.stat('source/file1')
1794
target_stat = os.stat('target/file1')
1795
self.assertEqual(source_stat, target_stat)
1797
# Explicitly disallowing hardlinks should prevent them.
1798
target2 = self.make_branch_and_tree('target2')
1799
build_tree(revision_tree, target2, source, hardlink=False)
1801
self.addCleanup(target2.unlock)
1802
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1803
source_stat = os.stat('source/file1')
1804
target2_stat = os.stat('target2/file1')
1805
self.assertNotEqual(source_stat, target2_stat)
1807
def test_build_tree_accelerator_tree_moved(self):
1808
source = self.make_branch_and_tree('source')
1809
self.build_tree_contents([('source/file1', 'A')])
1810
source.add(['file1'], ['file1-id'])
1811
source.commit('commit files')
1812
source.rename_one('file1', 'file2')
1814
self.addCleanup(source.unlock)
1815
target = self.make_branch_and_tree('target')
1816
revision_tree = source.basis_tree()
1817
revision_tree.lock_read()
1818
self.addCleanup(revision_tree.unlock)
1819
build_tree(revision_tree, target, source)
1821
self.addCleanup(target.unlock)
1822
self.assertEqual([], list(target.iter_changes(revision_tree)))
1824
def test_build_tree_hardlinks_preserve_execute(self):
1825
self.requireFeature(HardlinkFeature)
1826
source = self.create_ab_tree()
1827
tt = TreeTransform(source)
1828
trans_id = tt.trans_id_tree_file_id('file1-id')
1829
tt.set_executability(True, trans_id)
1831
self.assertTrue(source.is_executable('file1-id'))
1832
target = self.make_branch_and_tree('target')
1833
revision_tree = source.basis_tree()
1834
revision_tree.lock_read()
1835
self.addCleanup(revision_tree.unlock)
1836
build_tree(revision_tree, target, source, hardlink=True)
1838
self.addCleanup(target.unlock)
1839
self.assertEqual([], list(target.iter_changes(revision_tree)))
1840
self.assertTrue(source.is_executable('file1-id'))
1842
def test_case_insensitive_build_tree_inventory(self):
1843
source = self.make_branch_and_tree('source')
1844
self.build_tree(['source/file', 'source/FILE'])
1845
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1846
source.commit('added files')
1847
# Don't try this at home, kids!
1848
# Force the tree to report that it is case insensitive
1849
target = self.make_branch_and_tree('target')
1850
target.case_sensitive = False
1851
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1852
self.assertEqual('file.moved', target.id2path('lower-id'))
1853
self.assertEqual('FILE', target.id2path('upper-id'))
1856
class MockTransform(object):
1858
def has_named_child(self, by_parent, parent_id, name):
1859
for child_id in by_parent[parent_id]:
1863
elif name == "name.~%s~" % child_id:
1868
class MockEntry(object):
1870
object.__init__(self)
1874
class TestGetBackupName(TestCase):
1875
def test_get_backup_name(self):
1876
tt = MockTransform()
1877
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1878
self.assertEqual(name, 'name.~1~')
1879
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1880
self.assertEqual(name, 'name.~2~')
1881
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1882
self.assertEqual(name, 'name.~1~')
1883
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1884
self.assertEqual(name, 'name.~1~')
1885
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1886
self.assertEqual(name, 'name.~4~')
1889
class TestFileMover(tests.TestCaseWithTransport):
1891
def test_file_mover(self):
1892
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1893
mover = _FileMover()
1894
mover.rename('a', 'q')
1895
self.failUnlessExists('q')
1896
self.failIfExists('a')
1897
self.failUnlessExists('q/b')
1898
self.failUnlessExists('c')
1899
self.failUnlessExists('c/d')
1901
def test_pre_delete_rollback(self):
1902
self.build_tree(['a/'])
1903
mover = _FileMover()
1904
mover.pre_delete('a', 'q')
1905
self.failUnlessExists('q')
1906
self.failIfExists('a')
1908
self.failIfExists('q')
1909
self.failUnlessExists('a')
1911
def test_apply_deletions(self):
1912
self.build_tree(['a/', 'b/'])
1913
mover = _FileMover()
1914
mover.pre_delete('a', 'q')
1915
mover.pre_delete('b', 'r')
1916
self.failUnlessExists('q')
1917
self.failUnlessExists('r')
1918
self.failIfExists('a')
1919
self.failIfExists('b')
1920
mover.apply_deletions()
1921
self.failIfExists('q')
1922
self.failIfExists('r')
1923
self.failIfExists('a')
1924
self.failIfExists('b')
1926
def test_file_mover_rollback(self):
1927
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1928
mover = _FileMover()
1929
mover.rename('c/d', 'c/f')
1930
mover.rename('c/e', 'c/d')
1932
mover.rename('a', 'c')
1933
except errors.FileExists, e:
1935
self.failUnlessExists('a')
1936
self.failUnlessExists('c/d')
1939
class Bogus(Exception):
1943
class TestTransformRollback(tests.TestCaseWithTransport):
1945
class ExceptionFileMover(_FileMover):
1947
def __init__(self, bad_source=None, bad_target=None):
1948
_FileMover.__init__(self)
1949
self.bad_source = bad_source
1950
self.bad_target = bad_target
1952
def rename(self, source, target):
1953
if (self.bad_source is not None and
1954
source.endswith(self.bad_source)):
1956
elif (self.bad_target is not None and
1957
target.endswith(self.bad_target)):
1960
_FileMover.rename(self, source, target)
1962
def test_rollback_rename(self):
1963
tree = self.make_branch_and_tree('.')
1964
self.build_tree(['a/', 'a/b'])
1965
tt = TreeTransform(tree)
1966
self.addCleanup(tt.finalize)
1967
a_id = tt.trans_id_tree_path('a')
1968
tt.adjust_path('c', tt.root, a_id)
1969
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1970
self.assertRaises(Bogus, tt.apply,
1971
_mover=self.ExceptionFileMover(bad_source='a'))
1972
self.failUnlessExists('a')
1973
self.failUnlessExists('a/b')
1975
self.failUnlessExists('c')
1976
self.failUnlessExists('c/d')
1978
def test_rollback_rename_into_place(self):
1979
tree = self.make_branch_and_tree('.')
1980
self.build_tree(['a/', 'a/b'])
1981
tt = TreeTransform(tree)
1982
self.addCleanup(tt.finalize)
1983
a_id = tt.trans_id_tree_path('a')
1984
tt.adjust_path('c', tt.root, a_id)
1985
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1986
self.assertRaises(Bogus, tt.apply,
1987
_mover=self.ExceptionFileMover(bad_target='c/d'))
1988
self.failUnlessExists('a')
1989
self.failUnlessExists('a/b')
1991
self.failUnlessExists('c')
1992
self.failUnlessExists('c/d')
1994
def test_rollback_deletion(self):
1995
tree = self.make_branch_and_tree('.')
1996
self.build_tree(['a/', 'a/b'])
1997
tt = TreeTransform(tree)
1998
self.addCleanup(tt.finalize)
1999
a_id = tt.trans_id_tree_path('a')
2000
tt.delete_contents(a_id)
2001
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2002
self.assertRaises(Bogus, tt.apply,
2003
_mover=self.ExceptionFileMover(bad_target='d'))
2004
self.failUnlessExists('a')
2005
self.failUnlessExists('a/b')
2007
def test_resolve_no_parent(self):
2008
wt = self.make_branch_and_tree('.')
2009
tt = TreeTransform(wt)
2010
self.addCleanup(tt.finalize)
2011
parent = tt.trans_id_file_id('parent-id')
2012
tt.new_file('file', parent, 'Contents')
2013
resolve_conflicts(tt)
2016
class TestTransformPreview(tests.TestCaseWithTransport):
2018
def create_tree(self):
2019
tree = self.make_branch_and_tree('.')
2020
self.build_tree_contents([('a', 'content 1')])
2021
tree.add('a', 'a-id')
2022
tree.commit('rev1', rev_id='rev1')
2023
return tree.branch.repository.revision_tree('rev1')
2025
def get_empty_preview(self):
2026
repository = self.make_repository('repo')
2027
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2028
preview = TransformPreview(tree)
2029
self.addCleanup(preview.finalize)
2032
def test_transform_preview(self):
2033
revision_tree = self.create_tree()
2034
preview = TransformPreview(revision_tree)
2035
self.addCleanup(preview.finalize)
2037
def test_transform_preview_tree(self):
2038
revision_tree = self.create_tree()
2039
preview = TransformPreview(revision_tree)
2040
self.addCleanup(preview.finalize)
2041
preview.get_preview_tree()
2043
def test_transform_new_file(self):
2044
revision_tree = self.create_tree()
2045
preview = TransformPreview(revision_tree)
2046
self.addCleanup(preview.finalize)
2047
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2048
preview_tree = preview.get_preview_tree()
2049
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2051
preview_tree.get_file('file2-id').read(), 'content B\n')
2053
def test_diff_preview_tree(self):
2054
revision_tree = self.create_tree()
2055
preview = TransformPreview(revision_tree)
2056
self.addCleanup(preview.finalize)
2057
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2058
preview_tree = preview.get_preview_tree()
2060
show_diff_trees(revision_tree, preview_tree, out)
2061
lines = out.getvalue().splitlines()
2062
self.assertEqual(lines[0], "=== added file 'file2'")
2063
# 3 lines of diff administrivia
2064
self.assertEqual(lines[4], "+content B")
2066
def test_transform_conflicts(self):
2067
revision_tree = self.create_tree()
2068
preview = TransformPreview(revision_tree)
2069
self.addCleanup(preview.finalize)
2070
preview.new_file('a', preview.root, 'content 2')
2071
resolve_conflicts(preview)
2072
trans_id = preview.trans_id_file_id('a-id')
2073
self.assertEqual('a.moved', preview.final_name(trans_id))
2075
def get_tree_and_preview_tree(self):
2076
revision_tree = self.create_tree()
2077
preview = TransformPreview(revision_tree)
2078
self.addCleanup(preview.finalize)
2079
a_trans_id = preview.trans_id_file_id('a-id')
2080
preview.delete_contents(a_trans_id)
2081
preview.create_file('b content', a_trans_id)
2082
preview_tree = preview.get_preview_tree()
2083
return revision_tree, preview_tree
2085
def test_iter_changes(self):
2086
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2087
root = revision_tree.inventory.root.file_id
2088
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2089
(root, root), ('a', 'a'), ('file', 'file'),
2091
list(preview_tree.iter_changes(revision_tree)))
2093
def test_include_unchanged_value_error(self):
2094
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2095
e = self.assertRaises(ValueError, preview_tree.iter_changes,
2096
revision_tree, include_unchanged=True)
2097
self.assertEqual('include_unchanged is not supported', str(e))
2099
def test_specific_files(self):
2100
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2101
e = self.assertRaises(ValueError, preview_tree.iter_changes,
2102
revision_tree, specific_files=['pete'])
2103
self.assertEqual('specific_files is not supported', str(e))
2105
def test_want_unversioned_value_error(self):
2106
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2107
e = self.assertRaises(ValueError, preview_tree.iter_changes,
2108
revision_tree, want_unversioned=True)
2109
self.assertEqual('want_unversioned is not supported', str(e))
2111
def test_ignore_extra_trees_no_specific_files(self):
2112
# extra_trees is harmless without specific_files, so we'll silently
2113
# accept it, even though we won't use it.
2114
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2115
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2117
def test_ignore_require_versioned_no_specific_files(self):
2118
# require_versioned is meaningless without specific_files.
2119
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2120
preview_tree.iter_changes(revision_tree, require_versioned=False)
2122
def test_ignore_pb(self):
2123
# pb could be supported, but TT.iter_changes doesn't support it.
2124
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2125
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2127
def test_kind(self):
2128
revision_tree = self.create_tree()
2129
preview = TransformPreview(revision_tree)
2130
self.addCleanup(preview.finalize)
2131
preview.new_file('file', preview.root, 'contents', 'file-id')
2132
preview.new_directory('directory', preview.root, 'dir-id')
2133
preview_tree = preview.get_preview_tree()
2134
self.assertEqual('file', preview_tree.kind('file-id'))
2135
self.assertEqual('directory', preview_tree.kind('dir-id'))
2137
def test_get_file_mtime(self):
2138
preview = self.get_empty_preview()
2139
file_trans_id = preview.new_file('file', preview.root, 'contents',
2141
limbo_path = preview._limbo_name(file_trans_id)
2142
preview_tree = preview.get_preview_tree()
2143
self.assertEqual(os.stat(limbo_path).st_mtime,
2144
preview_tree.get_file_mtime('file-id'))
2146
def test_get_file(self):
2147
preview = self.get_empty_preview()
2148
preview.new_file('file', preview.root, 'contents', 'file-id')
2149
preview_tree = preview.get_preview_tree()
2150
tree_file = preview_tree.get_file('file-id')
2152
self.assertEqual('contents', tree_file.read())
2156
def test_get_symlink_target(self):
2157
self.requireFeature(SymlinkFeature)
2158
preview = self.get_empty_preview()
2159
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2160
preview_tree = preview.get_preview_tree()
2161
self.assertEqual('target',
2162
preview_tree.get_symlink_target('symlink-id'))
2164
def test_all_file_ids(self):
2165
tree = self.make_branch_and_tree('tree')
2166
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2167
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2168
preview = TransformPreview(tree)
2169
self.addCleanup(preview.finalize)
2170
preview.unversion_file(preview.trans_id_file_id('b-id'))
2171
c_trans_id = preview.trans_id_file_id('c-id')
2172
preview.unversion_file(c_trans_id)
2173
preview.version_file('c-id', c_trans_id)
2174
preview_tree = preview.get_preview_tree()
2175
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2176
preview_tree.all_file_ids())
2178
def test_path2id_deleted_unchanged(self):
2179
tree = self.make_branch_and_tree('tree')
2180
self.build_tree(['tree/unchanged', 'tree/deleted'])
2181
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2182
preview = TransformPreview(tree)
2183
self.addCleanup(preview.finalize)
2184
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2185
preview_tree = preview.get_preview_tree()
2186
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2187
self.assertIs(None, preview_tree.path2id('deleted'))
2189
def test_path2id_created(self):
2190
tree = self.make_branch_and_tree('tree')
2191
self.build_tree(['tree/unchanged'])
2192
tree.add(['unchanged'], ['unchanged-id'])
2193
preview = TransformPreview(tree)
2194
self.addCleanup(preview.finalize)
2195
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2196
'contents', 'new-id')
2197
preview_tree = preview.get_preview_tree()
2198
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2200
def test_path2id_moved(self):
2201
tree = self.make_branch_and_tree('tree')
2202
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2203
tree.add(['old_parent', 'old_parent/child'],
2204
['old_parent-id', 'child-id'])
2205
preview = TransformPreview(tree)
2206
self.addCleanup(preview.finalize)
2207
new_parent = preview.new_directory('new_parent', preview.root,
2209
preview.adjust_path('child', new_parent,
2210
preview.trans_id_file_id('child-id'))
2211
preview_tree = preview.get_preview_tree()
2212
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2213
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2215
def test_path2id_renamed_parent(self):
2216
tree = self.make_branch_and_tree('tree')
2217
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2218
tree.add(['old_name', 'old_name/child'],
2219
['parent-id', 'child-id'])
2220
preview = TransformPreview(tree)
2221
self.addCleanup(preview.finalize)
2222
preview.adjust_path('new_name', preview.root,
2223
preview.trans_id_file_id('parent-id'))
2224
preview_tree = preview.get_preview_tree()
2225
self.assertIs(None, preview_tree.path2id('old_name/child'))
2226
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2228
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2229
preview_tree = tt.get_preview_tree()
2230
preview_result = list(preview_tree.iter_entries_by_dir(
2234
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2235
self.assertEqual(actual_result, preview_result)
2237
def test_iter_entries_by_dir_new(self):
2238
tree = self.make_branch_and_tree('tree')
2239
tt = TreeTransform(tree)
2240
tt.new_file('new', tt.root, 'contents', 'new-id')
2241
self.assertMatchingIterEntries(tt)
2243
def test_iter_entries_by_dir_deleted(self):
2244
tree = self.make_branch_and_tree('tree')
2245
self.build_tree(['tree/deleted'])
2246
tree.add('deleted', 'deleted-id')
2247
tt = TreeTransform(tree)
2248
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2249
self.assertMatchingIterEntries(tt)
2251
def test_iter_entries_by_dir_unversioned(self):
2252
tree = self.make_branch_and_tree('tree')
2253
self.build_tree(['tree/removed'])
2254
tree.add('removed', 'removed-id')
2255
tt = TreeTransform(tree)
2256
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2257
self.assertMatchingIterEntries(tt)
2259
def test_iter_entries_by_dir_moved(self):
2260
tree = self.make_branch_and_tree('tree')
2261
self.build_tree(['tree/moved', 'tree/new_parent/'])
2262
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2263
tt = TreeTransform(tree)
2264
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2265
tt.trans_id_file_id('moved-id'))
2266
self.assertMatchingIterEntries(tt)
2268
def test_iter_entries_by_dir_specific_file_ids(self):
2269
tree = self.make_branch_and_tree('tree')
2270
tree.set_root_id('tree-root-id')
2271
self.build_tree(['tree/parent/', 'tree/parent/child'])
2272
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2273
tt = TreeTransform(tree)
2274
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2276
def test_symlink_content_summary(self):
2277
self.requireFeature(SymlinkFeature)
2278
preview = self.get_empty_preview()
2279
preview.new_symlink('path', preview.root, 'target', 'path-id')
2280
summary = preview.get_preview_tree().path_content_summary('path')
2281
self.assertEqual(('symlink', None, None, 'target'), summary)
2283
def test_missing_content_summary(self):
2284
preview = self.get_empty_preview()
2285
summary = preview.get_preview_tree().path_content_summary('path')
2286
self.assertEqual(('missing', None, None, None), summary)
2288
def test_deleted_content_summary(self):
2289
tree = self.make_branch_and_tree('tree')
2290
self.build_tree(['tree/path/'])
2292
preview = TransformPreview(tree)
2293
self.addCleanup(preview.finalize)
2294
preview.delete_contents(preview.trans_id_tree_path('path'))
2295
summary = preview.get_preview_tree().path_content_summary('path')
2296
self.assertEqual(('missing', None, None, None), summary)
2298
def test_file_content_summary_executable(self):
2299
if not osutils.supports_executable():
2300
raise TestNotApplicable()
2301
preview = self.get_empty_preview()
2302
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2303
preview.set_executability(True, path_id)
2304
summary = preview.get_preview_tree().path_content_summary('path')
2305
self.assertEqual(4, len(summary))
2306
self.assertEqual('file', summary[0])
2307
# size must be known
2308
self.assertEqual(len('contents'), summary[1])
2310
self.assertEqual(True, summary[2])
2311
# will not have hash (not cheap to determine)
2312
self.assertIs(None, summary[3])
2314
def test_change_executability(self):
2315
if not osutils.supports_executable():
2316
raise TestNotApplicable()
2317
tree = self.make_branch_and_tree('tree')
2318
self.build_tree(['tree/path'])
2320
preview = TransformPreview(tree)
2321
self.addCleanup(preview.finalize)
2322
path_id = preview.trans_id_tree_path('path')
2323
preview.set_executability(True, path_id)
2324
summary = preview.get_preview_tree().path_content_summary('path')
2325
self.assertEqual(True, summary[2])
2327
def test_file_content_summary_non_exec(self):
2328
preview = self.get_empty_preview()
2329
preview.new_file('path', preview.root, 'contents', 'path-id')
2330
summary = preview.get_preview_tree().path_content_summary('path')
2331
self.assertEqual(4, len(summary))
2332
self.assertEqual('file', summary[0])
2333
# size must be known
2334
self.assertEqual(len('contents'), summary[1])
2336
if osutils.supports_executable():
2337
self.assertEqual(False, summary[2])
2339
self.assertEqual(None, summary[2])
2340
# will not have hash (not cheap to determine)
2341
self.assertIs(None, summary[3])
2343
def test_dir_content_summary(self):
2344
preview = self.get_empty_preview()
2345
preview.new_directory('path', preview.root, 'path-id')
2346
summary = preview.get_preview_tree().path_content_summary('path')
2347
self.assertEqual(('directory', None, None, None), summary)
2349
def test_tree_content_summary(self):
2350
preview = self.get_empty_preview()
2351
path = preview.new_directory('path', preview.root, 'path-id')
2352
preview.set_tree_reference('rev-1', path)
2353
summary = preview.get_preview_tree().path_content_summary('path')
2354
self.assertEqual(4, len(summary))
2355
self.assertEqual('tree-reference', summary[0])
2357
def test_annotate(self):
2358
tree = self.make_branch_and_tree('tree')
2359
self.build_tree_contents([('tree/file', 'a\n')])
2360
tree.add('file', 'file-id')
2361
tree.commit('a', rev_id='one')
2362
self.build_tree_contents([('tree/file', 'a\nb\n')])
2363
preview = TransformPreview(tree)
2364
self.addCleanup(preview.finalize)
2365
file_trans_id = preview.trans_id_file_id('file-id')
2366
preview.delete_contents(file_trans_id)
2367
preview.create_file('a\nb\nc\n', file_trans_id)
2368
preview_tree = preview.get_preview_tree()
2374
annotation = preview_tree.annotate_iter('file-id', 'me:')
2375
self.assertEqual(expected, annotation)
2377
def test_annotate_missing(self):
2378
preview = self.get_empty_preview()
2379
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2380
preview_tree = preview.get_preview_tree()
2386
annotation = preview_tree.annotate_iter('file-id', 'me:')
2387
self.assertEqual(expected, annotation)
2389
def test_annotate_rename(self):
2390
tree = self.make_branch_and_tree('tree')
2391
self.build_tree_contents([('tree/file', 'a\n')])
2392
tree.add('file', 'file-id')
2393
tree.commit('a', rev_id='one')
2394
preview = TransformPreview(tree)
2395
self.addCleanup(preview.finalize)
2396
file_trans_id = preview.trans_id_file_id('file-id')
2397
preview.adjust_path('newname', preview.root, file_trans_id)
2398
preview_tree = preview.get_preview_tree()
2402
annotation = preview_tree.annotate_iter('file-id', 'me:')
2403
self.assertEqual(expected, annotation)
2405
def test_annotate_deleted(self):
2406
tree = self.make_branch_and_tree('tree')
2407
self.build_tree_contents([('tree/file', 'a\n')])
2408
tree.add('file', 'file-id')
2409
tree.commit('a', rev_id='one')
2410
self.build_tree_contents([('tree/file', 'a\nb\n')])
2411
preview = TransformPreview(tree)
2412
self.addCleanup(preview.finalize)
2413
file_trans_id = preview.trans_id_file_id('file-id')
2414
preview.delete_contents(file_trans_id)
2415
preview_tree = preview.get_preview_tree()
2416
annotation = preview_tree.annotate_iter('file-id', 'me:')
2417
self.assertIs(None, annotation)
2419
def test_stored_kind(self):
2420
preview = self.get_empty_preview()
2421
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2422
preview_tree = preview.get_preview_tree()
2423
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2425
def test_is_executable(self):
2426
preview = self.get_empty_preview()
2427
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2428
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2429
preview_tree = preview.get_preview_tree()
2430
self.assertEqual(True, preview_tree.is_executable('file-id'))
2432
def test_get_set_parent_ids(self):
2433
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2434
self.assertEqual([], preview_tree.get_parent_ids())
2435
preview_tree.set_parent_ids(['rev-1'])
2436
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2438
def test_plan_file_merge(self):
2439
work_a = self.make_branch_and_tree('wta')
2440
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2441
work_a.add('file', 'file-id')
2442
base_id = work_a.commit('base version')
2443
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2444
preview = TransformPreview(work_a)
2445
self.addCleanup(preview.finalize)
2446
trans_id = preview.trans_id_file_id('file-id')
2447
preview.delete_contents(trans_id)
2448
preview.create_file('b\nc\nd\ne\n', trans_id)
2449
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2450
tree_a = preview.get_preview_tree()
2451
tree_a.set_parent_ids([base_id])
2453
('killed-a', 'a\n'),
2454
('killed-b', 'b\n'),
2455
('unchanged', 'c\n'),
2456
('unchanged', 'd\n'),
2459
], list(tree_a.plan_file_merge('file-id', tree_b)))
2461
def test_plan_file_merge_revision_tree(self):
2462
work_a = self.make_branch_and_tree('wta')
2463
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2464
work_a.add('file', 'file-id')
2465
base_id = work_a.commit('base version')
2466
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2467
preview = TransformPreview(work_a.basis_tree())
2468
self.addCleanup(preview.finalize)
2469
trans_id = preview.trans_id_file_id('file-id')
2470
preview.delete_contents(trans_id)
2471
preview.create_file('b\nc\nd\ne\n', trans_id)
2472
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2473
tree_a = preview.get_preview_tree()
2474
tree_a.set_parent_ids([base_id])
2476
('killed-a', 'a\n'),
2477
('killed-b', 'b\n'),
2478
('unchanged', 'c\n'),
2479
('unchanged', 'd\n'),
2482
], list(tree_a.plan_file_merge('file-id', tree_b)))
2484
def test_walkdirs(self):
2485
preview = self.get_empty_preview()
2486
preview.version_file('tree-root', preview.root)
2487
preview_tree = preview.get_preview_tree()
2488
file_trans_id = preview.new_file('a', preview.root, 'contents',
2490
expected = [(('', 'tree-root'),
2491
[('a', 'a', 'file', None, 'a-id', 'file')])]
2492
self.assertEqual(expected, list(preview_tree.walkdirs()))
2494
def test_extras(self):
2495
work_tree = self.make_branch_and_tree('tree')
2496
self.build_tree(['tree/removed-file', 'tree/existing-file',
2497
'tree/not-removed-file'])
2498
work_tree.add(['removed-file', 'not-removed-file'])
2499
preview = TransformPreview(work_tree)
2500
self.addCleanup(preview.finalize)
2501
preview.new_file('new-file', preview.root, 'contents')
2502
preview.new_file('new-versioned-file', preview.root, 'contents',
2504
tree = preview.get_preview_tree()
2505
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2506
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2509
def test_merge_into_preview(self):
2510
work_tree = self.make_branch_and_tree('tree')
2511
self.build_tree_contents([('tree/file','b\n')])
2512
work_tree.add('file', 'file-id')
2513
work_tree.commit('first commit')
2514
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2515
self.build_tree_contents([('child/file','b\nc\n')])
2516
child_tree.commit('child commit')
2517
child_tree.lock_write()
2518
self.addCleanup(child_tree.unlock)
2519
work_tree.lock_write()
2520
self.addCleanup(work_tree.unlock)
2521
preview = TransformPreview(work_tree)
2522
self.addCleanup(preview.finalize)
2523
preview_tree = preview.get_preview_tree()
2524
file_trans_id = preview.trans_id_file_id('file-id')
2525
preview.delete_contents(file_trans_id)
2526
preview.create_file('a\nb\n', file_trans_id)
2527
pb = progress.DummyProgress()
2528
merger = Merger.from_revision_ids(pb, preview_tree,
2529
child_tree.branch.last_revision(),
2530
other_branch=child_tree.branch,
2531
tree_branch=work_tree.branch)
2532
merger.merge_type = Merge3Merger
2533
tt = merger.make_merger().make_preview_transform()
2534
self.addCleanup(tt.finalize)
2535
final_tree = tt.get_preview_tree()
2536
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2538
def test_merge_preview_into_workingtree(self):
2539
tree = self.make_branch_and_tree('tree')
2540
tt = TransformPreview(tree)
2541
self.addCleanup(tt.finalize)
2542
tt.new_file('name', tt.root, 'content', 'file-id')
2543
tree2 = self.make_branch_and_tree('tree2')
2544
pb = progress.DummyProgress()
2545
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2546
pb, tree.basis_tree())
2547
merger.merge_type = Merge3Merger
2550
def test_merge_preview_into_workingtree_handles_conflicts(self):
2551
tree = self.make_branch_and_tree('tree')
2552
self.build_tree_contents([('tree/foo', 'bar')])
2553
tree.add('foo', 'foo-id')
2555
tt = TransformPreview(tree)
2556
self.addCleanup(tt.finalize)
2557
trans_id = tt.trans_id_file_id('foo-id')
2558
tt.delete_contents(trans_id)
2559
tt.create_file('baz', trans_id)
2560
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2561
self.build_tree_contents([('tree2/foo', 'qux')])
2562
pb = progress.DummyProgress()
2563
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2564
pb, tree.basis_tree())
2565
merger.merge_type = Merge3Merger
2568
def test_is_executable(self):
2569
tree = self.make_branch_and_tree('tree')
2570
preview = TransformPreview(tree)
2571
self.addCleanup(preview.finalize)
2572
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2573
preview_tree = preview.get_preview_tree()
2574
self.assertEqual(False, preview_tree.is_executable('baz-id',
2576
self.assertEqual(False, preview_tree.is_executable('baz-id'))