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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 (
51
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
52
resolve_conflicts, cook_conflicts,
53
build_tree, get_backup_name,
54
_FileMover, resolve_checkout,
55
TransformPreview, create_from_tree)
56
from bzrlib.util import bencode
59
class TestTreeTransform(tests.TestCaseWithTransport):
62
super(TestTreeTransform, self).setUp()
63
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
66
def get_transform(self):
67
transform = TreeTransform(self.wt)
68
self.addCleanup(transform.finalize)
69
return transform, transform.root
71
def test_existing_limbo(self):
72
transform, root = self.get_transform()
73
limbo_name = transform._limbodir
74
deletion_path = transform._deletiondir
75
os.mkdir(pathjoin(limbo_name, 'hehe'))
76
self.assertRaises(ImmortalLimbo, transform.apply)
77
self.assertRaises(LockError, self.wt.unlock)
78
self.assertRaises(ExistingLimbo, self.get_transform)
79
self.assertRaises(LockError, self.wt.unlock)
80
os.rmdir(pathjoin(limbo_name, 'hehe'))
82
os.rmdir(deletion_path)
83
transform, root = self.get_transform()
86
def test_existing_pending_deletion(self):
87
transform, root = self.get_transform()
88
deletion_path = self._limbodir = urlutils.local_path_from_url(
89
transform._tree._transport.abspath('pending-deletion'))
90
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
91
self.assertRaises(ImmortalPendingDeletion, transform.apply)
92
self.assertRaises(LockError, self.wt.unlock)
93
self.assertRaises(ExistingPendingDeletion, self.get_transform)
96
transform, root = self.get_transform()
97
self.wt.lock_tree_write()
98
self.addCleanup(self.wt.unlock)
99
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
100
imaginary_id = transform.trans_id_tree_path('imaginary')
101
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
102
self.assertEqual(imaginary_id, imaginary_id2)
103
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
104
self.assertEqual(transform.final_kind(root), 'directory')
105
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
106
trans_id = transform.create_path('name', root)
107
self.assertIs(transform.final_file_id(trans_id), None)
108
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
109
transform.create_file('contents', trans_id)
110
transform.set_executability(True, trans_id)
111
transform.version_file('my_pretties', trans_id)
112
self.assertRaises(DuplicateKey, transform.version_file,
113
'my_pretties', trans_id)
114
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
115
self.assertEqual(transform.final_parent(trans_id), root)
116
self.assertIs(transform.final_parent(root), ROOT_PARENT)
117
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
118
oz_id = transform.create_path('oz', root)
119
transform.create_directory(oz_id)
120
transform.version_file('ozzie', oz_id)
121
trans_id2 = transform.create_path('name2', root)
122
transform.create_file('contents', trans_id2)
123
transform.set_executability(False, trans_id2)
124
transform.version_file('my_pretties2', trans_id2)
125
modified_paths = transform.apply().modified_paths
126
self.assertEqual('contents', self.wt.get_file_byname('name').read())
127
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
128
self.assertIs(self.wt.is_executable('my_pretties'), True)
129
self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
self.assertEqual(len(modified_paths), 3)
132
tree_mod_paths = [self.wt.id2abspath(f) for f in
133
('ozzie', 'my_pretties', 'my_pretties2')]
134
self.assertSubset(tree_mod_paths, modified_paths)
135
# is it safe to finalize repeatedly?
139
def test_hardlink(self):
140
self.requireFeature(HardlinkFeature)
141
transform, root = self.get_transform()
142
transform.new_file('file1', root, 'contents')
144
target = self.make_branch_and_tree('target')
145
target_transform = TreeTransform(target)
146
trans_id = target_transform.create_path('file1', target_transform.root)
147
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
148
target_transform.apply()
149
self.failUnlessExists('target/file1')
150
source_stat = os.stat(self.wt.abspath('file1'))
151
target_stat = os.stat('target/file1')
152
self.assertEqual(source_stat, target_stat)
154
def test_convenience(self):
155
transform, root = self.get_transform()
156
self.wt.lock_tree_write()
157
self.addCleanup(self.wt.unlock)
158
trans_id = transform.new_file('name', root, 'contents',
160
oz = transform.new_directory('oz', root, 'oz-id')
161
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
toto = transform.new_file('toto', dorothy, 'toto-contents',
165
self.assertEqual(len(transform.find_conflicts()), 0)
167
self.assertRaises(ReusingTransform, transform.find_conflicts)
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
169
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
170
self.assertIs(self.wt.is_executable('my_pretties'), True)
171
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
172
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
173
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
175
self.assertEqual('toto-contents',
176
self.wt.get_file_byname('oz/dorothy/toto').read())
177
self.assertIs(self.wt.is_executable('toto-id'), False)
179
def test_tree_reference(self):
180
transform, root = self.get_transform()
181
tree = transform._tree
182
trans_id = transform.new_directory('reference', root, 'subtree-id')
183
transform.set_tree_reference('subtree-revision', trans_id)
186
self.addCleanup(tree.unlock)
187
self.assertEqual('subtree-revision',
188
tree.inventory['subtree-id'].reference_revision)
190
def test_conflicts(self):
191
transform, root = self.get_transform()
192
trans_id = transform.new_file('name', root, 'contents',
194
self.assertEqual(len(transform.find_conflicts()), 0)
195
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
self.assertEqual(transform.find_conflicts(),
197
[('duplicate', trans_id, trans_id2, 'name')])
198
self.assertRaises(MalformedTransform, transform.apply)
199
transform.adjust_path('name', trans_id, trans_id2)
200
self.assertEqual(transform.find_conflicts(),
201
[('non-directory parent', trans_id)])
202
tinman_id = transform.trans_id_tree_path('tinman')
203
transform.adjust_path('name', tinman_id, trans_id2)
204
self.assertEqual(transform.find_conflicts(),
205
[('unversioned parent', tinman_id),
206
('missing parent', tinman_id)])
207
lion_id = transform.create_path('lion', root)
208
self.assertEqual(transform.find_conflicts(),
209
[('unversioned parent', tinman_id),
210
('missing parent', tinman_id)])
211
transform.adjust_path('name', lion_id, trans_id2)
212
self.assertEqual(transform.find_conflicts(),
213
[('unversioned parent', lion_id),
214
('missing parent', lion_id)])
215
transform.version_file("Courage", lion_id)
216
self.assertEqual(transform.find_conflicts(),
217
[('missing parent', lion_id),
218
('versioning no contents', lion_id)])
219
transform.adjust_path('name2', root, trans_id2)
220
self.assertEqual(transform.find_conflicts(),
221
[('versioning no contents', lion_id)])
222
transform.create_file('Contents, okay?', lion_id)
223
transform.adjust_path('name2', trans_id2, trans_id2)
224
self.assertEqual(transform.find_conflicts(),
225
[('parent loop', trans_id2),
226
('non-directory parent', trans_id2)])
227
transform.adjust_path('name2', root, trans_id2)
228
oz_id = transform.new_directory('oz', root)
229
transform.set_executability(True, oz_id)
230
self.assertEqual(transform.find_conflicts(),
231
[('unversioned executability', oz_id)])
232
transform.version_file('oz-id', oz_id)
233
self.assertEqual(transform.find_conflicts(),
234
[('non-file executability', oz_id)])
235
transform.set_executability(None, oz_id)
236
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
238
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
239
self.assertEqual('contents', file(self.wt.abspath('name')).read())
240
transform2, root = self.get_transform()
241
oz_id = transform2.trans_id_tree_file_id('oz-id')
242
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
243
result = transform2.find_conflicts()
244
fp = FinalPaths(transform2)
245
self.assert_('oz/tip' in transform2._tree_path_ids)
246
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
self.assertEqual(len(result), 2)
248
self.assertEqual((result[0][0], result[0][1]),
249
('duplicate', newtip))
250
self.assertEqual((result[1][0], result[1][2]),
251
('duplicate id', newtip))
252
transform2.finalize()
253
transform3 = TreeTransform(self.wt)
254
self.addCleanup(transform3.finalize)
255
oz_id = transform3.trans_id_tree_file_id('oz-id')
256
transform3.delete_contents(oz_id)
257
self.assertEqual(transform3.find_conflicts(),
258
[('missing parent', oz_id)])
259
root_id = transform3.root
260
tip_id = transform3.trans_id_tree_file_id('tip-id')
261
transform3.adjust_path('tip', root_id, tip_id)
264
def test_conflict_on_case_insensitive(self):
265
tree = self.make_branch_and_tree('tree')
266
# Don't try this at home, kids!
267
# Force the tree to report that it is case sensitive, for conflict
269
tree.case_sensitive = True
270
transform = TreeTransform(tree)
271
self.addCleanup(transform.finalize)
272
transform.new_file('file', transform.root, 'content')
273
transform.new_file('FiLe', transform.root, 'content')
274
result = transform.find_conflicts()
275
self.assertEqual([], result)
277
# Force the tree to report that it is case insensitive, for conflict
279
tree.case_sensitive = False
280
transform = TreeTransform(tree)
281
self.addCleanup(transform.finalize)
282
transform.new_file('file', transform.root, 'content')
283
transform.new_file('FiLe', transform.root, 'content')
284
result = transform.find_conflicts()
285
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
287
def test_conflict_on_case_insensitive_existing(self):
288
tree = self.make_branch_and_tree('tree')
289
self.build_tree(['tree/FiLe'])
290
# Don't try this at home, kids!
291
# Force the tree to report that it is case sensitive, for conflict
293
tree.case_sensitive = True
294
transform = TreeTransform(tree)
295
self.addCleanup(transform.finalize)
296
transform.new_file('file', transform.root, 'content')
297
result = transform.find_conflicts()
298
self.assertEqual([], result)
300
# Force the tree to report that it is case insensitive, for conflict
302
tree.case_sensitive = False
303
transform = TreeTransform(tree)
304
self.addCleanup(transform.finalize)
305
transform.new_file('file', transform.root, 'content')
306
result = transform.find_conflicts()
307
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
309
def test_resolve_case_insensitive_conflict(self):
310
tree = self.make_branch_and_tree('tree')
311
# Don't try this at home, kids!
312
# Force the tree to report that it is case insensitive, for conflict
314
tree.case_sensitive = False
315
transform = TreeTransform(tree)
316
self.addCleanup(transform.finalize)
317
transform.new_file('file', transform.root, 'content')
318
transform.new_file('FiLe', transform.root, 'content')
319
resolve_conflicts(transform)
321
self.failUnlessExists('tree/file')
322
self.failUnlessExists('tree/FiLe.moved')
324
def test_resolve_checkout_case_conflict(self):
325
tree = self.make_branch_and_tree('tree')
326
# Don't try this at home, kids!
327
# Force the tree to report that it is case insensitive, for conflict
329
tree.case_sensitive = False
330
transform = TreeTransform(tree)
331
self.addCleanup(transform.finalize)
332
transform.new_file('file', transform.root, 'content')
333
transform.new_file('FiLe', transform.root, 'content')
334
resolve_conflicts(transform,
335
pass_func=lambda t, c: resolve_checkout(t, c, []))
337
self.failUnlessExists('tree/file')
338
self.failUnlessExists('tree/FiLe.moved')
340
def test_apply_case_conflict(self):
341
"""Ensure that a transform with case conflicts can always be applied"""
342
tree = self.make_branch_and_tree('tree')
343
transform = TreeTransform(tree)
344
self.addCleanup(transform.finalize)
345
transform.new_file('file', transform.root, 'content')
346
transform.new_file('FiLe', transform.root, 'content')
347
dir = transform.new_directory('dir', transform.root)
348
transform.new_file('dirfile', dir, 'content')
349
transform.new_file('dirFiLe', dir, 'content')
350
resolve_conflicts(transform)
352
self.failUnlessExists('tree/file')
353
if not os.path.exists('tree/FiLe.moved'):
354
self.failUnlessExists('tree/FiLe')
355
self.failUnlessExists('tree/dir/dirfile')
356
if not os.path.exists('tree/dir/dirFiLe.moved'):
357
self.failUnlessExists('tree/dir/dirFiLe')
359
def test_case_insensitive_limbo(self):
360
tree = self.make_branch_and_tree('tree')
361
# Don't try this at home, kids!
362
# Force the tree to report that it is case insensitive
363
tree.case_sensitive = False
364
transform = TreeTransform(tree)
365
self.addCleanup(transform.finalize)
366
dir = transform.new_directory('dir', transform.root)
367
first = transform.new_file('file', dir, 'content')
368
second = transform.new_file('FiLe', dir, 'content')
369
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
372
def test_add_del(self):
373
start, root = self.get_transform()
374
start.new_directory('a', root, 'a')
376
transform, root = self.get_transform()
377
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
378
transform.new_directory('a', root, 'a')
381
def test_unversioning(self):
382
create_tree, root = self.get_transform()
383
parent_id = create_tree.new_directory('parent', root, 'parent-id')
384
create_tree.new_file('child', parent_id, 'child', 'child-id')
386
unversion = TreeTransform(self.wt)
387
self.addCleanup(unversion.finalize)
388
parent = unversion.trans_id_tree_path('parent')
389
unversion.unversion_file(parent)
390
self.assertEqual(unversion.find_conflicts(),
391
[('unversioned parent', parent_id)])
392
file_id = unversion.trans_id_tree_file_id('child-id')
393
unversion.unversion_file(file_id)
396
def test_name_invariants(self):
397
create_tree, root = self.get_transform()
399
root = create_tree.root
400
create_tree.new_file('name1', root, 'hello1', 'name1')
401
create_tree.new_file('name2', root, 'hello2', 'name2')
402
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
403
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
404
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
405
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
408
mangle_tree,root = self.get_transform()
409
root = mangle_tree.root
411
name1 = mangle_tree.trans_id_tree_file_id('name1')
412
name2 = mangle_tree.trans_id_tree_file_id('name2')
413
mangle_tree.adjust_path('name2', root, name1)
414
mangle_tree.adjust_path('name1', root, name2)
416
#tests for deleting parent directories
417
ddir = mangle_tree.trans_id_tree_file_id('ddir')
418
mangle_tree.delete_contents(ddir)
419
dfile = mangle_tree.trans_id_tree_file_id('dfile')
420
mangle_tree.delete_versioned(dfile)
421
mangle_tree.unversion_file(dfile)
422
mfile = mangle_tree.trans_id_tree_file_id('mfile')
423
mangle_tree.adjust_path('mfile', root, mfile)
425
#tests for adding parent directories
426
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
427
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
428
mangle_tree.adjust_path('mfile2', newdir, mfile2)
429
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
430
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
431
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
432
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
434
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
435
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
436
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
437
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
438
self.assertEqual(file(mfile2_path).read(), 'later2')
439
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
440
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
441
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
442
self.assertEqual(file(newfile_path).read(), 'hello3')
443
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
444
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
445
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
447
def test_both_rename(self):
448
create_tree,root = self.get_transform()
449
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
450
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
452
mangle_tree,root = self.get_transform()
453
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
454
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
455
mangle_tree.adjust_path('test', root, selftest)
456
mangle_tree.adjust_path('test_too_much', root, selftest)
457
mangle_tree.set_executability(True, blackbox)
460
def test_both_rename2(self):
461
create_tree,root = self.get_transform()
462
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
463
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
464
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
465
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
468
mangle_tree,root = self.get_transform()
469
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
470
tests = mangle_tree.trans_id_tree_file_id('tests-id')
471
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
472
mangle_tree.adjust_path('selftest', bzrlib, tests)
473
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
474
mangle_tree.set_executability(True, test_too_much)
477
def test_both_rename3(self):
478
create_tree,root = self.get_transform()
479
tests = create_tree.new_directory('tests', root, 'tests-id')
480
create_tree.new_file('test_too_much.py', tests, 'hello1',
483
mangle_tree,root = self.get_transform()
484
tests = mangle_tree.trans_id_tree_file_id('tests-id')
485
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
486
mangle_tree.adjust_path('selftest', root, tests)
487
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
488
mangle_tree.set_executability(True, test_too_much)
491
def test_move_dangling_ie(self):
492
create_tree, root = self.get_transform()
494
root = create_tree.root
495
create_tree.new_file('name1', root, 'hello1', 'name1')
497
delete_contents, root = self.get_transform()
498
file = delete_contents.trans_id_tree_file_id('name1')
499
delete_contents.delete_contents(file)
500
delete_contents.apply()
501
move_id, root = self.get_transform()
502
name1 = move_id.trans_id_tree_file_id('name1')
503
newdir = move_id.new_directory('dir', root, 'newdir')
504
move_id.adjust_path('name2', newdir, name1)
507
def test_replace_dangling_ie(self):
508
create_tree, root = self.get_transform()
510
root = create_tree.root
511
create_tree.new_file('name1', root, 'hello1', 'name1')
513
delete_contents = TreeTransform(self.wt)
514
self.addCleanup(delete_contents.finalize)
515
file = delete_contents.trans_id_tree_file_id('name1')
516
delete_contents.delete_contents(file)
517
delete_contents.apply()
518
delete_contents.finalize()
519
replace = TreeTransform(self.wt)
520
self.addCleanup(replace.finalize)
521
name2 = replace.new_file('name2', root, 'hello2', 'name1')
522
conflicts = replace.find_conflicts()
523
name1 = replace.trans_id_tree_file_id('name1')
524
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
525
resolve_conflicts(replace)
528
def test_symlinks(self):
529
self.requireFeature(SymlinkFeature)
530
transform,root = self.get_transform()
531
oz_id = transform.new_directory('oz', root, 'oz-id')
532
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
534
wiz_id = transform.create_path('wizard2', oz_id)
535
transform.create_symlink('behind_curtain', wiz_id)
536
transform.version_file('wiz-id2', wiz_id)
537
transform.set_executability(True, wiz_id)
538
self.assertEqual(transform.find_conflicts(),
539
[('non-file executability', wiz_id)])
540
transform.set_executability(None, wiz_id)
542
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
543
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
544
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
546
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
549
def test_unable_create_symlink(self):
551
wt = self.make_branch_and_tree('.')
552
tt = TreeTransform(wt) # TreeTransform obtains write lock
554
tt.new_symlink('foo', tt.root, 'bar')
558
os_symlink = getattr(os, 'symlink', None)
561
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
563
"Unable to create symlink 'foo' on this platform",
567
os.symlink = os_symlink
569
def get_conflicted(self):
570
create,root = self.get_transform()
571
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
572
oz = create.new_directory('oz', root, 'oz-id')
573
create.new_directory('emeraldcity', oz, 'emerald-id')
575
conflicts,root = self.get_transform()
576
# set up duplicate entry, duplicate id
577
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
579
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
580
oz = conflicts.trans_id_tree_file_id('oz-id')
581
# set up DeletedParent parent conflict
582
conflicts.delete_versioned(oz)
583
emerald = conflicts.trans_id_tree_file_id('emerald-id')
584
# set up MissingParent conflict
585
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
586
conflicts.adjust_path('munchkincity', root, munchkincity)
587
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
589
conflicts.adjust_path('emeraldcity', emerald, emerald)
590
return conflicts, emerald, oz, old_dorothy, new_dorothy
592
def test_conflict_resolution(self):
593
conflicts, emerald, oz, old_dorothy, new_dorothy =\
594
self.get_conflicted()
595
resolve_conflicts(conflicts)
596
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
597
self.assertIs(conflicts.final_file_id(old_dorothy), None)
598
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
599
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
600
self.assertEqual(conflicts.final_parent(emerald), oz)
603
def test_cook_conflicts(self):
604
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
605
raw_conflicts = resolve_conflicts(tt)
606
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
607
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
608
'dorothy', None, 'dorothy-id')
609
self.assertEqual(cooked_conflicts[0], duplicate)
610
duplicate_id = DuplicateID('Unversioned existing file',
611
'dorothy.moved', 'dorothy', None,
613
self.assertEqual(cooked_conflicts[1], duplicate_id)
614
missing_parent = MissingParent('Created directory', 'munchkincity',
616
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
617
self.assertEqual(cooked_conflicts[2], missing_parent)
618
unversioned_parent = UnversionedParent('Versioned directory',
621
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
623
self.assertEqual(cooked_conflicts[3], unversioned_parent)
624
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
625
'oz/emeraldcity', 'emerald-id', 'emerald-id')
626
self.assertEqual(cooked_conflicts[4], deleted_parent)
627
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
628
self.assertEqual(cooked_conflicts[6], parent_loop)
629
self.assertEqual(len(cooked_conflicts), 7)
632
def test_string_conflicts(self):
633
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
634
raw_conflicts = resolve_conflicts(tt)
635
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
637
conflicts_s = [str(c) for c in cooked_conflicts]
638
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
639
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
640
'Moved existing file to '
642
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
643
'Unversioned existing file '
645
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
646
' munchkincity. Created directory.')
647
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
648
' versioned, but has versioned'
649
' children. Versioned directory.')
650
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
651
" is not empty. Not deleting.")
652
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
653
' versioned, but has versioned'
654
' children. Versioned directory.')
655
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
656
' oz/emeraldcity. Cancelled move.')
658
def prepare_wrong_parent_kind(self):
659
tt, root = self.get_transform()
660
tt.new_file('parent', root, 'contents', 'parent-id')
662
tt, root = self.get_transform()
663
parent_id = tt.trans_id_file_id('parent-id')
664
tt.new_file('child,', parent_id, 'contents2', 'file-id')
667
def test_find_conflicts_wrong_parent_kind(self):
668
tt = self.prepare_wrong_parent_kind()
671
def test_resolve_conflicts_wrong_existing_parent_kind(self):
672
tt = self.prepare_wrong_parent_kind()
673
raw_conflicts = resolve_conflicts(tt)
674
self.assertEqual(set([('non-directory parent', 'Created directory',
675
'new-3')]), raw_conflicts)
676
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
677
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
678
'parent-id')], cooked_conflicts)
680
self.assertEqual(None, self.wt.path2id('parent'))
681
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
683
def test_resolve_conflicts_wrong_new_parent_kind(self):
684
tt, root = self.get_transform()
685
parent_id = tt.new_directory('parent', root, 'parent-id')
686
tt.new_file('child,', parent_id, 'contents2', 'file-id')
688
tt, root = self.get_transform()
689
parent_id = tt.trans_id_file_id('parent-id')
690
tt.delete_contents(parent_id)
691
tt.create_file('contents', parent_id)
692
raw_conflicts = resolve_conflicts(tt)
693
self.assertEqual(set([('non-directory parent', 'Created directory',
694
'new-3')]), raw_conflicts)
696
self.assertEqual(None, self.wt.path2id('parent'))
697
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
699
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
700
tt, root = self.get_transform()
701
parent_id = tt.new_directory('parent', root)
702
tt.new_file('child,', parent_id, 'contents2')
704
tt, root = self.get_transform()
705
parent_id = tt.trans_id_tree_path('parent')
706
tt.delete_contents(parent_id)
707
tt.create_file('contents', parent_id)
708
resolve_conflicts(tt)
710
self.assertIs(None, self.wt.path2id('parent'))
711
self.assertIs(None, self.wt.path2id('parent.new'))
713
def test_moving_versioned_directories(self):
714
create, root = self.get_transform()
715
kansas = create.new_directory('kansas', root, 'kansas-id')
716
create.new_directory('house', kansas, 'house-id')
717
create.new_directory('oz', root, 'oz-id')
719
cyclone, root = self.get_transform()
720
oz = cyclone.trans_id_tree_file_id('oz-id')
721
house = cyclone.trans_id_tree_file_id('house-id')
722
cyclone.adjust_path('house', oz, house)
725
def test_moving_root(self):
726
create, root = self.get_transform()
727
fun = create.new_directory('fun', root, 'fun-id')
728
create.new_directory('sun', root, 'sun-id')
729
create.new_directory('moon', root, 'moon')
731
transform, root = self.get_transform()
732
transform.adjust_root_path('oldroot', fun)
733
new_root=transform.trans_id_tree_path('')
734
transform.version_file('new-root', new_root)
737
def test_renames(self):
738
create, root = self.get_transform()
739
old = create.new_directory('old-parent', root, 'old-id')
740
intermediate = create.new_directory('intermediate', old, 'im-id')
741
myfile = create.new_file('myfile', intermediate, 'myfile-text',
744
rename, root = self.get_transform()
745
old = rename.trans_id_file_id('old-id')
746
rename.adjust_path('new', root, old)
747
myfile = rename.trans_id_file_id('myfile-id')
748
rename.set_executability(True, myfile)
751
def test_set_executability_order(self):
752
"""Ensure that executability behaves the same, no matter what order.
754
- create file and set executability simultaneously
755
- create file and set executability afterward
756
- unsetting the executability of a file whose executability has not been
757
declared should throw an exception (this may happen when a
758
merge attempts to create a file with a duplicate ID)
760
transform, root = self.get_transform()
763
self.addCleanup(wt.unlock)
764
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
766
sac = transform.new_file('set_after_creation', root,
767
'Set after creation', 'sac')
768
transform.set_executability(True, sac)
769
uws = transform.new_file('unset_without_set', root, 'Unset badly',
771
self.assertRaises(KeyError, transform.set_executability, None, uws)
773
self.assertTrue(wt.is_executable('soc'))
774
self.assertTrue(wt.is_executable('sac'))
776
def test_preserve_mode(self):
777
"""File mode is preserved when replacing content"""
778
if sys.platform == 'win32':
779
raise TestSkipped('chmod has no effect on win32')
780
transform, root = self.get_transform()
781
transform.new_file('file1', root, 'contents', 'file1-id', True)
784
self.addCleanup(self.wt.unlock)
785
self.assertTrue(self.wt.is_executable('file1-id'))
786
transform, root = self.get_transform()
787
file1_id = transform.trans_id_tree_file_id('file1-id')
788
transform.delete_contents(file1_id)
789
transform.create_file('contents2', file1_id)
791
self.assertTrue(self.wt.is_executable('file1-id'))
793
def test__set_mode_stats_correctly(self):
794
"""_set_mode stats to determine file mode."""
795
if sys.platform == 'win32':
796
raise TestSkipped('chmod has no effect on win32')
800
def instrumented_stat(path):
801
stat_paths.append(path)
802
return real_stat(path)
804
transform, root = self.get_transform()
806
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
807
file_id='bar-id-1', executable=False)
810
transform, root = self.get_transform()
811
bar1_id = transform.trans_id_tree_path('bar')
812
bar2_id = transform.trans_id_tree_path('bar2')
814
os.stat = instrumented_stat
815
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
820
bar1_abspath = self.wt.abspath('bar')
821
self.assertEqual([bar1_abspath], stat_paths)
823
def test_iter_changes(self):
824
self.wt.set_root_id('eert_toor')
825
transform, root = self.get_transform()
826
transform.new_file('old', root, 'blah', 'id-1', True)
828
transform, root = self.get_transform()
830
self.assertEqual([], list(transform.iter_changes()))
831
old = transform.trans_id_tree_file_id('id-1')
832
transform.unversion_file(old)
833
self.assertEqual([('id-1', ('old', None), False, (True, False),
834
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
835
(True, True))], list(transform.iter_changes()))
836
transform.new_directory('new', root, 'id-1')
837
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
838
('eert_toor', 'eert_toor'), ('old', 'new'),
839
('file', 'directory'),
840
(True, False))], list(transform.iter_changes()))
844
def test_iter_changes_new(self):
845
self.wt.set_root_id('eert_toor')
846
transform, root = self.get_transform()
847
transform.new_file('old', root, 'blah')
849
transform, root = self.get_transform()
851
old = transform.trans_id_tree_path('old')
852
transform.version_file('id-1', old)
853
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
854
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
855
(False, False))], list(transform.iter_changes()))
859
def test_iter_changes_modifications(self):
860
self.wt.set_root_id('eert_toor')
861
transform, root = self.get_transform()
862
transform.new_file('old', root, 'blah', 'id-1')
863
transform.new_file('new', root, 'blah')
864
transform.new_directory('subdir', root, 'subdir-id')
866
transform, root = self.get_transform()
868
old = transform.trans_id_tree_path('old')
869
subdir = transform.trans_id_tree_file_id('subdir-id')
870
new = transform.trans_id_tree_path('new')
871
self.assertEqual([], list(transform.iter_changes()))
874
transform.delete_contents(old)
875
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
876
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
877
(False, False))], list(transform.iter_changes()))
880
transform.create_file('blah', old)
881
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
882
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
883
(False, False))], list(transform.iter_changes()))
884
transform.cancel_deletion(old)
885
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
886
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
887
(False, False))], list(transform.iter_changes()))
888
transform.cancel_creation(old)
890
# move file_id to a different file
891
self.assertEqual([], list(transform.iter_changes()))
892
transform.unversion_file(old)
893
transform.version_file('id-1', new)
894
transform.adjust_path('old', root, new)
895
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
896
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
897
(False, False))], list(transform.iter_changes()))
898
transform.cancel_versioning(new)
899
transform._removed_id = set()
902
self.assertEqual([], list(transform.iter_changes()))
903
transform.set_executability(True, old)
904
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
905
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
906
(False, True))], list(transform.iter_changes()))
907
transform.set_executability(None, old)
910
self.assertEqual([], list(transform.iter_changes()))
911
transform.adjust_path('new', root, old)
912
transform._new_parent = {}
913
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
914
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
915
(False, False))], list(transform.iter_changes()))
916
transform._new_name = {}
919
self.assertEqual([], list(transform.iter_changes()))
920
transform.adjust_path('new', subdir, old)
921
transform._new_name = {}
922
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
923
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
924
('file', 'file'), (False, False))],
925
list(transform.iter_changes()))
926
transform._new_path = {}
931
def test_iter_changes_modified_bleed(self):
932
self.wt.set_root_id('eert_toor')
933
"""Modified flag should not bleed from one change to another"""
934
# unfortunately, we have no guarantee that file1 (which is modified)
935
# will be applied before file2. And if it's applied after file2, it
936
# obviously can't bleed into file2's change output. But for now, it
938
transform, root = self.get_transform()
939
transform.new_file('file1', root, 'blah', 'id-1')
940
transform.new_file('file2', root, 'blah', 'id-2')
942
transform, root = self.get_transform()
944
transform.delete_contents(transform.trans_id_file_id('id-1'))
945
transform.set_executability(True,
946
transform.trans_id_file_id('id-2'))
947
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
948
('eert_toor', 'eert_toor'), ('file1', u'file1'),
949
('file', None), (False, False)),
950
('id-2', (u'file2', u'file2'), False, (True, True),
951
('eert_toor', 'eert_toor'), ('file2', u'file2'),
952
('file', 'file'), (False, True))],
953
list(transform.iter_changes()))
957
def test_iter_changes_move_missing(self):
958
"""Test moving ids with no files around"""
959
self.wt.set_root_id('toor_eert')
960
# Need two steps because versioning a non-existant file is a conflict.
961
transform, root = self.get_transform()
962
transform.new_directory('floater', root, 'floater-id')
964
transform, root = self.get_transform()
965
transform.delete_contents(transform.trans_id_tree_path('floater'))
967
transform, root = self.get_transform()
968
floater = transform.trans_id_tree_path('floater')
970
transform.adjust_path('flitter', root, floater)
971
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
972
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
973
(None, None), (False, False))], list(transform.iter_changes()))
977
def test_iter_changes_pointless(self):
978
"""Ensure that no-ops are not treated as modifications"""
979
self.wt.set_root_id('eert_toor')
980
transform, root = self.get_transform()
981
transform.new_file('old', root, 'blah', 'id-1')
982
transform.new_directory('subdir', root, 'subdir-id')
984
transform, root = self.get_transform()
986
old = transform.trans_id_tree_path('old')
987
subdir = transform.trans_id_tree_file_id('subdir-id')
988
self.assertEqual([], list(transform.iter_changes()))
989
transform.delete_contents(subdir)
990
transform.create_directory(subdir)
991
transform.set_executability(False, old)
992
transform.unversion_file(old)
993
transform.version_file('id-1', old)
994
transform.adjust_path('old', root, old)
995
self.assertEqual([], list(transform.iter_changes()))
999
def test_rename_count(self):
1000
transform, root = self.get_transform()
1001
transform.new_file('name1', root, 'contents')
1002
self.assertEqual(transform.rename_count, 0)
1004
self.assertEqual(transform.rename_count, 1)
1005
transform2, root = self.get_transform()
1006
transform2.adjust_path('name2', root,
1007
transform2.trans_id_tree_path('name1'))
1008
self.assertEqual(transform2.rename_count, 0)
1010
self.assertEqual(transform2.rename_count, 2)
1012
def test_change_parent(self):
1013
"""Ensure that after we change a parent, the results are still right.
1015
Renames and parent changes on pending transforms can happen as part
1016
of conflict resolution, and are explicitly permitted by the
1019
This test ensures they work correctly with the rename-avoidance
1022
transform, root = self.get_transform()
1023
parent1 = transform.new_directory('parent1', root)
1024
child1 = transform.new_file('child1', parent1, 'contents')
1025
parent2 = transform.new_directory('parent2', root)
1026
transform.adjust_path('child1', parent2, child1)
1028
self.failIfExists(self.wt.abspath('parent1/child1'))
1029
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1030
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1031
# no rename for child1 (counting only renames during apply)
1032
self.failUnlessEqual(2, transform.rename_count)
1034
def test_cancel_parent(self):
1035
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1037
This is like the test_change_parent, except that we cancel the parent
1038
before adjusting the path. The transform must detect that the
1039
directory is non-empty, and move children to safe locations.
1041
transform, root = self.get_transform()
1042
parent1 = transform.new_directory('parent1', root)
1043
child1 = transform.new_file('child1', parent1, 'contents')
1044
child2 = transform.new_file('child2', parent1, 'contents')
1046
transform.cancel_creation(parent1)
1048
self.fail('Failed to move child1 before deleting parent1')
1049
transform.cancel_creation(child2)
1050
transform.create_directory(parent1)
1052
transform.cancel_creation(parent1)
1053
# If the transform incorrectly believes that child2 is still in
1054
# parent1's limbo directory, it will try to rename it and fail
1055
# because was already moved by the first cancel_creation.
1057
self.fail('Transform still thinks child2 is a child of parent1')
1058
parent2 = transform.new_directory('parent2', root)
1059
transform.adjust_path('child1', parent2, child1)
1061
self.failIfExists(self.wt.abspath('parent1'))
1062
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1063
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1064
self.failUnlessEqual(2, transform.rename_count)
1066
def test_adjust_and_cancel(self):
1067
"""Make sure adjust_path keeps track of limbo children properly"""
1068
transform, root = self.get_transform()
1069
parent1 = transform.new_directory('parent1', root)
1070
child1 = transform.new_file('child1', parent1, 'contents')
1071
parent2 = transform.new_directory('parent2', root)
1072
transform.adjust_path('child1', parent2, child1)
1073
transform.cancel_creation(child1)
1075
transform.cancel_creation(parent1)
1076
# if the transform thinks child1 is still in parent1's limbo
1077
# directory, it will attempt to move it and fail.
1079
self.fail('Transform still thinks child1 is a child of parent1')
1080
transform.finalize()
1082
def test_noname_contents(self):
1083
"""TreeTransform should permit deferring naming files."""
1084
transform, root = self.get_transform()
1085
parent = transform.trans_id_file_id('parent-id')
1087
transform.create_directory(parent)
1089
self.fail("Can't handle contents with no name")
1090
transform.finalize()
1092
def test_noname_contents_nested(self):
1093
"""TreeTransform should permit deferring naming files."""
1094
transform, root = self.get_transform()
1095
parent = transform.trans_id_file_id('parent-id')
1097
transform.create_directory(parent)
1099
self.fail("Can't handle contents with no name")
1100
child = transform.new_directory('child', parent)
1101
transform.adjust_path('parent', root, parent)
1103
self.failUnlessExists(self.wt.abspath('parent/child'))
1104
self.assertEqual(1, transform.rename_count)
1106
def test_reuse_name(self):
1107
"""Avoid reusing the same limbo name for different files"""
1108
transform, root = self.get_transform()
1109
parent = transform.new_directory('parent', root)
1110
child1 = transform.new_directory('child', parent)
1112
child2 = transform.new_directory('child', parent)
1114
self.fail('Tranform tried to use the same limbo name twice')
1115
transform.adjust_path('child2', parent, child2)
1117
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1118
# child2 is put into top-level limbo because child1 has already
1119
# claimed the direct limbo path when child2 is created. There is no
1120
# advantage in renaming files once they're in top-level limbo, except
1122
self.assertEqual(2, transform.rename_count)
1124
def test_reuse_when_first_moved(self):
1125
"""Don't avoid direct paths when it is safe to use them"""
1126
transform, root = self.get_transform()
1127
parent = transform.new_directory('parent', root)
1128
child1 = transform.new_directory('child', parent)
1129
transform.adjust_path('child1', parent, child1)
1130
child2 = transform.new_directory('child', parent)
1132
# limbo/new-1 => parent
1133
self.assertEqual(1, transform.rename_count)
1135
def test_reuse_after_cancel(self):
1136
"""Don't avoid direct paths when it is safe to use them"""
1137
transform, root = self.get_transform()
1138
parent2 = transform.new_directory('parent2', root)
1139
child1 = transform.new_directory('child1', parent2)
1140
transform.cancel_creation(parent2)
1141
transform.create_directory(parent2)
1142
child2 = transform.new_directory('child1', parent2)
1143
transform.adjust_path('child2', parent2, child1)
1145
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1146
self.assertEqual(2, transform.rename_count)
1148
def test_finalize_order(self):
1149
"""Finalize must be done in child-to-parent order"""
1150
transform, root = self.get_transform()
1151
parent = transform.new_directory('parent', root)
1152
child = transform.new_directory('child', parent)
1154
transform.finalize()
1156
self.fail('Tried to remove parent before child1')
1158
def test_cancel_with_cancelled_child_should_succeed(self):
1159
transform, root = self.get_transform()
1160
parent = transform.new_directory('parent', root)
1161
child = transform.new_directory('child', parent)
1162
transform.cancel_creation(child)
1163
transform.cancel_creation(parent)
1164
transform.finalize()
1166
def test_rollback_on_directory_clash(self):
1168
wt = self.make_branch_and_tree('.')
1169
tt = TreeTransform(wt) # TreeTransform obtains write lock
1171
foo = tt.new_directory('foo', tt.root)
1172
tt.new_file('bar', foo, 'foobar')
1173
baz = tt.new_directory('baz', tt.root)
1174
tt.new_file('qux', baz, 'quux')
1175
# Ask for a rename 'foo' -> 'baz'
1176
tt.adjust_path('baz', tt.root, foo)
1177
# Lie to tt that we've already resolved all conflicts.
1178
tt.apply(no_conflicts=True)
1182
# The rename will fail because the target directory is not empty (but
1183
# raises FileExists anyway).
1184
err = self.assertRaises(errors.FileExists, tt_helper)
1185
self.assertContainsRe(str(err),
1186
"^File exists: .+/baz")
1188
def test_two_directories_clash(self):
1190
wt = self.make_branch_and_tree('.')
1191
tt = TreeTransform(wt) # TreeTransform obtains write lock
1193
foo_1 = tt.new_directory('foo', tt.root)
1194
tt.new_directory('bar', foo_1)
1195
# Adding the same directory with a different content
1196
foo_2 = tt.new_directory('foo', tt.root)
1197
tt.new_directory('baz', foo_2)
1198
# Lie to tt that we've already resolved all conflicts.
1199
tt.apply(no_conflicts=True)
1203
err = self.assertRaises(errors.FileExists, tt_helper)
1204
self.assertContainsRe(str(err),
1205
"^File exists: .+/foo")
1207
def test_two_directories_clash_finalize(self):
1209
wt = self.make_branch_and_tree('.')
1210
tt = TreeTransform(wt) # TreeTransform obtains write lock
1212
foo_1 = tt.new_directory('foo', tt.root)
1213
tt.new_directory('bar', foo_1)
1214
# Adding the same directory with a different content
1215
foo_2 = tt.new_directory('foo', tt.root)
1216
tt.new_directory('baz', foo_2)
1217
# Lie to tt that we've already resolved all conflicts.
1218
tt.apply(no_conflicts=True)
1222
err = self.assertRaises(errors.FileExists, tt_helper)
1223
self.assertContainsRe(str(err),
1224
"^File exists: .+/foo")
1226
def test_file_to_directory(self):
1227
wt = self.make_branch_and_tree('.')
1228
self.build_tree(['foo'])
1231
tt = TreeTransform(wt)
1232
self.addCleanup(tt.finalize)
1233
foo_trans_id = tt.trans_id_tree_path("foo")
1234
tt.delete_contents(foo_trans_id)
1235
tt.create_directory(foo_trans_id)
1236
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1237
tt.create_file(["aa\n"], bar_trans_id)
1238
tt.version_file("bar-1", bar_trans_id)
1240
self.failUnlessExists("foo/bar")
1243
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1248
changes = wt.changes_from(wt.basis_tree())
1249
self.assertFalse(changes.has_changed(), changes)
1251
def test_file_to_symlink(self):
1252
self.requireFeature(SymlinkFeature)
1253
wt = self.make_branch_and_tree('.')
1254
self.build_tree(['foo'])
1257
tt = TreeTransform(wt)
1258
self.addCleanup(tt.finalize)
1259
foo_trans_id = tt.trans_id_tree_path("foo")
1260
tt.delete_contents(foo_trans_id)
1261
tt.create_symlink("bar", foo_trans_id)
1263
self.failUnlessExists("foo")
1265
self.addCleanup(wt.unlock)
1266
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1269
def test_dir_to_file(self):
1270
wt = self.make_branch_and_tree('.')
1271
self.build_tree(['foo/', 'foo/bar'])
1272
wt.add(['foo', 'foo/bar'])
1274
tt = TreeTransform(wt)
1275
self.addCleanup(tt.finalize)
1276
foo_trans_id = tt.trans_id_tree_path("foo")
1277
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1278
tt.delete_contents(foo_trans_id)
1279
tt.delete_versioned(bar_trans_id)
1280
tt.create_file(["aa\n"], foo_trans_id)
1282
self.failUnlessExists("foo")
1284
self.addCleanup(wt.unlock)
1285
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1288
def test_dir_to_hardlink(self):
1289
self.requireFeature(HardlinkFeature)
1290
wt = self.make_branch_and_tree('.')
1291
self.build_tree(['foo/', 'foo/bar'])
1292
wt.add(['foo', 'foo/bar'])
1294
tt = TreeTransform(wt)
1295
self.addCleanup(tt.finalize)
1296
foo_trans_id = tt.trans_id_tree_path("foo")
1297
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1298
tt.delete_contents(foo_trans_id)
1299
tt.delete_versioned(bar_trans_id)
1300
self.build_tree(['baz'])
1301
tt.create_hardlink("baz", foo_trans_id)
1303
self.failUnlessExists("foo")
1304
self.failUnlessExists("baz")
1306
self.addCleanup(wt.unlock)
1307
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1310
def test_no_final_path(self):
1311
transform, root = self.get_transform()
1312
trans_id = transform.trans_id_file_id('foo')
1313
transform.create_file('bar', trans_id)
1314
transform.cancel_creation(trans_id)
1317
def test_create_from_tree(self):
1318
tree1 = self.make_branch_and_tree('tree1')
1319
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1320
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1321
tree2 = self.make_branch_and_tree('tree2')
1322
tt = TreeTransform(tree2)
1323
foo_trans_id = tt.create_path('foo', tt.root)
1324
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1325
bar_trans_id = tt.create_path('bar', tt.root)
1326
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1328
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1329
self.assertFileEqual('baz', 'tree2/bar')
1331
def test_create_from_tree_bytes(self):
1332
"""Provided lines are used instead of tree content."""
1333
tree1 = self.make_branch_and_tree('tree1')
1334
self.build_tree_contents([('tree1/foo', 'bar'),])
1335
tree1.add('foo', 'foo-id')
1336
tree2 = self.make_branch_and_tree('tree2')
1337
tt = TreeTransform(tree2)
1338
foo_trans_id = tt.create_path('foo', tt.root)
1339
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1341
self.assertFileEqual('qux', 'tree2/foo')
1343
def test_create_from_tree_symlink(self):
1344
self.requireFeature(SymlinkFeature)
1345
tree1 = self.make_branch_and_tree('tree1')
1346
os.symlink('bar', 'tree1/foo')
1347
tree1.add('foo', 'foo-id')
1348
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1349
foo_trans_id = tt.create_path('foo', tt.root)
1350
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1352
self.assertEqual('bar', os.readlink('tree2/foo'))
1355
class TransformGroup(object):
1357
def __init__(self, dirname, root_id):
1360
self.wt = BzrDir.create_standalone_workingtree(dirname)
1361
self.wt.set_root_id(root_id)
1362
self.b = self.wt.branch
1363
self.tt = TreeTransform(self.wt)
1364
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1367
def conflict_text(tree, merge):
1368
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1369
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1372
class TestTransformMerge(TestCaseInTempDir):
1374
def test_text_merge(self):
1375
root_id = generate_ids.gen_root_id()
1376
base = TransformGroup("base", root_id)
1377
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1378
base.tt.new_file('b', base.root, 'b1', 'b')
1379
base.tt.new_file('c', base.root, 'c', 'c')
1380
base.tt.new_file('d', base.root, 'd', 'd')
1381
base.tt.new_file('e', base.root, 'e', 'e')
1382
base.tt.new_file('f', base.root, 'f', 'f')
1383
base.tt.new_directory('g', base.root, 'g')
1384
base.tt.new_directory('h', base.root, 'h')
1386
other = TransformGroup("other", root_id)
1387
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1388
other.tt.new_file('b', other.root, 'b2', 'b')
1389
other.tt.new_file('c', other.root, 'c2', 'c')
1390
other.tt.new_file('d', other.root, 'd', 'd')
1391
other.tt.new_file('e', other.root, 'e2', 'e')
1392
other.tt.new_file('f', other.root, 'f', 'f')
1393
other.tt.new_file('g', other.root, 'g', 'g')
1394
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1395
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1397
this = TransformGroup("this", root_id)
1398
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1399
this.tt.new_file('b', this.root, 'b', 'b')
1400
this.tt.new_file('c', this.root, 'c', 'c')
1401
this.tt.new_file('d', this.root, 'd2', 'd')
1402
this.tt.new_file('e', this.root, 'e2', 'e')
1403
this.tt.new_file('f', this.root, 'f', 'f')
1404
this.tt.new_file('g', this.root, 'g', 'g')
1405
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1406
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1408
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1411
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1412
# three-way text conflict
1413
self.assertEqual(this.wt.get_file('b').read(),
1414
conflict_text('b', 'b2'))
1416
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1418
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1419
# Ambigious clean merge
1420
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1422
self.assertEqual(this.wt.get_file('f').read(), 'f')
1423
# Correct correct results when THIS == OTHER
1424
self.assertEqual(this.wt.get_file('g').read(), 'g')
1425
# Text conflict when THIS & OTHER are text and BASE is dir
1426
self.assertEqual(this.wt.get_file('h').read(),
1427
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1428
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1430
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1432
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1433
self.assertEqual(this.wt.get_file('i').read(),
1434
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1435
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1437
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1439
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1440
modified = ['a', 'b', 'c', 'h', 'i']
1441
merge_modified = this.wt.merge_modified()
1442
self.assertSubset(merge_modified, modified)
1443
self.assertEqual(len(merge_modified), len(modified))
1444
file(this.wt.id2abspath('a'), 'wb').write('booga')
1446
merge_modified = this.wt.merge_modified()
1447
self.assertSubset(merge_modified, modified)
1448
self.assertEqual(len(merge_modified), len(modified))
1452
def test_file_merge(self):
1453
self.requireFeature(SymlinkFeature)
1454
root_id = generate_ids.gen_root_id()
1455
base = TransformGroup("BASE", root_id)
1456
this = TransformGroup("THIS", root_id)
1457
other = TransformGroup("OTHER", root_id)
1458
for tg in this, base, other:
1459
tg.tt.new_directory('a', tg.root, 'a')
1460
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1461
tg.tt.new_file('c', tg.root, 'c', 'c')
1462
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1463
targets = ((base, 'base-e', 'base-f', None, None),
1464
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1465
(other, 'other-e', None, 'other-g', 'other-h'))
1466
for tg, e_target, f_target, g_target, h_target in targets:
1467
for link, target in (('e', e_target), ('f', f_target),
1468
('g', g_target), ('h', h_target)):
1469
if target is not None:
1470
tg.tt.new_symlink(link, tg.root, target, link)
1472
for tg in this, base, other:
1474
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1475
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1476
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1477
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1478
for suffix in ('THIS', 'BASE', 'OTHER'):
1479
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1480
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1481
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1482
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1483
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1484
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1485
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1486
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1487
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1488
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1489
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1490
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1491
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1492
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1494
def test_filename_merge(self):
1495
root_id = generate_ids.gen_root_id()
1496
base = TransformGroup("BASE", root_id)
1497
this = TransformGroup("THIS", root_id)
1498
other = TransformGroup("OTHER", root_id)
1499
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1500
for t in [base, this, other]]
1501
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1502
for t in [base, this, other]]
1503
base.tt.new_directory('c', base_a, 'c')
1504
this.tt.new_directory('c1', this_a, 'c')
1505
other.tt.new_directory('c', other_b, 'c')
1507
base.tt.new_directory('d', base_a, 'd')
1508
this.tt.new_directory('d1', this_b, 'd')
1509
other.tt.new_directory('d', other_a, 'd')
1511
base.tt.new_directory('e', base_a, 'e')
1512
this.tt.new_directory('e', this_a, 'e')
1513
other.tt.new_directory('e1', other_b, 'e')
1515
base.tt.new_directory('f', base_a, 'f')
1516
this.tt.new_directory('f1', this_b, 'f')
1517
other.tt.new_directory('f1', other_b, 'f')
1519
for tg in [this, base, other]:
1521
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1522
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1523
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1524
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1525
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1527
def test_filename_merge_conflicts(self):
1528
root_id = generate_ids.gen_root_id()
1529
base = TransformGroup("BASE", root_id)
1530
this = TransformGroup("THIS", root_id)
1531
other = TransformGroup("OTHER", root_id)
1532
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1533
for t in [base, this, other]]
1534
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1535
for t in [base, this, other]]
1537
base.tt.new_file('g', base_a, 'g', 'g')
1538
other.tt.new_file('g1', other_b, 'g1', 'g')
1540
base.tt.new_file('h', base_a, 'h', 'h')
1541
this.tt.new_file('h1', this_b, 'h1', 'h')
1543
base.tt.new_file('i', base.root, 'i', 'i')
1544
other.tt.new_directory('i1', this_b, 'i')
1546
for tg in [this, base, other]:
1548
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1550
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1551
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1552
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1553
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1554
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1555
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1556
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1559
class TestBuildTree(tests.TestCaseWithTransport):
1561
def test_build_tree_with_symlinks(self):
1562
self.requireFeature(SymlinkFeature)
1564
a = BzrDir.create_standalone_workingtree('a')
1566
file('a/foo/bar', 'wb').write('contents')
1567
os.symlink('a/foo/bar', 'a/foo/baz')
1568
a.add(['foo', 'foo/bar', 'foo/baz'])
1569
a.commit('initial commit')
1570
b = BzrDir.create_standalone_workingtree('b')
1571
basis = a.basis_tree()
1573
self.addCleanup(basis.unlock)
1574
build_tree(basis, b)
1575
self.assertIs(os.path.isdir('b/foo'), True)
1576
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1577
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1579
def test_build_with_references(self):
1580
tree = self.make_branch_and_tree('source',
1581
format='dirstate-with-subtree')
1582
subtree = self.make_branch_and_tree('source/subtree',
1583
format='dirstate-with-subtree')
1584
tree.add_reference(subtree)
1585
tree.commit('a revision')
1586
tree.branch.create_checkout('target')
1587
self.failUnlessExists('target')
1588
self.failUnlessExists('target/subtree')
1590
def test_file_conflict_handling(self):
1591
"""Ensure that when building trees, conflict handling is done"""
1592
source = self.make_branch_and_tree('source')
1593
target = self.make_branch_and_tree('target')
1594
self.build_tree(['source/file', 'target/file'])
1595
source.add('file', 'new-file')
1596
source.commit('added file')
1597
build_tree(source.basis_tree(), target)
1598
self.assertEqual([DuplicateEntry('Moved existing file to',
1599
'file.moved', 'file', None, 'new-file')],
1601
target2 = self.make_branch_and_tree('target2')
1602
target_file = file('target2/file', 'wb')
1604
source_file = file('source/file', 'rb')
1606
target_file.write(source_file.read())
1611
build_tree(source.basis_tree(), target2)
1612
self.assertEqual([], target2.conflicts())
1614
def test_symlink_conflict_handling(self):
1615
"""Ensure that when building trees, conflict handling is done"""
1616
self.requireFeature(SymlinkFeature)
1617
source = self.make_branch_and_tree('source')
1618
os.symlink('foo', 'source/symlink')
1619
source.add('symlink', 'new-symlink')
1620
source.commit('added file')
1621
target = self.make_branch_and_tree('target')
1622
os.symlink('bar', 'target/symlink')
1623
build_tree(source.basis_tree(), target)
1624
self.assertEqual([DuplicateEntry('Moved existing file to',
1625
'symlink.moved', 'symlink', None, 'new-symlink')],
1627
target = self.make_branch_and_tree('target2')
1628
os.symlink('foo', 'target2/symlink')
1629
build_tree(source.basis_tree(), target)
1630
self.assertEqual([], target.conflicts())
1632
def test_directory_conflict_handling(self):
1633
"""Ensure that when building trees, conflict handling is done"""
1634
source = self.make_branch_and_tree('source')
1635
target = self.make_branch_and_tree('target')
1636
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1637
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1638
source.commit('added file')
1639
build_tree(source.basis_tree(), target)
1640
self.assertEqual([], target.conflicts())
1641
self.failUnlessExists('target/dir1/file')
1643
# Ensure contents are merged
1644
target = self.make_branch_and_tree('target2')
1645
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1646
build_tree(source.basis_tree(), target)
1647
self.assertEqual([], target.conflicts())
1648
self.failUnlessExists('target2/dir1/file2')
1649
self.failUnlessExists('target2/dir1/file')
1651
# Ensure new contents are suppressed for existing branches
1652
target = self.make_branch_and_tree('target3')
1653
self.make_branch('target3/dir1')
1654
self.build_tree(['target3/dir1/file2'])
1655
build_tree(source.basis_tree(), target)
1656
self.failIfExists('target3/dir1/file')
1657
self.failUnlessExists('target3/dir1/file2')
1658
self.failUnlessExists('target3/dir1.diverted/file')
1659
self.assertEqual([DuplicateEntry('Diverted to',
1660
'dir1.diverted', 'dir1', 'new-dir1', None)],
1663
target = self.make_branch_and_tree('target4')
1664
self.build_tree(['target4/dir1/'])
1665
self.make_branch('target4/dir1/file')
1666
build_tree(source.basis_tree(), target)
1667
self.failUnlessExists('target4/dir1/file')
1668
self.assertEqual('directory', file_kind('target4/dir1/file'))
1669
self.failUnlessExists('target4/dir1/file.diverted')
1670
self.assertEqual([DuplicateEntry('Diverted to',
1671
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1674
def test_mixed_conflict_handling(self):
1675
"""Ensure that when building trees, conflict handling is done"""
1676
source = self.make_branch_and_tree('source')
1677
target = self.make_branch_and_tree('target')
1678
self.build_tree(['source/name', 'target/name/'])
1679
source.add('name', 'new-name')
1680
source.commit('added file')
1681
build_tree(source.basis_tree(), target)
1682
self.assertEqual([DuplicateEntry('Moved existing file to',
1683
'name.moved', 'name', None, 'new-name')], target.conflicts())
1685
def test_raises_in_populated(self):
1686
source = self.make_branch_and_tree('source')
1687
self.build_tree(['source/name'])
1689
source.commit('added name')
1690
target = self.make_branch_and_tree('target')
1691
self.build_tree(['target/name'])
1693
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1694
build_tree, source.basis_tree(), target)
1696
def test_build_tree_rename_count(self):
1697
source = self.make_branch_and_tree('source')
1698
self.build_tree(['source/file1', 'source/dir1/'])
1699
source.add(['file1', 'dir1'])
1700
source.commit('add1')
1701
target1 = self.make_branch_and_tree('target1')
1702
transform_result = build_tree(source.basis_tree(), target1)
1703
self.assertEqual(2, transform_result.rename_count)
1705
self.build_tree(['source/dir1/file2'])
1706
source.add(['dir1/file2'])
1707
source.commit('add3')
1708
target2 = self.make_branch_and_tree('target2')
1709
transform_result = build_tree(source.basis_tree(), target2)
1710
# children of non-root directories should not be renamed
1711
self.assertEqual(2, transform_result.rename_count)
1713
def create_ab_tree(self):
1714
"""Create a committed test tree with two files"""
1715
source = self.make_branch_and_tree('source')
1716
self.build_tree_contents([('source/file1', 'A')])
1717
self.build_tree_contents([('source/file2', 'B')])
1718
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1719
source.commit('commit files')
1721
self.addCleanup(source.unlock)
1724
def test_build_tree_accelerator_tree(self):
1725
source = self.create_ab_tree()
1726
self.build_tree_contents([('source/file2', 'C')])
1728
real_source_get_file = source.get_file
1729
def get_file(file_id, path=None):
1730
calls.append(file_id)
1731
return real_source_get_file(file_id, path)
1732
source.get_file = get_file
1733
target = self.make_branch_and_tree('target')
1734
revision_tree = source.basis_tree()
1735
revision_tree.lock_read()
1736
self.addCleanup(revision_tree.unlock)
1737
build_tree(revision_tree, target, source)
1738
self.assertEqual(['file1-id'], calls)
1740
self.addCleanup(target.unlock)
1741
self.assertEqual([], list(target.iter_changes(revision_tree)))
1743
def test_build_tree_accelerator_tree_missing_file(self):
1744
source = self.create_ab_tree()
1745
os.unlink('source/file1')
1746
source.remove(['file2'])
1747
target = self.make_branch_and_tree('target')
1748
revision_tree = source.basis_tree()
1749
revision_tree.lock_read()
1750
self.addCleanup(revision_tree.unlock)
1751
build_tree(revision_tree, target, source)
1753
self.addCleanup(target.unlock)
1754
self.assertEqual([], list(target.iter_changes(revision_tree)))
1756
def test_build_tree_accelerator_wrong_kind(self):
1757
self.requireFeature(SymlinkFeature)
1758
source = self.make_branch_and_tree('source')
1759
self.build_tree_contents([('source/file1', '')])
1760
self.build_tree_contents([('source/file2', '')])
1761
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1762
source.commit('commit files')
1763
os.unlink('source/file2')
1764
self.build_tree_contents([('source/file2/', 'C')])
1765
os.unlink('source/file1')
1766
os.symlink('file2', 'source/file1')
1768
real_source_get_file = source.get_file
1769
def get_file(file_id, path=None):
1770
calls.append(file_id)
1771
return real_source_get_file(file_id, path)
1772
source.get_file = get_file
1773
target = self.make_branch_and_tree('target')
1774
revision_tree = source.basis_tree()
1775
revision_tree.lock_read()
1776
self.addCleanup(revision_tree.unlock)
1777
build_tree(revision_tree, target, source)
1778
self.assertEqual([], calls)
1780
self.addCleanup(target.unlock)
1781
self.assertEqual([], list(target.iter_changes(revision_tree)))
1783
def test_build_tree_hardlink(self):
1784
self.requireFeature(HardlinkFeature)
1785
source = self.create_ab_tree()
1786
target = self.make_branch_and_tree('target')
1787
revision_tree = source.basis_tree()
1788
revision_tree.lock_read()
1789
self.addCleanup(revision_tree.unlock)
1790
build_tree(revision_tree, target, source, hardlink=True)
1792
self.addCleanup(target.unlock)
1793
self.assertEqual([], list(target.iter_changes(revision_tree)))
1794
source_stat = os.stat('source/file1')
1795
target_stat = os.stat('target/file1')
1796
self.assertEqual(source_stat, target_stat)
1798
# Explicitly disallowing hardlinks should prevent them.
1799
target2 = self.make_branch_and_tree('target2')
1800
build_tree(revision_tree, target2, source, hardlink=False)
1802
self.addCleanup(target2.unlock)
1803
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1804
source_stat = os.stat('source/file1')
1805
target2_stat = os.stat('target2/file1')
1806
self.assertNotEqual(source_stat, target2_stat)
1808
def test_build_tree_accelerator_tree_moved(self):
1809
source = self.make_branch_and_tree('source')
1810
self.build_tree_contents([('source/file1', 'A')])
1811
source.add(['file1'], ['file1-id'])
1812
source.commit('commit files')
1813
source.rename_one('file1', 'file2')
1815
self.addCleanup(source.unlock)
1816
target = self.make_branch_and_tree('target')
1817
revision_tree = source.basis_tree()
1818
revision_tree.lock_read()
1819
self.addCleanup(revision_tree.unlock)
1820
build_tree(revision_tree, target, source)
1822
self.addCleanup(target.unlock)
1823
self.assertEqual([], list(target.iter_changes(revision_tree)))
1825
def test_build_tree_hardlinks_preserve_execute(self):
1826
self.requireFeature(HardlinkFeature)
1827
source = self.create_ab_tree()
1828
tt = TreeTransform(source)
1829
trans_id = tt.trans_id_tree_file_id('file1-id')
1830
tt.set_executability(True, trans_id)
1832
self.assertTrue(source.is_executable('file1-id'))
1833
target = self.make_branch_and_tree('target')
1834
revision_tree = source.basis_tree()
1835
revision_tree.lock_read()
1836
self.addCleanup(revision_tree.unlock)
1837
build_tree(revision_tree, target, source, hardlink=True)
1839
self.addCleanup(target.unlock)
1840
self.assertEqual([], list(target.iter_changes(revision_tree)))
1841
self.assertTrue(source.is_executable('file1-id'))
1843
def test_case_insensitive_build_tree_inventory(self):
1844
if (not tests.CaseInsensitiveFilesystemFeature.available()
1845
or not tests.CaseInsCasePresFilenameFeature.available()):
1846
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1847
source = self.make_branch_and_tree('source')
1848
self.build_tree(['source/file', 'source/FILE'])
1849
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1850
source.commit('added files')
1851
# Don't try this at home, kids!
1852
# Force the tree to report that it is case insensitive
1853
target = self.make_branch_and_tree('target')
1854
target.case_sensitive = False
1855
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1856
self.assertEqual('file.moved', target.id2path('lower-id'))
1857
self.assertEqual('FILE', target.id2path('upper-id'))
1860
class MockTransform(object):
1862
def has_named_child(self, by_parent, parent_id, name):
1863
for child_id in by_parent[parent_id]:
1867
elif name == "name.~%s~" % child_id:
1872
class MockEntry(object):
1874
object.__init__(self)
1878
class TestGetBackupName(TestCase):
1879
def test_get_backup_name(self):
1880
tt = MockTransform()
1881
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1882
self.assertEqual(name, 'name.~1~')
1883
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1884
self.assertEqual(name, 'name.~2~')
1885
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1886
self.assertEqual(name, 'name.~1~')
1887
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1888
self.assertEqual(name, 'name.~1~')
1889
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1890
self.assertEqual(name, 'name.~4~')
1893
class TestFileMover(tests.TestCaseWithTransport):
1895
def test_file_mover(self):
1896
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1897
mover = _FileMover()
1898
mover.rename('a', 'q')
1899
self.failUnlessExists('q')
1900
self.failIfExists('a')
1901
self.failUnlessExists('q/b')
1902
self.failUnlessExists('c')
1903
self.failUnlessExists('c/d')
1905
def test_pre_delete_rollback(self):
1906
self.build_tree(['a/'])
1907
mover = _FileMover()
1908
mover.pre_delete('a', 'q')
1909
self.failUnlessExists('q')
1910
self.failIfExists('a')
1912
self.failIfExists('q')
1913
self.failUnlessExists('a')
1915
def test_apply_deletions(self):
1916
self.build_tree(['a/', 'b/'])
1917
mover = _FileMover()
1918
mover.pre_delete('a', 'q')
1919
mover.pre_delete('b', 'r')
1920
self.failUnlessExists('q')
1921
self.failUnlessExists('r')
1922
self.failIfExists('a')
1923
self.failIfExists('b')
1924
mover.apply_deletions()
1925
self.failIfExists('q')
1926
self.failIfExists('r')
1927
self.failIfExists('a')
1928
self.failIfExists('b')
1930
def test_file_mover_rollback(self):
1931
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1932
mover = _FileMover()
1933
mover.rename('c/d', 'c/f')
1934
mover.rename('c/e', 'c/d')
1936
mover.rename('a', 'c')
1937
except errors.FileExists, e:
1939
self.failUnlessExists('a')
1940
self.failUnlessExists('c/d')
1943
class Bogus(Exception):
1947
class TestTransformRollback(tests.TestCaseWithTransport):
1949
class ExceptionFileMover(_FileMover):
1951
def __init__(self, bad_source=None, bad_target=None):
1952
_FileMover.__init__(self)
1953
self.bad_source = bad_source
1954
self.bad_target = bad_target
1956
def rename(self, source, target):
1957
if (self.bad_source is not None and
1958
source.endswith(self.bad_source)):
1960
elif (self.bad_target is not None and
1961
target.endswith(self.bad_target)):
1964
_FileMover.rename(self, source, target)
1966
def test_rollback_rename(self):
1967
tree = self.make_branch_and_tree('.')
1968
self.build_tree(['a/', 'a/b'])
1969
tt = TreeTransform(tree)
1970
self.addCleanup(tt.finalize)
1971
a_id = tt.trans_id_tree_path('a')
1972
tt.adjust_path('c', tt.root, a_id)
1973
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1974
self.assertRaises(Bogus, tt.apply,
1975
_mover=self.ExceptionFileMover(bad_source='a'))
1976
self.failUnlessExists('a')
1977
self.failUnlessExists('a/b')
1979
self.failUnlessExists('c')
1980
self.failUnlessExists('c/d')
1982
def test_rollback_rename_into_place(self):
1983
tree = self.make_branch_and_tree('.')
1984
self.build_tree(['a/', 'a/b'])
1985
tt = TreeTransform(tree)
1986
self.addCleanup(tt.finalize)
1987
a_id = tt.trans_id_tree_path('a')
1988
tt.adjust_path('c', tt.root, a_id)
1989
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1990
self.assertRaises(Bogus, tt.apply,
1991
_mover=self.ExceptionFileMover(bad_target='c/d'))
1992
self.failUnlessExists('a')
1993
self.failUnlessExists('a/b')
1995
self.failUnlessExists('c')
1996
self.failUnlessExists('c/d')
1998
def test_rollback_deletion(self):
1999
tree = self.make_branch_and_tree('.')
2000
self.build_tree(['a/', 'a/b'])
2001
tt = TreeTransform(tree)
2002
self.addCleanup(tt.finalize)
2003
a_id = tt.trans_id_tree_path('a')
2004
tt.delete_contents(a_id)
2005
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2006
self.assertRaises(Bogus, tt.apply,
2007
_mover=self.ExceptionFileMover(bad_target='d'))
2008
self.failUnlessExists('a')
2009
self.failUnlessExists('a/b')
2011
def test_resolve_no_parent(self):
2012
wt = self.make_branch_and_tree('.')
2013
tt = TreeTransform(wt)
2014
self.addCleanup(tt.finalize)
2015
parent = tt.trans_id_file_id('parent-id')
2016
tt.new_file('file', parent, 'Contents')
2017
resolve_conflicts(tt)
2020
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2021
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2023
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2024
('', ''), ('directory', 'directory'), (False, None))
2027
class TestTransformPreview(tests.TestCaseWithTransport):
2029
def create_tree(self):
2030
tree = self.make_branch_and_tree('.')
2031
self.build_tree_contents([('a', 'content 1')])
2032
tree.add('a', 'a-id')
2033
tree.commit('rev1', rev_id='rev1')
2034
return tree.branch.repository.revision_tree('rev1')
2036
def get_empty_preview(self):
2037
repository = self.make_repository('repo')
2038
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2039
preview = TransformPreview(tree)
2040
self.addCleanup(preview.finalize)
2043
def test_transform_preview(self):
2044
revision_tree = self.create_tree()
2045
preview = TransformPreview(revision_tree)
2046
self.addCleanup(preview.finalize)
2048
def test_transform_preview_tree(self):
2049
revision_tree = self.create_tree()
2050
preview = TransformPreview(revision_tree)
2051
self.addCleanup(preview.finalize)
2052
preview.get_preview_tree()
2054
def test_transform_new_file(self):
2055
revision_tree = self.create_tree()
2056
preview = TransformPreview(revision_tree)
2057
self.addCleanup(preview.finalize)
2058
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2059
preview_tree = preview.get_preview_tree()
2060
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2062
preview_tree.get_file('file2-id').read(), 'content B\n')
2064
def test_diff_preview_tree(self):
2065
revision_tree = self.create_tree()
2066
preview = TransformPreview(revision_tree)
2067
self.addCleanup(preview.finalize)
2068
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2069
preview_tree = preview.get_preview_tree()
2071
show_diff_trees(revision_tree, preview_tree, out)
2072
lines = out.getvalue().splitlines()
2073
self.assertEqual(lines[0], "=== added file 'file2'")
2074
# 3 lines of diff administrivia
2075
self.assertEqual(lines[4], "+content B")
2077
def test_transform_conflicts(self):
2078
revision_tree = self.create_tree()
2079
preview = TransformPreview(revision_tree)
2080
self.addCleanup(preview.finalize)
2081
preview.new_file('a', preview.root, 'content 2')
2082
resolve_conflicts(preview)
2083
trans_id = preview.trans_id_file_id('a-id')
2084
self.assertEqual('a.moved', preview.final_name(trans_id))
2086
def get_tree_and_preview_tree(self):
2087
revision_tree = self.create_tree()
2088
preview = TransformPreview(revision_tree)
2089
self.addCleanup(preview.finalize)
2090
a_trans_id = preview.trans_id_file_id('a-id')
2091
preview.delete_contents(a_trans_id)
2092
preview.create_file('b content', a_trans_id)
2093
preview_tree = preview.get_preview_tree()
2094
return revision_tree, preview_tree
2096
def test_iter_changes(self):
2097
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2098
root = revision_tree.inventory.root.file_id
2099
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2100
(root, root), ('a', 'a'), ('file', 'file'),
2102
list(preview_tree.iter_changes(revision_tree)))
2104
def test_include_unchanged_succeeds(self):
2105
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2106
changes = preview_tree.iter_changes(revision_tree,
2107
include_unchanged=True)
2108
root = revision_tree.inventory.root.file_id
2110
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2112
def test_specific_files(self):
2113
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2114
changes = preview_tree.iter_changes(revision_tree,
2115
specific_files=[''])
2116
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2118
def test_want_unversioned(self):
2119
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2120
changes = preview_tree.iter_changes(revision_tree,
2121
want_unversioned=True)
2122
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2124
def test_ignore_extra_trees_no_specific_files(self):
2125
# extra_trees is harmless without specific_files, so we'll silently
2126
# accept it, even though we won't use it.
2127
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2128
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2130
def test_ignore_require_versioned_no_specific_files(self):
2131
# require_versioned is meaningless without specific_files.
2132
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2133
preview_tree.iter_changes(revision_tree, require_versioned=False)
2135
def test_ignore_pb(self):
2136
# pb could be supported, but TT.iter_changes doesn't support it.
2137
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2138
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2140
def test_kind(self):
2141
revision_tree = self.create_tree()
2142
preview = TransformPreview(revision_tree)
2143
self.addCleanup(preview.finalize)
2144
preview.new_file('file', preview.root, 'contents', 'file-id')
2145
preview.new_directory('directory', preview.root, 'dir-id')
2146
preview_tree = preview.get_preview_tree()
2147
self.assertEqual('file', preview_tree.kind('file-id'))
2148
self.assertEqual('directory', preview_tree.kind('dir-id'))
2150
def test_get_file_mtime(self):
2151
preview = self.get_empty_preview()
2152
file_trans_id = preview.new_file('file', preview.root, 'contents',
2154
limbo_path = preview._limbo_name(file_trans_id)
2155
preview_tree = preview.get_preview_tree()
2156
self.assertEqual(os.stat(limbo_path).st_mtime,
2157
preview_tree.get_file_mtime('file-id'))
2159
def test_get_file(self):
2160
preview = self.get_empty_preview()
2161
preview.new_file('file', preview.root, 'contents', 'file-id')
2162
preview_tree = preview.get_preview_tree()
2163
tree_file = preview_tree.get_file('file-id')
2165
self.assertEqual('contents', tree_file.read())
2169
def test_get_symlink_target(self):
2170
self.requireFeature(SymlinkFeature)
2171
preview = self.get_empty_preview()
2172
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2173
preview_tree = preview.get_preview_tree()
2174
self.assertEqual('target',
2175
preview_tree.get_symlink_target('symlink-id'))
2177
def test_all_file_ids(self):
2178
tree = self.make_branch_and_tree('tree')
2179
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2180
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2181
preview = TransformPreview(tree)
2182
self.addCleanup(preview.finalize)
2183
preview.unversion_file(preview.trans_id_file_id('b-id'))
2184
c_trans_id = preview.trans_id_file_id('c-id')
2185
preview.unversion_file(c_trans_id)
2186
preview.version_file('c-id', c_trans_id)
2187
preview_tree = preview.get_preview_tree()
2188
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2189
preview_tree.all_file_ids())
2191
def test_path2id_deleted_unchanged(self):
2192
tree = self.make_branch_and_tree('tree')
2193
self.build_tree(['tree/unchanged', 'tree/deleted'])
2194
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2195
preview = TransformPreview(tree)
2196
self.addCleanup(preview.finalize)
2197
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2198
preview_tree = preview.get_preview_tree()
2199
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2200
self.assertIs(None, preview_tree.path2id('deleted'))
2202
def test_path2id_created(self):
2203
tree = self.make_branch_and_tree('tree')
2204
self.build_tree(['tree/unchanged'])
2205
tree.add(['unchanged'], ['unchanged-id'])
2206
preview = TransformPreview(tree)
2207
self.addCleanup(preview.finalize)
2208
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2209
'contents', 'new-id')
2210
preview_tree = preview.get_preview_tree()
2211
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2213
def test_path2id_moved(self):
2214
tree = self.make_branch_and_tree('tree')
2215
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2216
tree.add(['old_parent', 'old_parent/child'],
2217
['old_parent-id', 'child-id'])
2218
preview = TransformPreview(tree)
2219
self.addCleanup(preview.finalize)
2220
new_parent = preview.new_directory('new_parent', preview.root,
2222
preview.adjust_path('child', new_parent,
2223
preview.trans_id_file_id('child-id'))
2224
preview_tree = preview.get_preview_tree()
2225
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2226
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2228
def test_path2id_renamed_parent(self):
2229
tree = self.make_branch_and_tree('tree')
2230
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2231
tree.add(['old_name', 'old_name/child'],
2232
['parent-id', 'child-id'])
2233
preview = TransformPreview(tree)
2234
self.addCleanup(preview.finalize)
2235
preview.adjust_path('new_name', preview.root,
2236
preview.trans_id_file_id('parent-id'))
2237
preview_tree = preview.get_preview_tree()
2238
self.assertIs(None, preview_tree.path2id('old_name/child'))
2239
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2241
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2242
preview_tree = tt.get_preview_tree()
2243
preview_result = list(preview_tree.iter_entries_by_dir(
2247
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2248
self.assertEqual(actual_result, preview_result)
2250
def test_iter_entries_by_dir_new(self):
2251
tree = self.make_branch_and_tree('tree')
2252
tt = TreeTransform(tree)
2253
tt.new_file('new', tt.root, 'contents', 'new-id')
2254
self.assertMatchingIterEntries(tt)
2256
def test_iter_entries_by_dir_deleted(self):
2257
tree = self.make_branch_and_tree('tree')
2258
self.build_tree(['tree/deleted'])
2259
tree.add('deleted', 'deleted-id')
2260
tt = TreeTransform(tree)
2261
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2262
self.assertMatchingIterEntries(tt)
2264
def test_iter_entries_by_dir_unversioned(self):
2265
tree = self.make_branch_and_tree('tree')
2266
self.build_tree(['tree/removed'])
2267
tree.add('removed', 'removed-id')
2268
tt = TreeTransform(tree)
2269
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2270
self.assertMatchingIterEntries(tt)
2272
def test_iter_entries_by_dir_moved(self):
2273
tree = self.make_branch_and_tree('tree')
2274
self.build_tree(['tree/moved', 'tree/new_parent/'])
2275
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2276
tt = TreeTransform(tree)
2277
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2278
tt.trans_id_file_id('moved-id'))
2279
self.assertMatchingIterEntries(tt)
2281
def test_iter_entries_by_dir_specific_file_ids(self):
2282
tree = self.make_branch_and_tree('tree')
2283
tree.set_root_id('tree-root-id')
2284
self.build_tree(['tree/parent/', 'tree/parent/child'])
2285
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2286
tt = TreeTransform(tree)
2287
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2289
def test_symlink_content_summary(self):
2290
self.requireFeature(SymlinkFeature)
2291
preview = self.get_empty_preview()
2292
preview.new_symlink('path', preview.root, 'target', 'path-id')
2293
summary = preview.get_preview_tree().path_content_summary('path')
2294
self.assertEqual(('symlink', None, None, 'target'), summary)
2296
def test_missing_content_summary(self):
2297
preview = self.get_empty_preview()
2298
summary = preview.get_preview_tree().path_content_summary('path')
2299
self.assertEqual(('missing', None, None, None), summary)
2301
def test_deleted_content_summary(self):
2302
tree = self.make_branch_and_tree('tree')
2303
self.build_tree(['tree/path/'])
2305
preview = TransformPreview(tree)
2306
self.addCleanup(preview.finalize)
2307
preview.delete_contents(preview.trans_id_tree_path('path'))
2308
summary = preview.get_preview_tree().path_content_summary('path')
2309
self.assertEqual(('missing', None, None, None), summary)
2311
def test_file_content_summary_executable(self):
2312
if not osutils.supports_executable():
2313
raise TestNotApplicable()
2314
preview = self.get_empty_preview()
2315
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2316
preview.set_executability(True, path_id)
2317
summary = preview.get_preview_tree().path_content_summary('path')
2318
self.assertEqual(4, len(summary))
2319
self.assertEqual('file', summary[0])
2320
# size must be known
2321
self.assertEqual(len('contents'), summary[1])
2323
self.assertEqual(True, summary[2])
2324
# will not have hash (not cheap to determine)
2325
self.assertIs(None, summary[3])
2327
def test_change_executability(self):
2328
if not osutils.supports_executable():
2329
raise TestNotApplicable()
2330
tree = self.make_branch_and_tree('tree')
2331
self.build_tree(['tree/path'])
2333
preview = TransformPreview(tree)
2334
self.addCleanup(preview.finalize)
2335
path_id = preview.trans_id_tree_path('path')
2336
preview.set_executability(True, path_id)
2337
summary = preview.get_preview_tree().path_content_summary('path')
2338
self.assertEqual(True, summary[2])
2340
def test_file_content_summary_non_exec(self):
2341
preview = self.get_empty_preview()
2342
preview.new_file('path', preview.root, 'contents', 'path-id')
2343
summary = preview.get_preview_tree().path_content_summary('path')
2344
self.assertEqual(4, len(summary))
2345
self.assertEqual('file', summary[0])
2346
# size must be known
2347
self.assertEqual(len('contents'), summary[1])
2349
if osutils.supports_executable():
2350
self.assertEqual(False, summary[2])
2352
self.assertEqual(None, summary[2])
2353
# will not have hash (not cheap to determine)
2354
self.assertIs(None, summary[3])
2356
def test_dir_content_summary(self):
2357
preview = self.get_empty_preview()
2358
preview.new_directory('path', preview.root, 'path-id')
2359
summary = preview.get_preview_tree().path_content_summary('path')
2360
self.assertEqual(('directory', None, None, None), summary)
2362
def test_tree_content_summary(self):
2363
preview = self.get_empty_preview()
2364
path = preview.new_directory('path', preview.root, 'path-id')
2365
preview.set_tree_reference('rev-1', path)
2366
summary = preview.get_preview_tree().path_content_summary('path')
2367
self.assertEqual(4, len(summary))
2368
self.assertEqual('tree-reference', summary[0])
2370
def test_annotate(self):
2371
tree = self.make_branch_and_tree('tree')
2372
self.build_tree_contents([('tree/file', 'a\n')])
2373
tree.add('file', 'file-id')
2374
tree.commit('a', rev_id='one')
2375
self.build_tree_contents([('tree/file', 'a\nb\n')])
2376
preview = TransformPreview(tree)
2377
self.addCleanup(preview.finalize)
2378
file_trans_id = preview.trans_id_file_id('file-id')
2379
preview.delete_contents(file_trans_id)
2380
preview.create_file('a\nb\nc\n', file_trans_id)
2381
preview_tree = preview.get_preview_tree()
2387
annotation = preview_tree.annotate_iter('file-id', 'me:')
2388
self.assertEqual(expected, annotation)
2390
def test_annotate_missing(self):
2391
preview = self.get_empty_preview()
2392
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2393
preview_tree = preview.get_preview_tree()
2399
annotation = preview_tree.annotate_iter('file-id', 'me:')
2400
self.assertEqual(expected, annotation)
2402
def test_annotate_rename(self):
2403
tree = self.make_branch_and_tree('tree')
2404
self.build_tree_contents([('tree/file', 'a\n')])
2405
tree.add('file', 'file-id')
2406
tree.commit('a', rev_id='one')
2407
preview = TransformPreview(tree)
2408
self.addCleanup(preview.finalize)
2409
file_trans_id = preview.trans_id_file_id('file-id')
2410
preview.adjust_path('newname', preview.root, file_trans_id)
2411
preview_tree = preview.get_preview_tree()
2415
annotation = preview_tree.annotate_iter('file-id', 'me:')
2416
self.assertEqual(expected, annotation)
2418
def test_annotate_deleted(self):
2419
tree = self.make_branch_and_tree('tree')
2420
self.build_tree_contents([('tree/file', 'a\n')])
2421
tree.add('file', 'file-id')
2422
tree.commit('a', rev_id='one')
2423
self.build_tree_contents([('tree/file', 'a\nb\n')])
2424
preview = TransformPreview(tree)
2425
self.addCleanup(preview.finalize)
2426
file_trans_id = preview.trans_id_file_id('file-id')
2427
preview.delete_contents(file_trans_id)
2428
preview_tree = preview.get_preview_tree()
2429
annotation = preview_tree.annotate_iter('file-id', 'me:')
2430
self.assertIs(None, annotation)
2432
def test_stored_kind(self):
2433
preview = self.get_empty_preview()
2434
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2435
preview_tree = preview.get_preview_tree()
2436
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2438
def test_is_executable(self):
2439
preview = self.get_empty_preview()
2440
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2441
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2442
preview_tree = preview.get_preview_tree()
2443
self.assertEqual(True, preview_tree.is_executable('file-id'))
2445
def test_get_set_parent_ids(self):
2446
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2447
self.assertEqual([], preview_tree.get_parent_ids())
2448
preview_tree.set_parent_ids(['rev-1'])
2449
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2451
def test_plan_file_merge(self):
2452
work_a = self.make_branch_and_tree('wta')
2453
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2454
work_a.add('file', 'file-id')
2455
base_id = work_a.commit('base version')
2456
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2457
preview = TransformPreview(work_a)
2458
self.addCleanup(preview.finalize)
2459
trans_id = preview.trans_id_file_id('file-id')
2460
preview.delete_contents(trans_id)
2461
preview.create_file('b\nc\nd\ne\n', trans_id)
2462
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2463
tree_a = preview.get_preview_tree()
2464
tree_a.set_parent_ids([base_id])
2466
('killed-a', 'a\n'),
2467
('killed-b', 'b\n'),
2468
('unchanged', 'c\n'),
2469
('unchanged', 'd\n'),
2472
], list(tree_a.plan_file_merge('file-id', tree_b)))
2474
def test_plan_file_merge_revision_tree(self):
2475
work_a = self.make_branch_and_tree('wta')
2476
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2477
work_a.add('file', 'file-id')
2478
base_id = work_a.commit('base version')
2479
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2480
preview = TransformPreview(work_a.basis_tree())
2481
self.addCleanup(preview.finalize)
2482
trans_id = preview.trans_id_file_id('file-id')
2483
preview.delete_contents(trans_id)
2484
preview.create_file('b\nc\nd\ne\n', trans_id)
2485
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2486
tree_a = preview.get_preview_tree()
2487
tree_a.set_parent_ids([base_id])
2489
('killed-a', 'a\n'),
2490
('killed-b', 'b\n'),
2491
('unchanged', 'c\n'),
2492
('unchanged', 'd\n'),
2495
], list(tree_a.plan_file_merge('file-id', tree_b)))
2497
def test_walkdirs(self):
2498
preview = self.get_empty_preview()
2499
preview.version_file('tree-root', preview.root)
2500
preview_tree = preview.get_preview_tree()
2501
file_trans_id = preview.new_file('a', preview.root, 'contents',
2503
expected = [(('', 'tree-root'),
2504
[('a', 'a', 'file', None, 'a-id', 'file')])]
2505
self.assertEqual(expected, list(preview_tree.walkdirs()))
2507
def test_extras(self):
2508
work_tree = self.make_branch_and_tree('tree')
2509
self.build_tree(['tree/removed-file', 'tree/existing-file',
2510
'tree/not-removed-file'])
2511
work_tree.add(['removed-file', 'not-removed-file'])
2512
preview = TransformPreview(work_tree)
2513
self.addCleanup(preview.finalize)
2514
preview.new_file('new-file', preview.root, 'contents')
2515
preview.new_file('new-versioned-file', preview.root, 'contents',
2517
tree = preview.get_preview_tree()
2518
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2519
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2522
def test_merge_into_preview(self):
2523
work_tree = self.make_branch_and_tree('tree')
2524
self.build_tree_contents([('tree/file','b\n')])
2525
work_tree.add('file', 'file-id')
2526
work_tree.commit('first commit')
2527
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2528
self.build_tree_contents([('child/file','b\nc\n')])
2529
child_tree.commit('child commit')
2530
child_tree.lock_write()
2531
self.addCleanup(child_tree.unlock)
2532
work_tree.lock_write()
2533
self.addCleanup(work_tree.unlock)
2534
preview = TransformPreview(work_tree)
2535
self.addCleanup(preview.finalize)
2536
preview_tree = preview.get_preview_tree()
2537
file_trans_id = preview.trans_id_file_id('file-id')
2538
preview.delete_contents(file_trans_id)
2539
preview.create_file('a\nb\n', file_trans_id)
2540
pb = progress.DummyProgress()
2541
merger = Merger.from_revision_ids(pb, preview_tree,
2542
child_tree.branch.last_revision(),
2543
other_branch=child_tree.branch,
2544
tree_branch=work_tree.branch)
2545
merger.merge_type = Merge3Merger
2546
tt = merger.make_merger().make_preview_transform()
2547
self.addCleanup(tt.finalize)
2548
final_tree = tt.get_preview_tree()
2549
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2551
def test_merge_preview_into_workingtree(self):
2552
tree = self.make_branch_and_tree('tree')
2553
tt = TransformPreview(tree)
2554
self.addCleanup(tt.finalize)
2555
tt.new_file('name', tt.root, 'content', 'file-id')
2556
tree2 = self.make_branch_and_tree('tree2')
2557
pb = progress.DummyProgress()
2558
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2559
pb, tree.basis_tree())
2560
merger.merge_type = Merge3Merger
2563
def test_merge_preview_into_workingtree_handles_conflicts(self):
2564
tree = self.make_branch_and_tree('tree')
2565
self.build_tree_contents([('tree/foo', 'bar')])
2566
tree.add('foo', 'foo-id')
2568
tt = TransformPreview(tree)
2569
self.addCleanup(tt.finalize)
2570
trans_id = tt.trans_id_file_id('foo-id')
2571
tt.delete_contents(trans_id)
2572
tt.create_file('baz', trans_id)
2573
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2574
self.build_tree_contents([('tree2/foo', 'qux')])
2575
pb = progress.DummyProgress()
2576
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2577
pb, tree.basis_tree())
2578
merger.merge_type = Merge3Merger
2581
def test_is_executable(self):
2582
tree = self.make_branch_and_tree('tree')
2583
preview = TransformPreview(tree)
2584
self.addCleanup(preview.finalize)
2585
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2586
preview_tree = preview.get_preview_tree()
2587
self.assertEqual(False, preview_tree.is_executable('baz-id',
2589
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2592
class FakeSerializer(object):
2593
"""Serializer implementation that simply returns the input.
2595
The input is returned in the order used by pack.ContainerPushParser.
2598
def bytes_record(bytes, names):
2602
class TestSerializeTransform(tests.TestCaseWithTransport):
2604
_test_needs_features = [tests.UnicodeFilenameFeature]
2606
def get_preview(self, tree=None):
2608
tree = self.make_branch_and_tree('tree')
2609
tt = TransformPreview(tree)
2610
self.addCleanup(tt.finalize)
2613
def assertSerializesTo(self, expected, tt):
2614
records = list(tt.serialize(FakeSerializer()))
2615
self.assertEqual(expected, records)
2618
def default_attribs():
2623
'_new_executability': {},
2625
'_tree_path_ids': {'': 'new-0'},
2627
'_removed_contents': [],
2628
'_non_present_ids': {},
2631
def make_records(self, attribs, contents):
2633
(((('attribs'),),), bencode.bencode(attribs))]
2634
records.extend([(((n, k),), c) for n, k, c in contents])
2637
def creation_records(self):
2638
attribs = self.default_attribs()
2639
attribs['_id_number'] = 3
2640
attribs['_new_name'] = {
2641
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2642
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2643
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2644
attribs['_new_executability'] = {'new-1': 1}
2646
('new-1', 'file', 'i 1\nbar\n'),
2647
('new-2', 'directory', ''),
2649
return self.make_records(attribs, contents)
2651
def test_serialize_creation(self):
2652
tt = self.get_preview()
2653
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2654
tt.new_directory('qux', tt.root, 'quxx')
2655
self.assertSerializesTo(self.creation_records(), tt)
2657
def test_deserialize_creation(self):
2658
tt = self.get_preview()
2659
tt.deserialize(iter(self.creation_records()))
2660
self.assertEqual(3, tt._id_number)
2661
self.assertEqual({'new-1': u'foo\u1234',
2662
'new-2': 'qux'}, tt._new_name)
2663
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2664
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2665
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2666
self.assertEqual({'new-1': True}, tt._new_executability)
2667
self.assertEqual({'new-1': 'file',
2668
'new-2': 'directory'}, tt._new_contents)
2669
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2671
foo_content = foo_limbo.read()
2674
self.assertEqual('bar', foo_content)
2676
def symlink_creation_records(self):
2677
attribs = self.default_attribs()
2678
attribs['_id_number'] = 2
2679
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2680
attribs['_new_parent'] = {'new-1': 'new-0'}
2681
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2682
return self.make_records(attribs, contents)
2684
def test_serialize_symlink_creation(self):
2685
self.requireFeature(tests.SymlinkFeature)
2686
tt = self.get_preview()
2687
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2688
self.assertSerializesTo(self.symlink_creation_records(), tt)
2690
def test_deserialize_symlink_creation(self):
2691
tt = self.get_preview()
2692
tt.deserialize(iter(self.symlink_creation_records()))
2693
# XXX readlink should be returning unicode, not utf-8
2694
foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
2695
self.assertEqual(u'bar\u1234', foo_content)
2697
def make_destruction_preview(self):
2698
tree = self.make_branch_and_tree('.')
2699
self.build_tree([u'foo\u1234', 'bar'])
2700
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2701
return self.get_preview(tree)
2703
def destruction_records(self):
2704
attribs = self.default_attribs()
2705
attribs['_id_number'] = 3
2706
attribs['_removed_id'] = ['new-1']
2707
attribs['_removed_contents'] = ['new-2']
2708
attribs['_tree_path_ids'] = {
2710
u'foo\u1234'.encode('utf-8'): 'new-1',
2713
return self.make_records(attribs, [])
2715
def test_serialize_destruction(self):
2716
tt = self.make_destruction_preview()
2717
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2718
tt.unversion_file(foo_trans_id)
2719
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2720
tt.delete_contents(bar_trans_id)
2721
self.assertSerializesTo(self.destruction_records(), tt)
2723
def test_deserialize_destruction(self):
2724
tt = self.make_destruction_preview()
2725
tt.deserialize(iter(self.destruction_records()))
2726
self.assertEqual({u'foo\u1234': 'new-1',
2728
'': tt.root}, tt._tree_path_ids)
2729
self.assertEqual({'new-1': u'foo\u1234',
2731
tt.root: ''}, tt._tree_id_paths)
2732
self.assertEqual(set(['new-1']), tt._removed_id)
2733
self.assertEqual(set(['new-2']), tt._removed_contents)
2735
def missing_records(self):
2736
attribs = self.default_attribs()
2737
attribs['_id_number'] = 2
2738
attribs['_non_present_ids'] = {
2740
return self.make_records(attribs, [])
2742
def test_serialize_missing(self):
2743
tt = self.get_preview()
2744
boo_trans_id = tt.trans_id_file_id('boo')
2745
self.assertSerializesTo(self.missing_records(), tt)
2747
def test_deserialize_missing(self):
2748
tt = self.get_preview()
2749
tt.deserialize(iter(self.missing_records()))
2750
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2752
def make_modification_preview(self):
2753
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2754
LINES_TWO = 'z\nbb\nx\ndd\n'
2755
tree = self.make_branch_and_tree('tree')
2756
self.build_tree_contents([('tree/file', LINES_ONE)])
2757
tree.add('file', 'file-id')
2758
return self.get_preview(tree), LINES_TWO
2760
def modification_records(self):
2761
attribs = self.default_attribs()
2762
attribs['_id_number'] = 2
2763
attribs['_tree_path_ids'] = {
2766
attribs['_removed_contents'] = ['new-1']
2767
contents = [('new-1', 'file',
2768
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2769
return self.make_records(attribs, contents)
2771
def test_serialize_modification(self):
2772
tt, LINES = self.make_modification_preview()
2773
trans_id = tt.trans_id_file_id('file-id')
2774
tt.delete_contents(trans_id)
2775
tt.create_file(LINES, trans_id)
2776
self.assertSerializesTo(self.modification_records(), tt)
2778
def test_deserialize_modification(self):
2779
tt, LINES = self.make_modification_preview()
2780
tt.deserialize(iter(self.modification_records()))
2781
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2783
def make_kind_change_preview(self):
2784
LINES = 'a\nb\nc\nd\n'
2785
tree = self.make_branch_and_tree('tree')
2786
self.build_tree(['tree/foo/'])
2787
tree.add('foo', 'foo-id')
2788
return self.get_preview(tree), LINES
2790
def kind_change_records(self):
2791
attribs = self.default_attribs()
2792
attribs['_id_number'] = 2
2793
attribs['_tree_path_ids'] = {
2796
attribs['_removed_contents'] = ['new-1']
2797
contents = [('new-1', 'file',
2798
'i 4\na\nb\nc\nd\n\n')]
2799
return self.make_records(attribs, contents)
2801
def test_serialize_kind_change(self):
2802
tt, LINES = self.make_kind_change_preview()
2803
trans_id = tt.trans_id_file_id('foo-id')
2804
tt.delete_contents(trans_id)
2805
tt.create_file(LINES, trans_id)
2806
self.assertSerializesTo(self.kind_change_records(), tt)
2808
def test_deserialize_kind_change(self):
2809
tt, LINES = self.make_kind_change_preview()
2810
tt.deserialize(iter(self.kind_change_records()))
2811
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2813
def make_add_contents_preview(self):
2814
LINES = 'a\nb\nc\nd\n'
2815
tree = self.make_branch_and_tree('tree')
2816
self.build_tree(['tree/foo'])
2818
os.unlink('tree/foo')
2819
return self.get_preview(tree), LINES
2821
def add_contents_records(self):
2822
attribs = self.default_attribs()
2823
attribs['_id_number'] = 2
2824
attribs['_tree_path_ids'] = {
2827
contents = [('new-1', 'file',
2828
'i 4\na\nb\nc\nd\n\n')]
2829
return self.make_records(attribs, contents)
2831
def test_serialize_add_contents(self):
2832
tt, LINES = self.make_add_contents_preview()
2833
trans_id = tt.trans_id_tree_path('foo')
2834
tt.create_file(LINES, trans_id)
2835
self.assertSerializesTo(self.add_contents_records(), tt)
2837
def test_deserialize_add_contents(self):
2838
tt, LINES = self.make_add_contents_preview()
2839
tt.deserialize(iter(self.add_contents_records()))
2840
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2842
def test_get_parents_lines(self):
2843
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2844
LINES_TWO = 'z\nbb\nx\ndd\n'
2845
tree = self.make_branch_and_tree('tree')
2846
self.build_tree_contents([('tree/file', LINES_ONE)])
2847
tree.add('file', 'file-id')
2848
tt = self.get_preview(tree)
2849
trans_id = tt.trans_id_tree_path('file')
2850
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
2851
tt._get_parents_lines(trans_id))
2853
def test_get_parents_texts(self):
2854
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2855
LINES_TWO = 'z\nbb\nx\ndd\n'
2856
tree = self.make_branch_and_tree('tree')
2857
self.build_tree_contents([('tree/file', LINES_ONE)])
2858
tree.add('file', 'file-id')
2859
tt = self.get_preview(tree)
2860
trans_id = tt.trans_id_tree_path('file')
2861
self.assertEqual((LINES_ONE,),
2862
tt._get_parents_texts(trans_id))