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)
57
from bzrlib.util import bencode
60
class TestTreeTransform(tests.TestCaseWithTransport):
63
super(TestTreeTransform, self).setUp()
64
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
67
def get_transform(self):
68
transform = TreeTransform(self.wt)
69
self.addCleanup(transform.finalize)
70
return transform, transform.root
72
def test_existing_limbo(self):
73
transform, root = self.get_transform()
74
limbo_name = transform._limbodir
75
deletion_path = transform._deletiondir
76
os.mkdir(pathjoin(limbo_name, 'hehe'))
77
self.assertRaises(ImmortalLimbo, transform.apply)
78
self.assertRaises(LockError, self.wt.unlock)
79
self.assertRaises(ExistingLimbo, self.get_transform)
80
self.assertRaises(LockError, self.wt.unlock)
81
os.rmdir(pathjoin(limbo_name, 'hehe'))
83
os.rmdir(deletion_path)
84
transform, root = self.get_transform()
87
def test_existing_pending_deletion(self):
88
transform, root = self.get_transform()
89
deletion_path = self._limbodir = urlutils.local_path_from_url(
90
transform._tree._transport.abspath('pending-deletion'))
91
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
92
self.assertRaises(ImmortalPendingDeletion, transform.apply)
93
self.assertRaises(LockError, self.wt.unlock)
94
self.assertRaises(ExistingPendingDeletion, self.get_transform)
97
transform, root = self.get_transform()
98
self.wt.lock_tree_write()
99
self.addCleanup(self.wt.unlock)
100
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
101
imaginary_id = transform.trans_id_tree_path('imaginary')
102
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
103
self.assertEqual(imaginary_id, imaginary_id2)
104
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
105
self.assertEqual(transform.final_kind(root), 'directory')
106
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
107
trans_id = transform.create_path('name', root)
108
self.assertIs(transform.final_file_id(trans_id), None)
109
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
110
transform.create_file('contents', trans_id)
111
transform.set_executability(True, trans_id)
112
transform.version_file('my_pretties', trans_id)
113
self.assertRaises(DuplicateKey, transform.version_file,
114
'my_pretties', trans_id)
115
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
116
self.assertEqual(transform.final_parent(trans_id), root)
117
self.assertIs(transform.final_parent(root), ROOT_PARENT)
118
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
119
oz_id = transform.create_path('oz', root)
120
transform.create_directory(oz_id)
121
transform.version_file('ozzie', oz_id)
122
trans_id2 = transform.create_path('name2', root)
123
transform.create_file('contents', trans_id2)
124
transform.set_executability(False, trans_id2)
125
transform.version_file('my_pretties2', trans_id2)
126
modified_paths = transform.apply().modified_paths
127
self.assertEqual('contents', self.wt.get_file_byname('name').read())
128
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
129
self.assertIs(self.wt.is_executable('my_pretties'), True)
130
self.assertIs(self.wt.is_executable('my_pretties2'), False)
131
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
132
self.assertEqual(len(modified_paths), 3)
133
tree_mod_paths = [self.wt.id2abspath(f) for f in
134
('ozzie', 'my_pretties', 'my_pretties2')]
135
self.assertSubset(tree_mod_paths, modified_paths)
136
# is it safe to finalize repeatedly?
140
def test_hardlink(self):
141
self.requireFeature(HardlinkFeature)
142
transform, root = self.get_transform()
143
transform.new_file('file1', root, 'contents')
145
target = self.make_branch_and_tree('target')
146
target_transform = TreeTransform(target)
147
trans_id = target_transform.create_path('file1', target_transform.root)
148
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
149
target_transform.apply()
150
self.failUnlessExists('target/file1')
151
source_stat = os.stat(self.wt.abspath('file1'))
152
target_stat = os.stat('target/file1')
153
self.assertEqual(source_stat, target_stat)
155
def test_convenience(self):
156
transform, root = self.get_transform()
157
self.wt.lock_tree_write()
158
self.addCleanup(self.wt.unlock)
159
trans_id = transform.new_file('name', root, 'contents',
161
oz = transform.new_directory('oz', root, 'oz-id')
162
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
163
toto = transform.new_file('toto', dorothy, 'toto-contents',
166
self.assertEqual(len(transform.find_conflicts()), 0)
168
self.assertRaises(ReusingTransform, transform.find_conflicts)
169
self.assertEqual('contents', file(self.wt.abspath('name')).read())
170
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
171
self.assertIs(self.wt.is_executable('my_pretties'), True)
172
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
173
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
174
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
176
self.assertEqual('toto-contents',
177
self.wt.get_file_byname('oz/dorothy/toto').read())
178
self.assertIs(self.wt.is_executable('toto-id'), False)
180
def test_tree_reference(self):
181
transform, root = self.get_transform()
182
tree = transform._tree
183
trans_id = transform.new_directory('reference', root, 'subtree-id')
184
transform.set_tree_reference('subtree-revision', trans_id)
187
self.addCleanup(tree.unlock)
188
self.assertEqual('subtree-revision',
189
tree.inventory['subtree-id'].reference_revision)
191
def test_conflicts(self):
192
transform, root = self.get_transform()
193
trans_id = transform.new_file('name', root, 'contents',
195
self.assertEqual(len(transform.find_conflicts()), 0)
196
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
197
self.assertEqual(transform.find_conflicts(),
198
[('duplicate', trans_id, trans_id2, 'name')])
199
self.assertRaises(MalformedTransform, transform.apply)
200
transform.adjust_path('name', trans_id, trans_id2)
201
self.assertEqual(transform.find_conflicts(),
202
[('non-directory parent', trans_id)])
203
tinman_id = transform.trans_id_tree_path('tinman')
204
transform.adjust_path('name', tinman_id, trans_id2)
205
self.assertEqual(transform.find_conflicts(),
206
[('unversioned parent', tinman_id),
207
('missing parent', tinman_id)])
208
lion_id = transform.create_path('lion', root)
209
self.assertEqual(transform.find_conflicts(),
210
[('unversioned parent', tinman_id),
211
('missing parent', tinman_id)])
212
transform.adjust_path('name', lion_id, trans_id2)
213
self.assertEqual(transform.find_conflicts(),
214
[('unversioned parent', lion_id),
215
('missing parent', lion_id)])
216
transform.version_file("Courage", lion_id)
217
self.assertEqual(transform.find_conflicts(),
218
[('missing parent', lion_id),
219
('versioning no contents', lion_id)])
220
transform.adjust_path('name2', root, trans_id2)
221
self.assertEqual(transform.find_conflicts(),
222
[('versioning no contents', lion_id)])
223
transform.create_file('Contents, okay?', lion_id)
224
transform.adjust_path('name2', trans_id2, trans_id2)
225
self.assertEqual(transform.find_conflicts(),
226
[('parent loop', trans_id2),
227
('non-directory parent', trans_id2)])
228
transform.adjust_path('name2', root, trans_id2)
229
oz_id = transform.new_directory('oz', root)
230
transform.set_executability(True, oz_id)
231
self.assertEqual(transform.find_conflicts(),
232
[('unversioned executability', oz_id)])
233
transform.version_file('oz-id', oz_id)
234
self.assertEqual(transform.find_conflicts(),
235
[('non-file executability', oz_id)])
236
transform.set_executability(None, oz_id)
237
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
239
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
240
self.assertEqual('contents', file(self.wt.abspath('name')).read())
241
transform2, root = self.get_transform()
242
oz_id = transform2.trans_id_tree_file_id('oz-id')
243
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
244
result = transform2.find_conflicts()
245
fp = FinalPaths(transform2)
246
self.assert_('oz/tip' in transform2._tree_path_ids)
247
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
248
self.assertEqual(len(result), 2)
249
self.assertEqual((result[0][0], result[0][1]),
250
('duplicate', newtip))
251
self.assertEqual((result[1][0], result[1][2]),
252
('duplicate id', newtip))
253
transform2.finalize()
254
transform3 = TreeTransform(self.wt)
255
self.addCleanup(transform3.finalize)
256
oz_id = transform3.trans_id_tree_file_id('oz-id')
257
transform3.delete_contents(oz_id)
258
self.assertEqual(transform3.find_conflicts(),
259
[('missing parent', oz_id)])
260
root_id = transform3.root
261
tip_id = transform3.trans_id_tree_file_id('tip-id')
262
transform3.adjust_path('tip', root_id, tip_id)
265
def test_conflict_on_case_insensitive(self):
266
tree = self.make_branch_and_tree('tree')
267
# Don't try this at home, kids!
268
# Force the tree to report that it is case sensitive, for conflict
270
tree.case_sensitive = True
271
transform = TreeTransform(tree)
272
self.addCleanup(transform.finalize)
273
transform.new_file('file', transform.root, 'content')
274
transform.new_file('FiLe', transform.root, 'content')
275
result = transform.find_conflicts()
276
self.assertEqual([], result)
278
# Force the tree to report that it is case insensitive, for conflict
280
tree.case_sensitive = False
281
transform = TreeTransform(tree)
282
self.addCleanup(transform.finalize)
283
transform.new_file('file', transform.root, 'content')
284
transform.new_file('FiLe', transform.root, 'content')
285
result = transform.find_conflicts()
286
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
288
def test_conflict_on_case_insensitive_existing(self):
289
tree = self.make_branch_and_tree('tree')
290
self.build_tree(['tree/FiLe'])
291
# Don't try this at home, kids!
292
# Force the tree to report that it is case sensitive, for conflict
294
tree.case_sensitive = True
295
transform = TreeTransform(tree)
296
self.addCleanup(transform.finalize)
297
transform.new_file('file', transform.root, 'content')
298
result = transform.find_conflicts()
299
self.assertEqual([], result)
301
# Force the tree to report that it is case insensitive, for conflict
303
tree.case_sensitive = False
304
transform = TreeTransform(tree)
305
self.addCleanup(transform.finalize)
306
transform.new_file('file', transform.root, 'content')
307
result = transform.find_conflicts()
308
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
310
def test_resolve_case_insensitive_conflict(self):
311
tree = self.make_branch_and_tree('tree')
312
# Don't try this at home, kids!
313
# Force the tree to report that it is case insensitive, for conflict
315
tree.case_sensitive = False
316
transform = TreeTransform(tree)
317
self.addCleanup(transform.finalize)
318
transform.new_file('file', transform.root, 'content')
319
transform.new_file('FiLe', transform.root, 'content')
320
resolve_conflicts(transform)
322
self.failUnlessExists('tree/file')
323
self.failUnlessExists('tree/FiLe.moved')
325
def test_resolve_checkout_case_conflict(self):
326
tree = self.make_branch_and_tree('tree')
327
# Don't try this at home, kids!
328
# Force the tree to report that it is case insensitive, for conflict
330
tree.case_sensitive = False
331
transform = TreeTransform(tree)
332
self.addCleanup(transform.finalize)
333
transform.new_file('file', transform.root, 'content')
334
transform.new_file('FiLe', transform.root, 'content')
335
resolve_conflicts(transform,
336
pass_func=lambda t, c: resolve_checkout(t, c, []))
338
self.failUnlessExists('tree/file')
339
self.failUnlessExists('tree/FiLe.moved')
341
def test_apply_case_conflict(self):
342
"""Ensure that a transform with case conflicts can always be applied"""
343
tree = self.make_branch_and_tree('tree')
344
transform = TreeTransform(tree)
345
self.addCleanup(transform.finalize)
346
transform.new_file('file', transform.root, 'content')
347
transform.new_file('FiLe', transform.root, 'content')
348
dir = transform.new_directory('dir', transform.root)
349
transform.new_file('dirfile', dir, 'content')
350
transform.new_file('dirFiLe', dir, 'content')
351
resolve_conflicts(transform)
353
self.failUnlessExists('tree/file')
354
if not os.path.exists('tree/FiLe.moved'):
355
self.failUnlessExists('tree/FiLe')
356
self.failUnlessExists('tree/dir/dirfile')
357
if not os.path.exists('tree/dir/dirFiLe.moved'):
358
self.failUnlessExists('tree/dir/dirFiLe')
360
def test_case_insensitive_limbo(self):
361
tree = self.make_branch_and_tree('tree')
362
# Don't try this at home, kids!
363
# Force the tree to report that it is case insensitive
364
tree.case_sensitive = False
365
transform = TreeTransform(tree)
366
self.addCleanup(transform.finalize)
367
dir = transform.new_directory('dir', transform.root)
368
first = transform.new_file('file', dir, 'content')
369
second = transform.new_file('FiLe', dir, 'content')
370
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
371
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
373
def test_add_del(self):
374
start, root = self.get_transform()
375
start.new_directory('a', root, 'a')
377
transform, root = self.get_transform()
378
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
379
transform.new_directory('a', root, 'a')
382
def test_unversioning(self):
383
create_tree, root = self.get_transform()
384
parent_id = create_tree.new_directory('parent', root, 'parent-id')
385
create_tree.new_file('child', parent_id, 'child', 'child-id')
387
unversion = TreeTransform(self.wt)
388
self.addCleanup(unversion.finalize)
389
parent = unversion.trans_id_tree_path('parent')
390
unversion.unversion_file(parent)
391
self.assertEqual(unversion.find_conflicts(),
392
[('unversioned parent', parent_id)])
393
file_id = unversion.trans_id_tree_file_id('child-id')
394
unversion.unversion_file(file_id)
397
def test_name_invariants(self):
398
create_tree, root = self.get_transform()
400
root = create_tree.root
401
create_tree.new_file('name1', root, 'hello1', 'name1')
402
create_tree.new_file('name2', root, 'hello2', 'name2')
403
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
404
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
405
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
406
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
409
mangle_tree,root = self.get_transform()
410
root = mangle_tree.root
412
name1 = mangle_tree.trans_id_tree_file_id('name1')
413
name2 = mangle_tree.trans_id_tree_file_id('name2')
414
mangle_tree.adjust_path('name2', root, name1)
415
mangle_tree.adjust_path('name1', root, name2)
417
#tests for deleting parent directories
418
ddir = mangle_tree.trans_id_tree_file_id('ddir')
419
mangle_tree.delete_contents(ddir)
420
dfile = mangle_tree.trans_id_tree_file_id('dfile')
421
mangle_tree.delete_versioned(dfile)
422
mangle_tree.unversion_file(dfile)
423
mfile = mangle_tree.trans_id_tree_file_id('mfile')
424
mangle_tree.adjust_path('mfile', root, mfile)
426
#tests for adding parent directories
427
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
428
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
429
mangle_tree.adjust_path('mfile2', newdir, mfile2)
430
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
431
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
432
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
433
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
435
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
436
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
437
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
438
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
439
self.assertEqual(file(mfile2_path).read(), 'later2')
440
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
441
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
442
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
443
self.assertEqual(file(newfile_path).read(), 'hello3')
444
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
445
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
446
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
448
def test_both_rename(self):
449
create_tree,root = self.get_transform()
450
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
451
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
453
mangle_tree,root = self.get_transform()
454
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
455
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
456
mangle_tree.adjust_path('test', root, selftest)
457
mangle_tree.adjust_path('test_too_much', root, selftest)
458
mangle_tree.set_executability(True, blackbox)
461
def test_both_rename2(self):
462
create_tree,root = self.get_transform()
463
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
464
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
465
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
466
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
469
mangle_tree,root = self.get_transform()
470
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
471
tests = mangle_tree.trans_id_tree_file_id('tests-id')
472
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
473
mangle_tree.adjust_path('selftest', bzrlib, tests)
474
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
475
mangle_tree.set_executability(True, test_too_much)
478
def test_both_rename3(self):
479
create_tree,root = self.get_transform()
480
tests = create_tree.new_directory('tests', root, 'tests-id')
481
create_tree.new_file('test_too_much.py', tests, 'hello1',
484
mangle_tree,root = self.get_transform()
485
tests = mangle_tree.trans_id_tree_file_id('tests-id')
486
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
487
mangle_tree.adjust_path('selftest', root, tests)
488
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
489
mangle_tree.set_executability(True, test_too_much)
492
def test_move_dangling_ie(self):
493
create_tree, root = self.get_transform()
495
root = create_tree.root
496
create_tree.new_file('name1', root, 'hello1', 'name1')
498
delete_contents, root = self.get_transform()
499
file = delete_contents.trans_id_tree_file_id('name1')
500
delete_contents.delete_contents(file)
501
delete_contents.apply()
502
move_id, root = self.get_transform()
503
name1 = move_id.trans_id_tree_file_id('name1')
504
newdir = move_id.new_directory('dir', root, 'newdir')
505
move_id.adjust_path('name2', newdir, name1)
508
def test_replace_dangling_ie(self):
509
create_tree, root = self.get_transform()
511
root = create_tree.root
512
create_tree.new_file('name1', root, 'hello1', 'name1')
514
delete_contents = TreeTransform(self.wt)
515
self.addCleanup(delete_contents.finalize)
516
file = delete_contents.trans_id_tree_file_id('name1')
517
delete_contents.delete_contents(file)
518
delete_contents.apply()
519
delete_contents.finalize()
520
replace = TreeTransform(self.wt)
521
self.addCleanup(replace.finalize)
522
name2 = replace.new_file('name2', root, 'hello2', 'name1')
523
conflicts = replace.find_conflicts()
524
name1 = replace.trans_id_tree_file_id('name1')
525
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
526
resolve_conflicts(replace)
529
def test_symlinks(self):
530
self.requireFeature(SymlinkFeature)
531
transform,root = self.get_transform()
532
oz_id = transform.new_directory('oz', root, 'oz-id')
533
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
535
wiz_id = transform.create_path('wizard2', oz_id)
536
transform.create_symlink('behind_curtain', wiz_id)
537
transform.version_file('wiz-id2', wiz_id)
538
transform.set_executability(True, wiz_id)
539
self.assertEqual(transform.find_conflicts(),
540
[('non-file executability', wiz_id)])
541
transform.set_executability(None, wiz_id)
543
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
544
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
545
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
547
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
550
def test_unable_create_symlink(self):
552
wt = self.make_branch_and_tree('.')
553
tt = TreeTransform(wt) # TreeTransform obtains write lock
555
tt.new_symlink('foo', tt.root, 'bar')
559
os_symlink = getattr(os, 'symlink', None)
562
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
564
"Unable to create symlink 'foo' on this platform",
568
os.symlink = os_symlink
570
def get_conflicted(self):
571
create,root = self.get_transform()
572
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
573
oz = create.new_directory('oz', root, 'oz-id')
574
create.new_directory('emeraldcity', oz, 'emerald-id')
576
conflicts,root = self.get_transform()
577
# set up duplicate entry, duplicate id
578
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
580
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
581
oz = conflicts.trans_id_tree_file_id('oz-id')
582
# set up DeletedParent parent conflict
583
conflicts.delete_versioned(oz)
584
emerald = conflicts.trans_id_tree_file_id('emerald-id')
585
# set up MissingParent conflict
586
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
587
conflicts.adjust_path('munchkincity', root, munchkincity)
588
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
590
conflicts.adjust_path('emeraldcity', emerald, emerald)
591
return conflicts, emerald, oz, old_dorothy, new_dorothy
593
def test_conflict_resolution(self):
594
conflicts, emerald, oz, old_dorothy, new_dorothy =\
595
self.get_conflicted()
596
resolve_conflicts(conflicts)
597
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
598
self.assertIs(conflicts.final_file_id(old_dorothy), None)
599
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
600
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
601
self.assertEqual(conflicts.final_parent(emerald), oz)
604
def test_cook_conflicts(self):
605
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
606
raw_conflicts = resolve_conflicts(tt)
607
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
608
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
609
'dorothy', None, 'dorothy-id')
610
self.assertEqual(cooked_conflicts[0], duplicate)
611
duplicate_id = DuplicateID('Unversioned existing file',
612
'dorothy.moved', 'dorothy', None,
614
self.assertEqual(cooked_conflicts[1], duplicate_id)
615
missing_parent = MissingParent('Created directory', 'munchkincity',
617
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
618
self.assertEqual(cooked_conflicts[2], missing_parent)
619
unversioned_parent = UnversionedParent('Versioned directory',
622
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
624
self.assertEqual(cooked_conflicts[3], unversioned_parent)
625
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
626
'oz/emeraldcity', 'emerald-id', 'emerald-id')
627
self.assertEqual(cooked_conflicts[4], deleted_parent)
628
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
629
self.assertEqual(cooked_conflicts[6], parent_loop)
630
self.assertEqual(len(cooked_conflicts), 7)
633
def test_string_conflicts(self):
634
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
635
raw_conflicts = resolve_conflicts(tt)
636
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
638
conflicts_s = [str(c) for c in cooked_conflicts]
639
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
640
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
641
'Moved existing file to '
643
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
644
'Unversioned existing file '
646
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
647
' munchkincity. Created directory.')
648
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
649
' versioned, but has versioned'
650
' children. Versioned directory.')
651
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
652
" is not empty. Not deleting.")
653
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
654
' versioned, but has versioned'
655
' children. Versioned directory.')
656
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
657
' oz/emeraldcity. Cancelled move.')
659
def prepare_wrong_parent_kind(self):
660
tt, root = self.get_transform()
661
tt.new_file('parent', root, 'contents', 'parent-id')
663
tt, root = self.get_transform()
664
parent_id = tt.trans_id_file_id('parent-id')
665
tt.new_file('child,', parent_id, 'contents2', 'file-id')
668
def test_find_conflicts_wrong_parent_kind(self):
669
tt = self.prepare_wrong_parent_kind()
672
def test_resolve_conflicts_wrong_existing_parent_kind(self):
673
tt = self.prepare_wrong_parent_kind()
674
raw_conflicts = resolve_conflicts(tt)
675
self.assertEqual(set([('non-directory parent', 'Created directory',
676
'new-3')]), raw_conflicts)
677
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
678
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
679
'parent-id')], cooked_conflicts)
681
self.assertEqual(None, self.wt.path2id('parent'))
682
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
684
def test_resolve_conflicts_wrong_new_parent_kind(self):
685
tt, root = self.get_transform()
686
parent_id = tt.new_directory('parent', root, 'parent-id')
687
tt.new_file('child,', parent_id, 'contents2', 'file-id')
689
tt, root = self.get_transform()
690
parent_id = tt.trans_id_file_id('parent-id')
691
tt.delete_contents(parent_id)
692
tt.create_file('contents', parent_id)
693
raw_conflicts = resolve_conflicts(tt)
694
self.assertEqual(set([('non-directory parent', 'Created directory',
695
'new-3')]), raw_conflicts)
697
self.assertEqual(None, self.wt.path2id('parent'))
698
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
700
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
701
tt, root = self.get_transform()
702
parent_id = tt.new_directory('parent', root)
703
tt.new_file('child,', parent_id, 'contents2')
705
tt, root = self.get_transform()
706
parent_id = tt.trans_id_tree_path('parent')
707
tt.delete_contents(parent_id)
708
tt.create_file('contents', parent_id)
709
resolve_conflicts(tt)
711
self.assertIs(None, self.wt.path2id('parent'))
712
self.assertIs(None, self.wt.path2id('parent.new'))
714
def test_moving_versioned_directories(self):
715
create, root = self.get_transform()
716
kansas = create.new_directory('kansas', root, 'kansas-id')
717
create.new_directory('house', kansas, 'house-id')
718
create.new_directory('oz', root, 'oz-id')
720
cyclone, root = self.get_transform()
721
oz = cyclone.trans_id_tree_file_id('oz-id')
722
house = cyclone.trans_id_tree_file_id('house-id')
723
cyclone.adjust_path('house', oz, house)
726
def test_moving_root(self):
727
create, root = self.get_transform()
728
fun = create.new_directory('fun', root, 'fun-id')
729
create.new_directory('sun', root, 'sun-id')
730
create.new_directory('moon', root, 'moon')
732
transform, root = self.get_transform()
733
transform.adjust_root_path('oldroot', fun)
734
new_root=transform.trans_id_tree_path('')
735
transform.version_file('new-root', new_root)
738
def test_renames(self):
739
create, root = self.get_transform()
740
old = create.new_directory('old-parent', root, 'old-id')
741
intermediate = create.new_directory('intermediate', old, 'im-id')
742
myfile = create.new_file('myfile', intermediate, 'myfile-text',
745
rename, root = self.get_transform()
746
old = rename.trans_id_file_id('old-id')
747
rename.adjust_path('new', root, old)
748
myfile = rename.trans_id_file_id('myfile-id')
749
rename.set_executability(True, myfile)
752
def test_set_executability_order(self):
753
"""Ensure that executability behaves the same, no matter what order.
755
- create file and set executability simultaneously
756
- create file and set executability afterward
757
- unsetting the executability of a file whose executability has not been
758
declared should throw an exception (this may happen when a
759
merge attempts to create a file with a duplicate ID)
761
transform, root = self.get_transform()
764
self.addCleanup(wt.unlock)
765
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
767
sac = transform.new_file('set_after_creation', root,
768
'Set after creation', 'sac')
769
transform.set_executability(True, sac)
770
uws = transform.new_file('unset_without_set', root, 'Unset badly',
772
self.assertRaises(KeyError, transform.set_executability, None, uws)
774
self.assertTrue(wt.is_executable('soc'))
775
self.assertTrue(wt.is_executable('sac'))
777
def test_preserve_mode(self):
778
"""File mode is preserved when replacing content"""
779
if sys.platform == 'win32':
780
raise TestSkipped('chmod has no effect on win32')
781
transform, root = self.get_transform()
782
transform.new_file('file1', root, 'contents', 'file1-id', True)
785
self.addCleanup(self.wt.unlock)
786
self.assertTrue(self.wt.is_executable('file1-id'))
787
transform, root = self.get_transform()
788
file1_id = transform.trans_id_tree_file_id('file1-id')
789
transform.delete_contents(file1_id)
790
transform.create_file('contents2', file1_id)
792
self.assertTrue(self.wt.is_executable('file1-id'))
794
def test__set_mode_stats_correctly(self):
795
"""_set_mode stats to determine file mode."""
796
if sys.platform == 'win32':
797
raise TestSkipped('chmod has no effect on win32')
801
def instrumented_stat(path):
802
stat_paths.append(path)
803
return real_stat(path)
805
transform, root = self.get_transform()
807
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
808
file_id='bar-id-1', executable=False)
811
transform, root = self.get_transform()
812
bar1_id = transform.trans_id_tree_path('bar')
813
bar2_id = transform.trans_id_tree_path('bar2')
815
os.stat = instrumented_stat
816
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
821
bar1_abspath = self.wt.abspath('bar')
822
self.assertEqual([bar1_abspath], stat_paths)
824
def test_iter_changes(self):
825
self.wt.set_root_id('eert_toor')
826
transform, root = self.get_transform()
827
transform.new_file('old', root, 'blah', 'id-1', True)
829
transform, root = self.get_transform()
831
self.assertEqual([], list(transform.iter_changes()))
832
old = transform.trans_id_tree_file_id('id-1')
833
transform.unversion_file(old)
834
self.assertEqual([('id-1', ('old', None), False, (True, False),
835
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
836
(True, True))], list(transform.iter_changes()))
837
transform.new_directory('new', root, 'id-1')
838
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
839
('eert_toor', 'eert_toor'), ('old', 'new'),
840
('file', 'directory'),
841
(True, False))], list(transform.iter_changes()))
845
def test_iter_changes_new(self):
846
self.wt.set_root_id('eert_toor')
847
transform, root = self.get_transform()
848
transform.new_file('old', root, 'blah')
850
transform, root = self.get_transform()
852
old = transform.trans_id_tree_path('old')
853
transform.version_file('id-1', old)
854
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
855
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
856
(False, False))], list(transform.iter_changes()))
860
def test_iter_changes_modifications(self):
861
self.wt.set_root_id('eert_toor')
862
transform, root = self.get_transform()
863
transform.new_file('old', root, 'blah', 'id-1')
864
transform.new_file('new', root, 'blah')
865
transform.new_directory('subdir', root, 'subdir-id')
867
transform, root = self.get_transform()
869
old = transform.trans_id_tree_path('old')
870
subdir = transform.trans_id_tree_file_id('subdir-id')
871
new = transform.trans_id_tree_path('new')
872
self.assertEqual([], list(transform.iter_changes()))
875
transform.delete_contents(old)
876
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
877
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
878
(False, False))], list(transform.iter_changes()))
881
transform.create_file('blah', old)
882
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
883
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
884
(False, False))], list(transform.iter_changes()))
885
transform.cancel_deletion(old)
886
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
887
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
888
(False, False))], list(transform.iter_changes()))
889
transform.cancel_creation(old)
891
# move file_id to a different file
892
self.assertEqual([], list(transform.iter_changes()))
893
transform.unversion_file(old)
894
transform.version_file('id-1', new)
895
transform.adjust_path('old', root, new)
896
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
897
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
898
(False, False))], list(transform.iter_changes()))
899
transform.cancel_versioning(new)
900
transform._removed_id = set()
903
self.assertEqual([], list(transform.iter_changes()))
904
transform.set_executability(True, old)
905
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
906
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
907
(False, True))], list(transform.iter_changes()))
908
transform.set_executability(None, old)
911
self.assertEqual([], list(transform.iter_changes()))
912
transform.adjust_path('new', root, old)
913
transform._new_parent = {}
914
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
915
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
916
(False, False))], list(transform.iter_changes()))
917
transform._new_name = {}
920
self.assertEqual([], list(transform.iter_changes()))
921
transform.adjust_path('new', subdir, old)
922
transform._new_name = {}
923
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
924
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
925
('file', 'file'), (False, False))],
926
list(transform.iter_changes()))
927
transform._new_path = {}
932
def test_iter_changes_modified_bleed(self):
933
self.wt.set_root_id('eert_toor')
934
"""Modified flag should not bleed from one change to another"""
935
# unfortunately, we have no guarantee that file1 (which is modified)
936
# will be applied before file2. And if it's applied after file2, it
937
# obviously can't bleed into file2's change output. But for now, it
939
transform, root = self.get_transform()
940
transform.new_file('file1', root, 'blah', 'id-1')
941
transform.new_file('file2', root, 'blah', 'id-2')
943
transform, root = self.get_transform()
945
transform.delete_contents(transform.trans_id_file_id('id-1'))
946
transform.set_executability(True,
947
transform.trans_id_file_id('id-2'))
948
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
949
('eert_toor', 'eert_toor'), ('file1', u'file1'),
950
('file', None), (False, False)),
951
('id-2', (u'file2', u'file2'), False, (True, True),
952
('eert_toor', 'eert_toor'), ('file2', u'file2'),
953
('file', 'file'), (False, True))],
954
list(transform.iter_changes()))
958
def test_iter_changes_move_missing(self):
959
"""Test moving ids with no files around"""
960
self.wt.set_root_id('toor_eert')
961
# Need two steps because versioning a non-existant file is a conflict.
962
transform, root = self.get_transform()
963
transform.new_directory('floater', root, 'floater-id')
965
transform, root = self.get_transform()
966
transform.delete_contents(transform.trans_id_tree_path('floater'))
968
transform, root = self.get_transform()
969
floater = transform.trans_id_tree_path('floater')
971
transform.adjust_path('flitter', root, floater)
972
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
973
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
974
(None, None), (False, False))], list(transform.iter_changes()))
978
def test_iter_changes_pointless(self):
979
"""Ensure that no-ops are not treated as modifications"""
980
self.wt.set_root_id('eert_toor')
981
transform, root = self.get_transform()
982
transform.new_file('old', root, 'blah', 'id-1')
983
transform.new_directory('subdir', root, 'subdir-id')
985
transform, root = self.get_transform()
987
old = transform.trans_id_tree_path('old')
988
subdir = transform.trans_id_tree_file_id('subdir-id')
989
self.assertEqual([], list(transform.iter_changes()))
990
transform.delete_contents(subdir)
991
transform.create_directory(subdir)
992
transform.set_executability(False, old)
993
transform.unversion_file(old)
994
transform.version_file('id-1', old)
995
transform.adjust_path('old', root, old)
996
self.assertEqual([], list(transform.iter_changes()))
1000
def test_rename_count(self):
1001
transform, root = self.get_transform()
1002
transform.new_file('name1', root, 'contents')
1003
self.assertEqual(transform.rename_count, 0)
1005
self.assertEqual(transform.rename_count, 1)
1006
transform2, root = self.get_transform()
1007
transform2.adjust_path('name2', root,
1008
transform2.trans_id_tree_path('name1'))
1009
self.assertEqual(transform2.rename_count, 0)
1011
self.assertEqual(transform2.rename_count, 2)
1013
def test_change_parent(self):
1014
"""Ensure that after we change a parent, the results are still right.
1016
Renames and parent changes on pending transforms can happen as part
1017
of conflict resolution, and are explicitly permitted by the
1020
This test ensures they work correctly with the rename-avoidance
1023
transform, root = self.get_transform()
1024
parent1 = transform.new_directory('parent1', root)
1025
child1 = transform.new_file('child1', parent1, 'contents')
1026
parent2 = transform.new_directory('parent2', root)
1027
transform.adjust_path('child1', parent2, child1)
1029
self.failIfExists(self.wt.abspath('parent1/child1'))
1030
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1031
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1032
# no rename for child1 (counting only renames during apply)
1033
self.failUnlessEqual(2, transform.rename_count)
1035
def test_cancel_parent(self):
1036
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1038
This is like the test_change_parent, except that we cancel the parent
1039
before adjusting the path. The transform must detect that the
1040
directory is non-empty, and move children to safe locations.
1042
transform, root = self.get_transform()
1043
parent1 = transform.new_directory('parent1', root)
1044
child1 = transform.new_file('child1', parent1, 'contents')
1045
child2 = transform.new_file('child2', parent1, 'contents')
1047
transform.cancel_creation(parent1)
1049
self.fail('Failed to move child1 before deleting parent1')
1050
transform.cancel_creation(child2)
1051
transform.create_directory(parent1)
1053
transform.cancel_creation(parent1)
1054
# If the transform incorrectly believes that child2 is still in
1055
# parent1's limbo directory, it will try to rename it and fail
1056
# because was already moved by the first cancel_creation.
1058
self.fail('Transform still thinks child2 is a child of parent1')
1059
parent2 = transform.new_directory('parent2', root)
1060
transform.adjust_path('child1', parent2, child1)
1062
self.failIfExists(self.wt.abspath('parent1'))
1063
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1064
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1065
self.failUnlessEqual(2, transform.rename_count)
1067
def test_adjust_and_cancel(self):
1068
"""Make sure adjust_path keeps track of limbo children properly"""
1069
transform, root = self.get_transform()
1070
parent1 = transform.new_directory('parent1', root)
1071
child1 = transform.new_file('child1', parent1, 'contents')
1072
parent2 = transform.new_directory('parent2', root)
1073
transform.adjust_path('child1', parent2, child1)
1074
transform.cancel_creation(child1)
1076
transform.cancel_creation(parent1)
1077
# if the transform thinks child1 is still in parent1's limbo
1078
# directory, it will attempt to move it and fail.
1080
self.fail('Transform still thinks child1 is a child of parent1')
1081
transform.finalize()
1083
def test_noname_contents(self):
1084
"""TreeTransform should permit deferring naming files."""
1085
transform, root = self.get_transform()
1086
parent = transform.trans_id_file_id('parent-id')
1088
transform.create_directory(parent)
1090
self.fail("Can't handle contents with no name")
1091
transform.finalize()
1093
def test_noname_contents_nested(self):
1094
"""TreeTransform should permit deferring naming files."""
1095
transform, root = self.get_transform()
1096
parent = transform.trans_id_file_id('parent-id')
1098
transform.create_directory(parent)
1100
self.fail("Can't handle contents with no name")
1101
child = transform.new_directory('child', parent)
1102
transform.adjust_path('parent', root, parent)
1104
self.failUnlessExists(self.wt.abspath('parent/child'))
1105
self.assertEqual(1, transform.rename_count)
1107
def test_reuse_name(self):
1108
"""Avoid reusing the same limbo name for different files"""
1109
transform, root = self.get_transform()
1110
parent = transform.new_directory('parent', root)
1111
child1 = transform.new_directory('child', parent)
1113
child2 = transform.new_directory('child', parent)
1115
self.fail('Tranform tried to use the same limbo name twice')
1116
transform.adjust_path('child2', parent, child2)
1118
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1119
# child2 is put into top-level limbo because child1 has already
1120
# claimed the direct limbo path when child2 is created. There is no
1121
# advantage in renaming files once they're in top-level limbo, except
1123
self.assertEqual(2, transform.rename_count)
1125
def test_reuse_when_first_moved(self):
1126
"""Don't avoid direct paths when it is safe to use them"""
1127
transform, root = self.get_transform()
1128
parent = transform.new_directory('parent', root)
1129
child1 = transform.new_directory('child', parent)
1130
transform.adjust_path('child1', parent, child1)
1131
child2 = transform.new_directory('child', parent)
1133
# limbo/new-1 => parent
1134
self.assertEqual(1, transform.rename_count)
1136
def test_reuse_after_cancel(self):
1137
"""Don't avoid direct paths when it is safe to use them"""
1138
transform, root = self.get_transform()
1139
parent2 = transform.new_directory('parent2', root)
1140
child1 = transform.new_directory('child1', parent2)
1141
transform.cancel_creation(parent2)
1142
transform.create_directory(parent2)
1143
child2 = transform.new_directory('child1', parent2)
1144
transform.adjust_path('child2', parent2, child1)
1146
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1147
self.assertEqual(2, transform.rename_count)
1149
def test_finalize_order(self):
1150
"""Finalize must be done in child-to-parent order"""
1151
transform, root = self.get_transform()
1152
parent = transform.new_directory('parent', root)
1153
child = transform.new_directory('child', parent)
1155
transform.finalize()
1157
self.fail('Tried to remove parent before child1')
1159
def test_cancel_with_cancelled_child_should_succeed(self):
1160
transform, root = self.get_transform()
1161
parent = transform.new_directory('parent', root)
1162
child = transform.new_directory('child', parent)
1163
transform.cancel_creation(child)
1164
transform.cancel_creation(parent)
1165
transform.finalize()
1167
def test_rollback_on_directory_clash(self):
1169
wt = self.make_branch_and_tree('.')
1170
tt = TreeTransform(wt) # TreeTransform obtains write lock
1172
foo = tt.new_directory('foo', tt.root)
1173
tt.new_file('bar', foo, 'foobar')
1174
baz = tt.new_directory('baz', tt.root)
1175
tt.new_file('qux', baz, 'quux')
1176
# Ask for a rename 'foo' -> 'baz'
1177
tt.adjust_path('baz', tt.root, foo)
1178
# Lie to tt that we've already resolved all conflicts.
1179
tt.apply(no_conflicts=True)
1183
# The rename will fail because the target directory is not empty (but
1184
# raises FileExists anyway).
1185
err = self.assertRaises(errors.FileExists, tt_helper)
1186
self.assertContainsRe(str(err),
1187
"^File exists: .+/baz")
1189
def test_two_directories_clash(self):
1191
wt = self.make_branch_and_tree('.')
1192
tt = TreeTransform(wt) # TreeTransform obtains write lock
1194
foo_1 = tt.new_directory('foo', tt.root)
1195
tt.new_directory('bar', foo_1)
1196
# Adding the same directory with a different content
1197
foo_2 = tt.new_directory('foo', tt.root)
1198
tt.new_directory('baz', foo_2)
1199
# Lie to tt that we've already resolved all conflicts.
1200
tt.apply(no_conflicts=True)
1204
err = self.assertRaises(errors.FileExists, tt_helper)
1205
self.assertContainsRe(str(err),
1206
"^File exists: .+/foo")
1208
def test_two_directories_clash_finalize(self):
1210
wt = self.make_branch_and_tree('.')
1211
tt = TreeTransform(wt) # TreeTransform obtains write lock
1213
foo_1 = tt.new_directory('foo', tt.root)
1214
tt.new_directory('bar', foo_1)
1215
# Adding the same directory with a different content
1216
foo_2 = tt.new_directory('foo', tt.root)
1217
tt.new_directory('baz', foo_2)
1218
# Lie to tt that we've already resolved all conflicts.
1219
tt.apply(no_conflicts=True)
1223
err = self.assertRaises(errors.FileExists, tt_helper)
1224
self.assertContainsRe(str(err),
1225
"^File exists: .+/foo")
1227
def test_file_to_directory(self):
1228
wt = self.make_branch_and_tree('.')
1229
self.build_tree(['foo'])
1232
tt = TreeTransform(wt)
1233
self.addCleanup(tt.finalize)
1234
foo_trans_id = tt.trans_id_tree_path("foo")
1235
tt.delete_contents(foo_trans_id)
1236
tt.create_directory(foo_trans_id)
1237
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1238
tt.create_file(["aa\n"], bar_trans_id)
1239
tt.version_file("bar-1", bar_trans_id)
1241
self.failUnlessExists("foo/bar")
1244
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1249
changes = wt.changes_from(wt.basis_tree())
1250
self.assertFalse(changes.has_changed(), changes)
1252
def test_file_to_symlink(self):
1253
self.requireFeature(SymlinkFeature)
1254
wt = self.make_branch_and_tree('.')
1255
self.build_tree(['foo'])
1258
tt = TreeTransform(wt)
1259
self.addCleanup(tt.finalize)
1260
foo_trans_id = tt.trans_id_tree_path("foo")
1261
tt.delete_contents(foo_trans_id)
1262
tt.create_symlink("bar", foo_trans_id)
1264
self.failUnlessExists("foo")
1266
self.addCleanup(wt.unlock)
1267
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1270
def test_dir_to_file(self):
1271
wt = self.make_branch_and_tree('.')
1272
self.build_tree(['foo/', 'foo/bar'])
1273
wt.add(['foo', 'foo/bar'])
1275
tt = TreeTransform(wt)
1276
self.addCleanup(tt.finalize)
1277
foo_trans_id = tt.trans_id_tree_path("foo")
1278
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1279
tt.delete_contents(foo_trans_id)
1280
tt.delete_versioned(bar_trans_id)
1281
tt.create_file(["aa\n"], foo_trans_id)
1283
self.failUnlessExists("foo")
1285
self.addCleanup(wt.unlock)
1286
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1289
def test_dir_to_hardlink(self):
1290
self.requireFeature(HardlinkFeature)
1291
wt = self.make_branch_and_tree('.')
1292
self.build_tree(['foo/', 'foo/bar'])
1293
wt.add(['foo', 'foo/bar'])
1295
tt = TreeTransform(wt)
1296
self.addCleanup(tt.finalize)
1297
foo_trans_id = tt.trans_id_tree_path("foo")
1298
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1299
tt.delete_contents(foo_trans_id)
1300
tt.delete_versioned(bar_trans_id)
1301
self.build_tree(['baz'])
1302
tt.create_hardlink("baz", foo_trans_id)
1304
self.failUnlessExists("foo")
1305
self.failUnlessExists("baz")
1307
self.addCleanup(wt.unlock)
1308
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1311
def test_no_final_path(self):
1312
transform, root = self.get_transform()
1313
trans_id = transform.trans_id_file_id('foo')
1314
transform.create_file('bar', trans_id)
1315
transform.cancel_creation(trans_id)
1318
def test_create_from_tree(self):
1319
tree1 = self.make_branch_and_tree('tree1')
1320
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1321
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1322
tree2 = self.make_branch_and_tree('tree2')
1323
tt = TreeTransform(tree2)
1324
foo_trans_id = tt.create_path('foo', tt.root)
1325
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1326
bar_trans_id = tt.create_path('bar', tt.root)
1327
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1329
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1330
self.assertFileEqual('baz', 'tree2/bar')
1332
def test_create_from_tree_bytes(self):
1333
"""Provided lines are used instead of tree content."""
1334
tree1 = self.make_branch_and_tree('tree1')
1335
self.build_tree_contents([('tree1/foo', 'bar'),])
1336
tree1.add('foo', 'foo-id')
1337
tree2 = self.make_branch_and_tree('tree2')
1338
tt = TreeTransform(tree2)
1339
foo_trans_id = tt.create_path('foo', tt.root)
1340
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1342
self.assertFileEqual('qux', 'tree2/foo')
1344
def test_create_from_tree_symlink(self):
1345
self.requireFeature(SymlinkFeature)
1346
tree1 = self.make_branch_and_tree('tree1')
1347
os.symlink('bar', 'tree1/foo')
1348
tree1.add('foo', 'foo-id')
1349
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1350
foo_trans_id = tt.create_path('foo', tt.root)
1351
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1353
self.assertEqual('bar', os.readlink('tree2/foo'))
1356
class TransformGroup(object):
1358
def __init__(self, dirname, root_id):
1361
self.wt = BzrDir.create_standalone_workingtree(dirname)
1362
self.wt.set_root_id(root_id)
1363
self.b = self.wt.branch
1364
self.tt = TreeTransform(self.wt)
1365
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1368
def conflict_text(tree, merge):
1369
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1370
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1373
class TestTransformMerge(TestCaseInTempDir):
1375
def test_text_merge(self):
1376
root_id = generate_ids.gen_root_id()
1377
base = TransformGroup("base", root_id)
1378
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1379
base.tt.new_file('b', base.root, 'b1', 'b')
1380
base.tt.new_file('c', base.root, 'c', 'c')
1381
base.tt.new_file('d', base.root, 'd', 'd')
1382
base.tt.new_file('e', base.root, 'e', 'e')
1383
base.tt.new_file('f', base.root, 'f', 'f')
1384
base.tt.new_directory('g', base.root, 'g')
1385
base.tt.new_directory('h', base.root, 'h')
1387
other = TransformGroup("other", root_id)
1388
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1389
other.tt.new_file('b', other.root, 'b2', 'b')
1390
other.tt.new_file('c', other.root, 'c2', 'c')
1391
other.tt.new_file('d', other.root, 'd', 'd')
1392
other.tt.new_file('e', other.root, 'e2', 'e')
1393
other.tt.new_file('f', other.root, 'f', 'f')
1394
other.tt.new_file('g', other.root, 'g', 'g')
1395
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1396
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1398
this = TransformGroup("this", root_id)
1399
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1400
this.tt.new_file('b', this.root, 'b', 'b')
1401
this.tt.new_file('c', this.root, 'c', 'c')
1402
this.tt.new_file('d', this.root, 'd2', 'd')
1403
this.tt.new_file('e', this.root, 'e2', 'e')
1404
this.tt.new_file('f', this.root, 'f', 'f')
1405
this.tt.new_file('g', this.root, 'g', 'g')
1406
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1407
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1409
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1412
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1413
# three-way text conflict
1414
self.assertEqual(this.wt.get_file('b').read(),
1415
conflict_text('b', 'b2'))
1417
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1419
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1420
# Ambigious clean merge
1421
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1423
self.assertEqual(this.wt.get_file('f').read(), 'f')
1424
# Correct correct results when THIS == OTHER
1425
self.assertEqual(this.wt.get_file('g').read(), 'g')
1426
# Text conflict when THIS & OTHER are text and BASE is dir
1427
self.assertEqual(this.wt.get_file('h').read(),
1428
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1429
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1431
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1433
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1434
self.assertEqual(this.wt.get_file('i').read(),
1435
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1436
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1438
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1440
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1441
modified = ['a', 'b', 'c', 'h', 'i']
1442
merge_modified = this.wt.merge_modified()
1443
self.assertSubset(merge_modified, modified)
1444
self.assertEqual(len(merge_modified), len(modified))
1445
file(this.wt.id2abspath('a'), 'wb').write('booga')
1447
merge_modified = this.wt.merge_modified()
1448
self.assertSubset(merge_modified, modified)
1449
self.assertEqual(len(merge_modified), len(modified))
1453
def test_file_merge(self):
1454
self.requireFeature(SymlinkFeature)
1455
root_id = generate_ids.gen_root_id()
1456
base = TransformGroup("BASE", root_id)
1457
this = TransformGroup("THIS", root_id)
1458
other = TransformGroup("OTHER", root_id)
1459
for tg in this, base, other:
1460
tg.tt.new_directory('a', tg.root, 'a')
1461
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1462
tg.tt.new_file('c', tg.root, 'c', 'c')
1463
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1464
targets = ((base, 'base-e', 'base-f', None, None),
1465
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1466
(other, 'other-e', None, 'other-g', 'other-h'))
1467
for tg, e_target, f_target, g_target, h_target in targets:
1468
for link, target in (('e', e_target), ('f', f_target),
1469
('g', g_target), ('h', h_target)):
1470
if target is not None:
1471
tg.tt.new_symlink(link, tg.root, target, link)
1473
for tg in this, base, other:
1475
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1476
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1477
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1478
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1479
for suffix in ('THIS', 'BASE', 'OTHER'):
1480
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1481
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1482
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1483
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1484
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1485
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1486
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1487
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1488
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1489
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1490
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1491
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1492
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1493
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1495
def test_filename_merge(self):
1496
root_id = generate_ids.gen_root_id()
1497
base = TransformGroup("BASE", root_id)
1498
this = TransformGroup("THIS", root_id)
1499
other = TransformGroup("OTHER", root_id)
1500
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1501
for t in [base, this, other]]
1502
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1503
for t in [base, this, other]]
1504
base.tt.new_directory('c', base_a, 'c')
1505
this.tt.new_directory('c1', this_a, 'c')
1506
other.tt.new_directory('c', other_b, 'c')
1508
base.tt.new_directory('d', base_a, 'd')
1509
this.tt.new_directory('d1', this_b, 'd')
1510
other.tt.new_directory('d', other_a, 'd')
1512
base.tt.new_directory('e', base_a, 'e')
1513
this.tt.new_directory('e', this_a, 'e')
1514
other.tt.new_directory('e1', other_b, 'e')
1516
base.tt.new_directory('f', base_a, 'f')
1517
this.tt.new_directory('f1', this_b, 'f')
1518
other.tt.new_directory('f1', other_b, 'f')
1520
for tg in [this, base, other]:
1522
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1523
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1524
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1525
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1526
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1528
def test_filename_merge_conflicts(self):
1529
root_id = generate_ids.gen_root_id()
1530
base = TransformGroup("BASE", root_id)
1531
this = TransformGroup("THIS", root_id)
1532
other = TransformGroup("OTHER", root_id)
1533
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1534
for t in [base, this, other]]
1535
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1536
for t in [base, this, other]]
1538
base.tt.new_file('g', base_a, 'g', 'g')
1539
other.tt.new_file('g1', other_b, 'g1', 'g')
1541
base.tt.new_file('h', base_a, 'h', 'h')
1542
this.tt.new_file('h1', this_b, 'h1', 'h')
1544
base.tt.new_file('i', base.root, 'i', 'i')
1545
other.tt.new_directory('i1', this_b, 'i')
1547
for tg in [this, base, other]:
1549
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1551
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1552
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1553
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1554
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1555
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1556
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1557
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1560
class TestBuildTree(tests.TestCaseWithTransport):
1562
def test_build_tree_with_symlinks(self):
1563
self.requireFeature(SymlinkFeature)
1565
a = BzrDir.create_standalone_workingtree('a')
1567
file('a/foo/bar', 'wb').write('contents')
1568
os.symlink('a/foo/bar', 'a/foo/baz')
1569
a.add(['foo', 'foo/bar', 'foo/baz'])
1570
a.commit('initial commit')
1571
b = BzrDir.create_standalone_workingtree('b')
1572
basis = a.basis_tree()
1574
self.addCleanup(basis.unlock)
1575
build_tree(basis, b)
1576
self.assertIs(os.path.isdir('b/foo'), True)
1577
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1578
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1580
def test_build_with_references(self):
1581
tree = self.make_branch_and_tree('source',
1582
format='dirstate-with-subtree')
1583
subtree = self.make_branch_and_tree('source/subtree',
1584
format='dirstate-with-subtree')
1585
tree.add_reference(subtree)
1586
tree.commit('a revision')
1587
tree.branch.create_checkout('target')
1588
self.failUnlessExists('target')
1589
self.failUnlessExists('target/subtree')
1591
def test_file_conflict_handling(self):
1592
"""Ensure that when building trees, conflict handling is done"""
1593
source = self.make_branch_and_tree('source')
1594
target = self.make_branch_and_tree('target')
1595
self.build_tree(['source/file', 'target/file'])
1596
source.add('file', 'new-file')
1597
source.commit('added file')
1598
build_tree(source.basis_tree(), target)
1599
self.assertEqual([DuplicateEntry('Moved existing file to',
1600
'file.moved', 'file', None, 'new-file')],
1602
target2 = self.make_branch_and_tree('target2')
1603
target_file = file('target2/file', 'wb')
1605
source_file = file('source/file', 'rb')
1607
target_file.write(source_file.read())
1612
build_tree(source.basis_tree(), target2)
1613
self.assertEqual([], target2.conflicts())
1615
def test_symlink_conflict_handling(self):
1616
"""Ensure that when building trees, conflict handling is done"""
1617
self.requireFeature(SymlinkFeature)
1618
source = self.make_branch_and_tree('source')
1619
os.symlink('foo', 'source/symlink')
1620
source.add('symlink', 'new-symlink')
1621
source.commit('added file')
1622
target = self.make_branch_and_tree('target')
1623
os.symlink('bar', 'target/symlink')
1624
build_tree(source.basis_tree(), target)
1625
self.assertEqual([DuplicateEntry('Moved existing file to',
1626
'symlink.moved', 'symlink', None, 'new-symlink')],
1628
target = self.make_branch_and_tree('target2')
1629
os.symlink('foo', 'target2/symlink')
1630
build_tree(source.basis_tree(), target)
1631
self.assertEqual([], target.conflicts())
1633
def test_directory_conflict_handling(self):
1634
"""Ensure that when building trees, conflict handling is done"""
1635
source = self.make_branch_and_tree('source')
1636
target = self.make_branch_and_tree('target')
1637
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1638
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1639
source.commit('added file')
1640
build_tree(source.basis_tree(), target)
1641
self.assertEqual([], target.conflicts())
1642
self.failUnlessExists('target/dir1/file')
1644
# Ensure contents are merged
1645
target = self.make_branch_and_tree('target2')
1646
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1647
build_tree(source.basis_tree(), target)
1648
self.assertEqual([], target.conflicts())
1649
self.failUnlessExists('target2/dir1/file2')
1650
self.failUnlessExists('target2/dir1/file')
1652
# Ensure new contents are suppressed for existing branches
1653
target = self.make_branch_and_tree('target3')
1654
self.make_branch('target3/dir1')
1655
self.build_tree(['target3/dir1/file2'])
1656
build_tree(source.basis_tree(), target)
1657
self.failIfExists('target3/dir1/file')
1658
self.failUnlessExists('target3/dir1/file2')
1659
self.failUnlessExists('target3/dir1.diverted/file')
1660
self.assertEqual([DuplicateEntry('Diverted to',
1661
'dir1.diverted', 'dir1', 'new-dir1', None)],
1664
target = self.make_branch_and_tree('target4')
1665
self.build_tree(['target4/dir1/'])
1666
self.make_branch('target4/dir1/file')
1667
build_tree(source.basis_tree(), target)
1668
self.failUnlessExists('target4/dir1/file')
1669
self.assertEqual('directory', file_kind('target4/dir1/file'))
1670
self.failUnlessExists('target4/dir1/file.diverted')
1671
self.assertEqual([DuplicateEntry('Diverted to',
1672
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1675
def test_mixed_conflict_handling(self):
1676
"""Ensure that when building trees, conflict handling is done"""
1677
source = self.make_branch_and_tree('source')
1678
target = self.make_branch_and_tree('target')
1679
self.build_tree(['source/name', 'target/name/'])
1680
source.add('name', 'new-name')
1681
source.commit('added file')
1682
build_tree(source.basis_tree(), target)
1683
self.assertEqual([DuplicateEntry('Moved existing file to',
1684
'name.moved', 'name', None, 'new-name')], target.conflicts())
1686
def test_raises_in_populated(self):
1687
source = self.make_branch_and_tree('source')
1688
self.build_tree(['source/name'])
1690
source.commit('added name')
1691
target = self.make_branch_and_tree('target')
1692
self.build_tree(['target/name'])
1694
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1695
build_tree, source.basis_tree(), target)
1697
def test_build_tree_rename_count(self):
1698
source = self.make_branch_and_tree('source')
1699
self.build_tree(['source/file1', 'source/dir1/'])
1700
source.add(['file1', 'dir1'])
1701
source.commit('add1')
1702
target1 = self.make_branch_and_tree('target1')
1703
transform_result = build_tree(source.basis_tree(), target1)
1704
self.assertEqual(2, transform_result.rename_count)
1706
self.build_tree(['source/dir1/file2'])
1707
source.add(['dir1/file2'])
1708
source.commit('add3')
1709
target2 = self.make_branch_and_tree('target2')
1710
transform_result = build_tree(source.basis_tree(), target2)
1711
# children of non-root directories should not be renamed
1712
self.assertEqual(2, transform_result.rename_count)
1714
def create_ab_tree(self):
1715
"""Create a committed test tree with two files"""
1716
source = self.make_branch_and_tree('source')
1717
self.build_tree_contents([('source/file1', 'A')])
1718
self.build_tree_contents([('source/file2', 'B')])
1719
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1720
source.commit('commit files')
1722
self.addCleanup(source.unlock)
1725
def test_build_tree_accelerator_tree(self):
1726
source = self.create_ab_tree()
1727
self.build_tree_contents([('source/file2', 'C')])
1729
real_source_get_file = source.get_file
1730
def get_file(file_id, path=None):
1731
calls.append(file_id)
1732
return real_source_get_file(file_id, path)
1733
source.get_file = get_file
1734
target = self.make_branch_and_tree('target')
1735
revision_tree = source.basis_tree()
1736
revision_tree.lock_read()
1737
self.addCleanup(revision_tree.unlock)
1738
build_tree(revision_tree, target, source)
1739
self.assertEqual(['file1-id'], calls)
1741
self.addCleanup(target.unlock)
1742
self.assertEqual([], list(target.iter_changes(revision_tree)))
1744
def test_build_tree_accelerator_tree_missing_file(self):
1745
source = self.create_ab_tree()
1746
os.unlink('source/file1')
1747
source.remove(['file2'])
1748
target = self.make_branch_and_tree('target')
1749
revision_tree = source.basis_tree()
1750
revision_tree.lock_read()
1751
self.addCleanup(revision_tree.unlock)
1752
build_tree(revision_tree, target, source)
1754
self.addCleanup(target.unlock)
1755
self.assertEqual([], list(target.iter_changes(revision_tree)))
1757
def test_build_tree_accelerator_wrong_kind(self):
1758
self.requireFeature(SymlinkFeature)
1759
source = self.make_branch_and_tree('source')
1760
self.build_tree_contents([('source/file1', '')])
1761
self.build_tree_contents([('source/file2', '')])
1762
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1763
source.commit('commit files')
1764
os.unlink('source/file2')
1765
self.build_tree_contents([('source/file2/', 'C')])
1766
os.unlink('source/file1')
1767
os.symlink('file2', 'source/file1')
1769
real_source_get_file = source.get_file
1770
def get_file(file_id, path=None):
1771
calls.append(file_id)
1772
return real_source_get_file(file_id, path)
1773
source.get_file = get_file
1774
target = self.make_branch_and_tree('target')
1775
revision_tree = source.basis_tree()
1776
revision_tree.lock_read()
1777
self.addCleanup(revision_tree.unlock)
1778
build_tree(revision_tree, target, source)
1779
self.assertEqual([], calls)
1781
self.addCleanup(target.unlock)
1782
self.assertEqual([], list(target.iter_changes(revision_tree)))
1784
def test_build_tree_hardlink(self):
1785
self.requireFeature(HardlinkFeature)
1786
source = self.create_ab_tree()
1787
target = self.make_branch_and_tree('target')
1788
revision_tree = source.basis_tree()
1789
revision_tree.lock_read()
1790
self.addCleanup(revision_tree.unlock)
1791
build_tree(revision_tree, target, source, hardlink=True)
1793
self.addCleanup(target.unlock)
1794
self.assertEqual([], list(target.iter_changes(revision_tree)))
1795
source_stat = os.stat('source/file1')
1796
target_stat = os.stat('target/file1')
1797
self.assertEqual(source_stat, target_stat)
1799
# Explicitly disallowing hardlinks should prevent them.
1800
target2 = self.make_branch_and_tree('target2')
1801
build_tree(revision_tree, target2, source, hardlink=False)
1803
self.addCleanup(target2.unlock)
1804
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1805
source_stat = os.stat('source/file1')
1806
target2_stat = os.stat('target2/file1')
1807
self.assertNotEqual(source_stat, target2_stat)
1809
def test_build_tree_accelerator_tree_moved(self):
1810
source = self.make_branch_and_tree('source')
1811
self.build_tree_contents([('source/file1', 'A')])
1812
source.add(['file1'], ['file1-id'])
1813
source.commit('commit files')
1814
source.rename_one('file1', 'file2')
1816
self.addCleanup(source.unlock)
1817
target = self.make_branch_and_tree('target')
1818
revision_tree = source.basis_tree()
1819
revision_tree.lock_read()
1820
self.addCleanup(revision_tree.unlock)
1821
build_tree(revision_tree, target, source)
1823
self.addCleanup(target.unlock)
1824
self.assertEqual([], list(target.iter_changes(revision_tree)))
1826
def test_build_tree_hardlinks_preserve_execute(self):
1827
self.requireFeature(HardlinkFeature)
1828
source = self.create_ab_tree()
1829
tt = TreeTransform(source)
1830
trans_id = tt.trans_id_tree_file_id('file1-id')
1831
tt.set_executability(True, trans_id)
1833
self.assertTrue(source.is_executable('file1-id'))
1834
target = self.make_branch_and_tree('target')
1835
revision_tree = source.basis_tree()
1836
revision_tree.lock_read()
1837
self.addCleanup(revision_tree.unlock)
1838
build_tree(revision_tree, target, source, hardlink=True)
1840
self.addCleanup(target.unlock)
1841
self.assertEqual([], list(target.iter_changes(revision_tree)))
1842
self.assertTrue(source.is_executable('file1-id'))
1844
def test_case_insensitive_build_tree_inventory(self):
1845
source = self.make_branch_and_tree('source')
1846
self.build_tree(['source/file', 'source/FILE'])
1847
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1848
source.commit('added files')
1849
# Don't try this at home, kids!
1850
# Force the tree to report that it is case insensitive
1851
target = self.make_branch_and_tree('target')
1852
target.case_sensitive = False
1853
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1854
self.assertEqual('file.moved', target.id2path('lower-id'))
1855
self.assertEqual('FILE', target.id2path('upper-id'))
1858
class MockTransform(object):
1860
def has_named_child(self, by_parent, parent_id, name):
1861
for child_id in by_parent[parent_id]:
1865
elif name == "name.~%s~" % child_id:
1870
class MockEntry(object):
1872
object.__init__(self)
1876
class TestGetBackupName(TestCase):
1877
def test_get_backup_name(self):
1878
tt = MockTransform()
1879
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1880
self.assertEqual(name, 'name.~1~')
1881
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1882
self.assertEqual(name, 'name.~2~')
1883
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1884
self.assertEqual(name, 'name.~1~')
1885
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1886
self.assertEqual(name, 'name.~1~')
1887
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1888
self.assertEqual(name, 'name.~4~')
1891
class TestFileMover(tests.TestCaseWithTransport):
1893
def test_file_mover(self):
1894
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1895
mover = _FileMover()
1896
mover.rename('a', 'q')
1897
self.failUnlessExists('q')
1898
self.failIfExists('a')
1899
self.failUnlessExists('q/b')
1900
self.failUnlessExists('c')
1901
self.failUnlessExists('c/d')
1903
def test_pre_delete_rollback(self):
1904
self.build_tree(['a/'])
1905
mover = _FileMover()
1906
mover.pre_delete('a', 'q')
1907
self.failUnlessExists('q')
1908
self.failIfExists('a')
1910
self.failIfExists('q')
1911
self.failUnlessExists('a')
1913
def test_apply_deletions(self):
1914
self.build_tree(['a/', 'b/'])
1915
mover = _FileMover()
1916
mover.pre_delete('a', 'q')
1917
mover.pre_delete('b', 'r')
1918
self.failUnlessExists('q')
1919
self.failUnlessExists('r')
1920
self.failIfExists('a')
1921
self.failIfExists('b')
1922
mover.apply_deletions()
1923
self.failIfExists('q')
1924
self.failIfExists('r')
1925
self.failIfExists('a')
1926
self.failIfExists('b')
1928
def test_file_mover_rollback(self):
1929
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1930
mover = _FileMover()
1931
mover.rename('c/d', 'c/f')
1932
mover.rename('c/e', 'c/d')
1934
mover.rename('a', 'c')
1935
except errors.FileExists, e:
1937
self.failUnlessExists('a')
1938
self.failUnlessExists('c/d')
1941
class Bogus(Exception):
1945
class TestTransformRollback(tests.TestCaseWithTransport):
1947
class ExceptionFileMover(_FileMover):
1949
def __init__(self, bad_source=None, bad_target=None):
1950
_FileMover.__init__(self)
1951
self.bad_source = bad_source
1952
self.bad_target = bad_target
1954
def rename(self, source, target):
1955
if (self.bad_source is not None and
1956
source.endswith(self.bad_source)):
1958
elif (self.bad_target is not None and
1959
target.endswith(self.bad_target)):
1962
_FileMover.rename(self, source, target)
1964
def test_rollback_rename(self):
1965
tree = self.make_branch_and_tree('.')
1966
self.build_tree(['a/', 'a/b'])
1967
tt = TreeTransform(tree)
1968
self.addCleanup(tt.finalize)
1969
a_id = tt.trans_id_tree_path('a')
1970
tt.adjust_path('c', tt.root, a_id)
1971
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1972
self.assertRaises(Bogus, tt.apply,
1973
_mover=self.ExceptionFileMover(bad_source='a'))
1974
self.failUnlessExists('a')
1975
self.failUnlessExists('a/b')
1977
self.failUnlessExists('c')
1978
self.failUnlessExists('c/d')
1980
def test_rollback_rename_into_place(self):
1981
tree = self.make_branch_and_tree('.')
1982
self.build_tree(['a/', 'a/b'])
1983
tt = TreeTransform(tree)
1984
self.addCleanup(tt.finalize)
1985
a_id = tt.trans_id_tree_path('a')
1986
tt.adjust_path('c', tt.root, a_id)
1987
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1988
self.assertRaises(Bogus, tt.apply,
1989
_mover=self.ExceptionFileMover(bad_target='c/d'))
1990
self.failUnlessExists('a')
1991
self.failUnlessExists('a/b')
1993
self.failUnlessExists('c')
1994
self.failUnlessExists('c/d')
1996
def test_rollback_deletion(self):
1997
tree = self.make_branch_and_tree('.')
1998
self.build_tree(['a/', 'a/b'])
1999
tt = TreeTransform(tree)
2000
self.addCleanup(tt.finalize)
2001
a_id = tt.trans_id_tree_path('a')
2002
tt.delete_contents(a_id)
2003
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2004
self.assertRaises(Bogus, tt.apply,
2005
_mover=self.ExceptionFileMover(bad_target='d'))
2006
self.failUnlessExists('a')
2007
self.failUnlessExists('a/b')
2009
def test_resolve_no_parent(self):
2010
wt = self.make_branch_and_tree('.')
2011
tt = TreeTransform(wt)
2012
self.addCleanup(tt.finalize)
2013
parent = tt.trans_id_file_id('parent-id')
2014
tt.new_file('file', parent, 'Contents')
2015
resolve_conflicts(tt)
2018
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2019
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2021
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2022
('', ''), ('directory', 'directory'), (False, None))
2025
class TestTransformPreview(tests.TestCaseWithTransport):
2027
def create_tree(self):
2028
tree = self.make_branch_and_tree('.')
2029
self.build_tree_contents([('a', 'content 1')])
2030
tree.add('a', 'a-id')
2031
tree.commit('rev1', rev_id='rev1')
2032
return tree.branch.repository.revision_tree('rev1')
2034
def get_empty_preview(self):
2035
repository = self.make_repository('repo')
2036
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2037
preview = TransformPreview(tree)
2038
self.addCleanup(preview.finalize)
2041
def test_transform_preview(self):
2042
revision_tree = self.create_tree()
2043
preview = TransformPreview(revision_tree)
2044
self.addCleanup(preview.finalize)
2046
def test_transform_preview_tree(self):
2047
revision_tree = self.create_tree()
2048
preview = TransformPreview(revision_tree)
2049
self.addCleanup(preview.finalize)
2050
preview.get_preview_tree()
2052
def test_transform_new_file(self):
2053
revision_tree = self.create_tree()
2054
preview = TransformPreview(revision_tree)
2055
self.addCleanup(preview.finalize)
2056
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2057
preview_tree = preview.get_preview_tree()
2058
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2060
preview_tree.get_file('file2-id').read(), 'content B\n')
2062
def test_diff_preview_tree(self):
2063
revision_tree = self.create_tree()
2064
preview = TransformPreview(revision_tree)
2065
self.addCleanup(preview.finalize)
2066
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2067
preview_tree = preview.get_preview_tree()
2069
show_diff_trees(revision_tree, preview_tree, out)
2070
lines = out.getvalue().splitlines()
2071
self.assertEqual(lines[0], "=== added file 'file2'")
2072
# 3 lines of diff administrivia
2073
self.assertEqual(lines[4], "+content B")
2075
def test_transform_conflicts(self):
2076
revision_tree = self.create_tree()
2077
preview = TransformPreview(revision_tree)
2078
self.addCleanup(preview.finalize)
2079
preview.new_file('a', preview.root, 'content 2')
2080
resolve_conflicts(preview)
2081
trans_id = preview.trans_id_file_id('a-id')
2082
self.assertEqual('a.moved', preview.final_name(trans_id))
2084
def get_tree_and_preview_tree(self):
2085
revision_tree = self.create_tree()
2086
preview = TransformPreview(revision_tree)
2087
self.addCleanup(preview.finalize)
2088
a_trans_id = preview.trans_id_file_id('a-id')
2089
preview.delete_contents(a_trans_id)
2090
preview.create_file('b content', a_trans_id)
2091
preview_tree = preview.get_preview_tree()
2092
return revision_tree, preview_tree
2094
def test_iter_changes(self):
2095
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2096
root = revision_tree.inventory.root.file_id
2097
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2098
(root, root), ('a', 'a'), ('file', 'file'),
2100
list(preview_tree.iter_changes(revision_tree)))
2102
def test_include_unchanged_succeeds(self):
2103
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2104
changes = preview_tree.iter_changes(revision_tree,
2105
include_unchanged=True)
2106
root = revision_tree.inventory.root.file_id
2108
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2110
def test_specific_files(self):
2111
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2112
changes = preview_tree.iter_changes(revision_tree,
2113
specific_files=[''])
2114
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2116
def test_want_unversioned(self):
2117
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2118
changes = preview_tree.iter_changes(revision_tree,
2119
want_unversioned=True)
2120
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2122
def test_ignore_extra_trees_no_specific_files(self):
2123
# extra_trees is harmless without specific_files, so we'll silently
2124
# accept it, even though we won't use it.
2125
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2126
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2128
def test_ignore_require_versioned_no_specific_files(self):
2129
# require_versioned is meaningless without specific_files.
2130
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2131
preview_tree.iter_changes(revision_tree, require_versioned=False)
2133
def test_ignore_pb(self):
2134
# pb could be supported, but TT.iter_changes doesn't support it.
2135
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2136
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2138
def test_kind(self):
2139
revision_tree = self.create_tree()
2140
preview = TransformPreview(revision_tree)
2141
self.addCleanup(preview.finalize)
2142
preview.new_file('file', preview.root, 'contents', 'file-id')
2143
preview.new_directory('directory', preview.root, 'dir-id')
2144
preview_tree = preview.get_preview_tree()
2145
self.assertEqual('file', preview_tree.kind('file-id'))
2146
self.assertEqual('directory', preview_tree.kind('dir-id'))
2148
def test_get_file_mtime(self):
2149
preview = self.get_empty_preview()
2150
file_trans_id = preview.new_file('file', preview.root, 'contents',
2152
limbo_path = preview._limbo_name(file_trans_id)
2153
preview_tree = preview.get_preview_tree()
2154
self.assertEqual(os.stat(limbo_path).st_mtime,
2155
preview_tree.get_file_mtime('file-id'))
2157
def test_get_file(self):
2158
preview = self.get_empty_preview()
2159
preview.new_file('file', preview.root, 'contents', 'file-id')
2160
preview_tree = preview.get_preview_tree()
2161
tree_file = preview_tree.get_file('file-id')
2163
self.assertEqual('contents', tree_file.read())
2167
def test_get_symlink_target(self):
2168
self.requireFeature(SymlinkFeature)
2169
preview = self.get_empty_preview()
2170
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2171
preview_tree = preview.get_preview_tree()
2172
self.assertEqual('target',
2173
preview_tree.get_symlink_target('symlink-id'))
2175
def test_all_file_ids(self):
2176
tree = self.make_branch_and_tree('tree')
2177
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2178
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2179
preview = TransformPreview(tree)
2180
self.addCleanup(preview.finalize)
2181
preview.unversion_file(preview.trans_id_file_id('b-id'))
2182
c_trans_id = preview.trans_id_file_id('c-id')
2183
preview.unversion_file(c_trans_id)
2184
preview.version_file('c-id', c_trans_id)
2185
preview_tree = preview.get_preview_tree()
2186
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2187
preview_tree.all_file_ids())
2189
def test_path2id_deleted_unchanged(self):
2190
tree = self.make_branch_and_tree('tree')
2191
self.build_tree(['tree/unchanged', 'tree/deleted'])
2192
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2193
preview = TransformPreview(tree)
2194
self.addCleanup(preview.finalize)
2195
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2196
preview_tree = preview.get_preview_tree()
2197
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2198
self.assertIs(None, preview_tree.path2id('deleted'))
2200
def test_path2id_created(self):
2201
tree = self.make_branch_and_tree('tree')
2202
self.build_tree(['tree/unchanged'])
2203
tree.add(['unchanged'], ['unchanged-id'])
2204
preview = TransformPreview(tree)
2205
self.addCleanup(preview.finalize)
2206
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2207
'contents', 'new-id')
2208
preview_tree = preview.get_preview_tree()
2209
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2211
def test_path2id_moved(self):
2212
tree = self.make_branch_and_tree('tree')
2213
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2214
tree.add(['old_parent', 'old_parent/child'],
2215
['old_parent-id', 'child-id'])
2216
preview = TransformPreview(tree)
2217
self.addCleanup(preview.finalize)
2218
new_parent = preview.new_directory('new_parent', preview.root,
2220
preview.adjust_path('child', new_parent,
2221
preview.trans_id_file_id('child-id'))
2222
preview_tree = preview.get_preview_tree()
2223
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2224
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2226
def test_path2id_renamed_parent(self):
2227
tree = self.make_branch_and_tree('tree')
2228
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2229
tree.add(['old_name', 'old_name/child'],
2230
['parent-id', 'child-id'])
2231
preview = TransformPreview(tree)
2232
self.addCleanup(preview.finalize)
2233
preview.adjust_path('new_name', preview.root,
2234
preview.trans_id_file_id('parent-id'))
2235
preview_tree = preview.get_preview_tree()
2236
self.assertIs(None, preview_tree.path2id('old_name/child'))
2237
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2239
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2240
preview_tree = tt.get_preview_tree()
2241
preview_result = list(preview_tree.iter_entries_by_dir(
2245
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2246
self.assertEqual(actual_result, preview_result)
2248
def test_iter_entries_by_dir_new(self):
2249
tree = self.make_branch_and_tree('tree')
2250
tt = TreeTransform(tree)
2251
tt.new_file('new', tt.root, 'contents', 'new-id')
2252
self.assertMatchingIterEntries(tt)
2254
def test_iter_entries_by_dir_deleted(self):
2255
tree = self.make_branch_and_tree('tree')
2256
self.build_tree(['tree/deleted'])
2257
tree.add('deleted', 'deleted-id')
2258
tt = TreeTransform(tree)
2259
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2260
self.assertMatchingIterEntries(tt)
2262
def test_iter_entries_by_dir_unversioned(self):
2263
tree = self.make_branch_and_tree('tree')
2264
self.build_tree(['tree/removed'])
2265
tree.add('removed', 'removed-id')
2266
tt = TreeTransform(tree)
2267
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2268
self.assertMatchingIterEntries(tt)
2270
def test_iter_entries_by_dir_moved(self):
2271
tree = self.make_branch_and_tree('tree')
2272
self.build_tree(['tree/moved', 'tree/new_parent/'])
2273
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2274
tt = TreeTransform(tree)
2275
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2276
tt.trans_id_file_id('moved-id'))
2277
self.assertMatchingIterEntries(tt)
2279
def test_iter_entries_by_dir_specific_file_ids(self):
2280
tree = self.make_branch_and_tree('tree')
2281
tree.set_root_id('tree-root-id')
2282
self.build_tree(['tree/parent/', 'tree/parent/child'])
2283
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2284
tt = TreeTransform(tree)
2285
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2287
def test_symlink_content_summary(self):
2288
self.requireFeature(SymlinkFeature)
2289
preview = self.get_empty_preview()
2290
preview.new_symlink('path', preview.root, 'target', 'path-id')
2291
summary = preview.get_preview_tree().path_content_summary('path')
2292
self.assertEqual(('symlink', None, None, 'target'), summary)
2294
def test_missing_content_summary(self):
2295
preview = self.get_empty_preview()
2296
summary = preview.get_preview_tree().path_content_summary('path')
2297
self.assertEqual(('missing', None, None, None), summary)
2299
def test_deleted_content_summary(self):
2300
tree = self.make_branch_and_tree('tree')
2301
self.build_tree(['tree/path/'])
2303
preview = TransformPreview(tree)
2304
self.addCleanup(preview.finalize)
2305
preview.delete_contents(preview.trans_id_tree_path('path'))
2306
summary = preview.get_preview_tree().path_content_summary('path')
2307
self.assertEqual(('missing', None, None, None), summary)
2309
def test_file_content_summary_executable(self):
2310
if not osutils.supports_executable():
2311
raise TestNotApplicable()
2312
preview = self.get_empty_preview()
2313
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2314
preview.set_executability(True, path_id)
2315
summary = preview.get_preview_tree().path_content_summary('path')
2316
self.assertEqual(4, len(summary))
2317
self.assertEqual('file', summary[0])
2318
# size must be known
2319
self.assertEqual(len('contents'), summary[1])
2321
self.assertEqual(True, summary[2])
2322
# will not have hash (not cheap to determine)
2323
self.assertIs(None, summary[3])
2325
def test_change_executability(self):
2326
if not osutils.supports_executable():
2327
raise TestNotApplicable()
2328
tree = self.make_branch_and_tree('tree')
2329
self.build_tree(['tree/path'])
2331
preview = TransformPreview(tree)
2332
self.addCleanup(preview.finalize)
2333
path_id = preview.trans_id_tree_path('path')
2334
preview.set_executability(True, path_id)
2335
summary = preview.get_preview_tree().path_content_summary('path')
2336
self.assertEqual(True, summary[2])
2338
def test_file_content_summary_non_exec(self):
2339
preview = self.get_empty_preview()
2340
preview.new_file('path', preview.root, 'contents', 'path-id')
2341
summary = preview.get_preview_tree().path_content_summary('path')
2342
self.assertEqual(4, len(summary))
2343
self.assertEqual('file', summary[0])
2344
# size must be known
2345
self.assertEqual(len('contents'), summary[1])
2347
if osutils.supports_executable():
2348
self.assertEqual(False, summary[2])
2350
self.assertEqual(None, summary[2])
2351
# will not have hash (not cheap to determine)
2352
self.assertIs(None, summary[3])
2354
def test_dir_content_summary(self):
2355
preview = self.get_empty_preview()
2356
preview.new_directory('path', preview.root, 'path-id')
2357
summary = preview.get_preview_tree().path_content_summary('path')
2358
self.assertEqual(('directory', None, None, None), summary)
2360
def test_tree_content_summary(self):
2361
preview = self.get_empty_preview()
2362
path = preview.new_directory('path', preview.root, 'path-id')
2363
preview.set_tree_reference('rev-1', path)
2364
summary = preview.get_preview_tree().path_content_summary('path')
2365
self.assertEqual(4, len(summary))
2366
self.assertEqual('tree-reference', summary[0])
2368
def test_annotate(self):
2369
tree = self.make_branch_and_tree('tree')
2370
self.build_tree_contents([('tree/file', 'a\n')])
2371
tree.add('file', 'file-id')
2372
tree.commit('a', rev_id='one')
2373
self.build_tree_contents([('tree/file', 'a\nb\n')])
2374
preview = TransformPreview(tree)
2375
self.addCleanup(preview.finalize)
2376
file_trans_id = preview.trans_id_file_id('file-id')
2377
preview.delete_contents(file_trans_id)
2378
preview.create_file('a\nb\nc\n', file_trans_id)
2379
preview_tree = preview.get_preview_tree()
2385
annotation = preview_tree.annotate_iter('file-id', 'me:')
2386
self.assertEqual(expected, annotation)
2388
def test_annotate_missing(self):
2389
preview = self.get_empty_preview()
2390
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2391
preview_tree = preview.get_preview_tree()
2397
annotation = preview_tree.annotate_iter('file-id', 'me:')
2398
self.assertEqual(expected, annotation)
2400
def test_annotate_rename(self):
2401
tree = self.make_branch_and_tree('tree')
2402
self.build_tree_contents([('tree/file', 'a\n')])
2403
tree.add('file', 'file-id')
2404
tree.commit('a', rev_id='one')
2405
preview = TransformPreview(tree)
2406
self.addCleanup(preview.finalize)
2407
file_trans_id = preview.trans_id_file_id('file-id')
2408
preview.adjust_path('newname', preview.root, file_trans_id)
2409
preview_tree = preview.get_preview_tree()
2413
annotation = preview_tree.annotate_iter('file-id', 'me:')
2414
self.assertEqual(expected, annotation)
2416
def test_annotate_deleted(self):
2417
tree = self.make_branch_and_tree('tree')
2418
self.build_tree_contents([('tree/file', 'a\n')])
2419
tree.add('file', 'file-id')
2420
tree.commit('a', rev_id='one')
2421
self.build_tree_contents([('tree/file', 'a\nb\n')])
2422
preview = TransformPreview(tree)
2423
self.addCleanup(preview.finalize)
2424
file_trans_id = preview.trans_id_file_id('file-id')
2425
preview.delete_contents(file_trans_id)
2426
preview_tree = preview.get_preview_tree()
2427
annotation = preview_tree.annotate_iter('file-id', 'me:')
2428
self.assertIs(None, annotation)
2430
def test_stored_kind(self):
2431
preview = self.get_empty_preview()
2432
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2433
preview_tree = preview.get_preview_tree()
2434
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2436
def test_is_executable(self):
2437
preview = self.get_empty_preview()
2438
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2439
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2440
preview_tree = preview.get_preview_tree()
2441
self.assertEqual(True, preview_tree.is_executable('file-id'))
2443
def test_get_set_parent_ids(self):
2444
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2445
self.assertEqual([], preview_tree.get_parent_ids())
2446
preview_tree.set_parent_ids(['rev-1'])
2447
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2449
def test_plan_file_merge(self):
2450
work_a = self.make_branch_and_tree('wta')
2451
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2452
work_a.add('file', 'file-id')
2453
base_id = work_a.commit('base version')
2454
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2455
preview = TransformPreview(work_a)
2456
self.addCleanup(preview.finalize)
2457
trans_id = preview.trans_id_file_id('file-id')
2458
preview.delete_contents(trans_id)
2459
preview.create_file('b\nc\nd\ne\n', trans_id)
2460
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2461
tree_a = preview.get_preview_tree()
2462
tree_a.set_parent_ids([base_id])
2464
('killed-a', 'a\n'),
2465
('killed-b', 'b\n'),
2466
('unchanged', 'c\n'),
2467
('unchanged', 'd\n'),
2470
], list(tree_a.plan_file_merge('file-id', tree_b)))
2472
def test_plan_file_merge_revision_tree(self):
2473
work_a = self.make_branch_and_tree('wta')
2474
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2475
work_a.add('file', 'file-id')
2476
base_id = work_a.commit('base version')
2477
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2478
preview = TransformPreview(work_a.basis_tree())
2479
self.addCleanup(preview.finalize)
2480
trans_id = preview.trans_id_file_id('file-id')
2481
preview.delete_contents(trans_id)
2482
preview.create_file('b\nc\nd\ne\n', trans_id)
2483
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2484
tree_a = preview.get_preview_tree()
2485
tree_a.set_parent_ids([base_id])
2487
('killed-a', 'a\n'),
2488
('killed-b', 'b\n'),
2489
('unchanged', 'c\n'),
2490
('unchanged', 'd\n'),
2493
], list(tree_a.plan_file_merge('file-id', tree_b)))
2495
def test_walkdirs(self):
2496
preview = self.get_empty_preview()
2497
preview.version_file('tree-root', preview.root)
2498
preview_tree = preview.get_preview_tree()
2499
file_trans_id = preview.new_file('a', preview.root, 'contents',
2501
expected = [(('', 'tree-root'),
2502
[('a', 'a', 'file', None, 'a-id', 'file')])]
2503
self.assertEqual(expected, list(preview_tree.walkdirs()))
2505
def test_extras(self):
2506
work_tree = self.make_branch_and_tree('tree')
2507
self.build_tree(['tree/removed-file', 'tree/existing-file',
2508
'tree/not-removed-file'])
2509
work_tree.add(['removed-file', 'not-removed-file'])
2510
preview = TransformPreview(work_tree)
2511
self.addCleanup(preview.finalize)
2512
preview.new_file('new-file', preview.root, 'contents')
2513
preview.new_file('new-versioned-file', preview.root, 'contents',
2515
tree = preview.get_preview_tree()
2516
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2517
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2520
def test_merge_into_preview(self):
2521
work_tree = self.make_branch_and_tree('tree')
2522
self.build_tree_contents([('tree/file','b\n')])
2523
work_tree.add('file', 'file-id')
2524
work_tree.commit('first commit')
2525
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2526
self.build_tree_contents([('child/file','b\nc\n')])
2527
child_tree.commit('child commit')
2528
child_tree.lock_write()
2529
self.addCleanup(child_tree.unlock)
2530
work_tree.lock_write()
2531
self.addCleanup(work_tree.unlock)
2532
preview = TransformPreview(work_tree)
2533
self.addCleanup(preview.finalize)
2534
preview_tree = preview.get_preview_tree()
2535
file_trans_id = preview.trans_id_file_id('file-id')
2536
preview.delete_contents(file_trans_id)
2537
preview.create_file('a\nb\n', file_trans_id)
2538
pb = progress.DummyProgress()
2539
merger = Merger.from_revision_ids(pb, preview_tree,
2540
child_tree.branch.last_revision(),
2541
other_branch=child_tree.branch,
2542
tree_branch=work_tree.branch)
2543
merger.merge_type = Merge3Merger
2544
tt = merger.make_merger().make_preview_transform()
2545
self.addCleanup(tt.finalize)
2546
final_tree = tt.get_preview_tree()
2547
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2549
def test_merge_preview_into_workingtree(self):
2550
tree = self.make_branch_and_tree('tree')
2551
tt = TransformPreview(tree)
2552
self.addCleanup(tt.finalize)
2553
tt.new_file('name', tt.root, 'content', 'file-id')
2554
tree2 = self.make_branch_and_tree('tree2')
2555
pb = progress.DummyProgress()
2556
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2557
pb, tree.basis_tree())
2558
merger.merge_type = Merge3Merger
2561
def test_merge_preview_into_workingtree_handles_conflicts(self):
2562
tree = self.make_branch_and_tree('tree')
2563
self.build_tree_contents([('tree/foo', 'bar')])
2564
tree.add('foo', 'foo-id')
2566
tt = TransformPreview(tree)
2567
self.addCleanup(tt.finalize)
2568
trans_id = tt.trans_id_file_id('foo-id')
2569
tt.delete_contents(trans_id)
2570
tt.create_file('baz', trans_id)
2571
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2572
self.build_tree_contents([('tree2/foo', 'qux')])
2573
pb = progress.DummyProgress()
2574
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2575
pb, tree.basis_tree())
2576
merger.merge_type = Merge3Merger
2579
def test_is_executable(self):
2580
tree = self.make_branch_and_tree('tree')
2581
preview = TransformPreview(tree)
2582
self.addCleanup(preview.finalize)
2583
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2584
preview_tree = preview.get_preview_tree()
2585
self.assertEqual(False, preview_tree.is_executable('baz-id',
2587
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2590
class FakeSerializer(object):
2591
"""Serializer implementation that simply returns the input.
2593
The input is returned in the order used by pack.ContainerPushParser.
2596
def bytes_record(bytes, names):
2600
class TestSerializeTransform(tests.TestCaseWithTransport):
2602
_test_needs_features = [tests.UnicodeFilenameFeature]
2604
def get_preview(self, tree=None):
2606
tree = self.make_branch_and_tree('tree')
2607
tt = TransformPreview(tree)
2608
self.addCleanup(tt.finalize)
2611
def assertSerializesTo(self, expected, tt):
2612
records = list(tt.serialize(FakeSerializer()))
2613
self.assertEqual(expected, records)
2616
def default_attribs():
2621
'_new_executability': {},
2623
'_tree_path_ids': {'': 'new-0'},
2625
'_removed_contents': [],
2626
'_non_present_ids': {},
2629
def make_records(self, attribs, contents):
2631
(((('attribs'),),), bencode.bencode(attribs))]
2632
records.extend([(((n, k),), c) for n, k, c in contents])
2635
def creation_records(self):
2636
attribs = self.default_attribs()
2637
attribs['_id_number'] = 3
2638
attribs['_new_name'] = {
2639
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2640
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2641
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2642
attribs['_new_executability'] = {'new-1': 1}
2644
('new-1', 'file', 'i 1\nbar\n'),
2645
('new-2', 'directory', ''),
2647
return self.make_records(attribs, contents)
2649
def test_serialize_creation(self):
2650
tt = self.get_preview()
2651
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2652
tt.new_directory('qux', tt.root, 'quxx')
2653
self.assertSerializesTo(self.creation_records(), tt)
2655
def test_deserialize_creation(self):
2656
tt = self.get_preview()
2657
tt.deserialize(iter(self.creation_records()))
2658
self.assertEqual(3, tt._id_number)
2659
self.assertEqual({'new-1': u'foo\u1234',
2660
'new-2': 'qux'}, tt._new_name)
2661
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2662
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2663
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2664
self.assertEqual({'new-1': True}, tt._new_executability)
2665
self.assertEqual({'new-1': 'file',
2666
'new-2': 'directory'}, tt._new_contents)
2667
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2669
foo_content = foo_limbo.read()
2672
self.assertEqual('bar', foo_content)
2674
def symlink_creation_records(self):
2675
attribs = self.default_attribs()
2676
attribs['_id_number'] = 2
2677
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2678
attribs['_new_parent'] = {'new-1': 'new-0'}
2679
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2680
return self.make_records(attribs, contents)
2682
def test_serialize_symlink_creation(self):
2683
self.requireFeature(tests.SymlinkFeature)
2684
tt = self.get_preview()
2685
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2686
self.assertSerializesTo(self.symlink_creation_records(), tt)
2688
def test_deserialize_symlink_creation(self):
2689
tt = self.get_preview()
2690
tt.deserialize(iter(self.symlink_creation_records()))
2691
# XXX readlink should be returning unicode, not utf-8
2692
foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
2693
self.assertEqual(u'bar\u1234', foo_content)
2695
def make_destruction_preview(self):
2696
tree = self.make_branch_and_tree('.')
2697
self.build_tree([u'foo\u1234', 'bar'])
2698
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2699
return self.get_preview(tree)
2701
def destruction_records(self):
2702
attribs = self.default_attribs()
2703
attribs['_id_number'] = 3
2704
attribs['_removed_id'] = ['new-1']
2705
attribs['_removed_contents'] = ['new-2']
2706
attribs['_tree_path_ids'] = {
2708
u'foo\u1234'.encode('utf-8'): 'new-1',
2711
return self.make_records(attribs, [])
2713
def test_serialize_destruction(self):
2714
tt = self.make_destruction_preview()
2715
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2716
tt.unversion_file(foo_trans_id)
2717
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2718
tt.delete_contents(bar_trans_id)
2719
self.assertSerializesTo(self.destruction_records(), tt)
2721
def test_deserialize_destruction(self):
2722
tt = self.make_destruction_preview()
2723
tt.deserialize(iter(self.destruction_records()))
2724
self.assertEqual({u'foo\u1234': 'new-1',
2726
'': tt.root}, tt._tree_path_ids)
2727
self.assertEqual({'new-1': u'foo\u1234',
2729
tt.root: ''}, tt._tree_id_paths)
2730
self.assertEqual(set(['new-1']), tt._removed_id)
2731
self.assertEqual(set(['new-2']), tt._removed_contents)
2733
def missing_records(self):
2734
attribs = self.default_attribs()
2735
attribs['_id_number'] = 2
2736
attribs['_non_present_ids'] = {
2738
return self.make_records(attribs, [])
2740
def test_serialize_missing(self):
2741
tt = self.get_preview()
2742
boo_trans_id = tt.trans_id_file_id('boo')
2743
self.assertSerializesTo(self.missing_records(), tt)
2745
def test_deserialize_missing(self):
2746
tt = self.get_preview()
2747
tt.deserialize(iter(self.missing_records()))
2748
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2750
def make_modification_preview(self):
2751
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2752
LINES_TWO = 'z\nbb\nx\ndd\n'
2753
tree = self.make_branch_and_tree('tree')
2754
self.build_tree_contents([('tree/file', LINES_ONE)])
2755
tree.add('file', 'file-id')
2756
return self.get_preview(tree), LINES_TWO
2758
def modification_records(self):
2759
attribs = self.default_attribs()
2760
attribs['_id_number'] = 2
2761
attribs['_tree_path_ids'] = {
2764
attribs['_removed_contents'] = ['new-1']
2765
contents = [('new-1', 'file',
2766
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2767
return self.make_records(attribs, contents)
2769
def test_serialize_modification(self):
2770
tt, LINES = self.make_modification_preview()
2771
trans_id = tt.trans_id_file_id('file-id')
2772
tt.delete_contents(trans_id)
2773
tt.create_file(LINES, trans_id)
2774
self.assertSerializesTo(self.modification_records(), tt)
2776
def test_deserialize_modification(self):
2777
tt, LINES = self.make_modification_preview()
2778
tt.deserialize(iter(self.modification_records()))
2779
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2781
def make_kind_change_preview(self):
2782
LINES = 'a\nb\nc\nd\n'
2783
tree = self.make_branch_and_tree('tree')
2784
self.build_tree(['tree/foo/'])
2785
tree.add('foo', 'foo-id')
2786
return self.get_preview(tree), LINES
2788
def kind_change_records(self):
2789
attribs = self.default_attribs()
2790
attribs['_id_number'] = 2
2791
attribs['_tree_path_ids'] = {
2794
attribs['_removed_contents'] = ['new-1']
2795
contents = [('new-1', 'file',
2796
'i 4\na\nb\nc\nd\n\n')]
2797
return self.make_records(attribs, contents)
2799
def test_serialize_kind_change(self):
2800
tt, LINES = self.make_kind_change_preview()
2801
trans_id = tt.trans_id_file_id('foo-id')
2802
tt.delete_contents(trans_id)
2803
tt.create_file(LINES, trans_id)
2804
self.assertSerializesTo(self.kind_change_records(), tt)
2806
def test_deserialize_kind_change(self):
2807
tt, LINES = self.make_kind_change_preview()
2808
tt.deserialize(iter(self.kind_change_records()))
2809
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2811
def make_add_contents_preview(self):
2812
LINES = 'a\nb\nc\nd\n'
2813
tree = self.make_branch_and_tree('tree')
2814
self.build_tree(['tree/foo'])
2816
os.unlink('tree/foo')
2817
return self.get_preview(tree), LINES
2819
def add_contents_records(self):
2820
attribs = self.default_attribs()
2821
attribs['_id_number'] = 2
2822
attribs['_tree_path_ids'] = {
2825
contents = [('new-1', 'file',
2826
'i 4\na\nb\nc\nd\n\n')]
2827
return self.make_records(attribs, contents)
2829
def test_serialize_add_contents(self):
2830
tt, LINES = self.make_add_contents_preview()
2831
trans_id = tt.trans_id_tree_path('foo')
2832
tt.create_file(LINES, trans_id)
2833
self.assertSerializesTo(self.add_contents_records(), tt)
2835
def test_deserialize_add_contents(self):
2836
tt, LINES = self.make_add_contents_preview()
2837
tt.deserialize(iter(self.add_contents_records()))
2838
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2840
def test_get_parents_lines(self):
2841
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2842
LINES_TWO = 'z\nbb\nx\ndd\n'
2843
tree = self.make_branch_and_tree('tree')
2844
self.build_tree_contents([('tree/file', LINES_ONE)])
2845
tree.add('file', 'file-id')
2846
tt = self.get_preview(tree)
2847
trans_id = tt.trans_id_tree_path('file')
2848
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
2849
tt._get_parents_lines(trans_id))
2851
def test_get_parents_texts(self):
2852
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2853
LINES_TWO = 'z\nbb\nx\ndd\n'
2854
tree = self.make_branch_and_tree('tree')
2855
self.build_tree_contents([('tree/file', LINES_ONE)])
2856
tree.add('file', 'file-id')
2857
tt = self.get_preview(tree)
2858
trans_id = tt.trans_id_tree_path('file')
2859
self.assertEqual((LINES_ONE,),
2860
tt._get_parents_texts(trans_id))