1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from StringIO import StringIO
27
revision as _mod_revision,
32
from bzrlib.bzrdir import BzrDir
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
UnversionedParent, ParentLoop, DeletingParent,
36
from bzrlib.diff import show_diff_trees
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
ReusingTransform, CantMoveRoot,
39
PathsNotVersionedError, ExistingLimbo,
40
ExistingPendingDeletion, ImmortalLimbo,
41
ImmortalPendingDeletion, LockError)
42
from bzrlib.osutils import file_kind, pathjoin
43
from bzrlib.merge import Merge3Merger, Merger
44
from bzrlib.tests import (
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
source = self.make_branch_and_tree('source')
1845
self.build_tree(['source/file', 'source/FILE'])
1846
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1847
source.commit('added files')
1848
# Don't try this at home, kids!
1849
# Force the tree to report that it is case insensitive
1850
target = self.make_branch_and_tree('target')
1851
target.case_sensitive = False
1852
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1853
self.assertEqual('file.moved', target.id2path('lower-id'))
1854
self.assertEqual('FILE', target.id2path('upper-id'))
1857
class MockTransform(object):
1859
def has_named_child(self, by_parent, parent_id, name):
1860
for child_id in by_parent[parent_id]:
1864
elif name == "name.~%s~" % child_id:
1869
class MockEntry(object):
1871
object.__init__(self)
1875
class TestGetBackupName(TestCase):
1876
def test_get_backup_name(self):
1877
tt = MockTransform()
1878
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1879
self.assertEqual(name, 'name.~1~')
1880
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1881
self.assertEqual(name, 'name.~2~')
1882
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1883
self.assertEqual(name, 'name.~1~')
1884
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1885
self.assertEqual(name, 'name.~1~')
1886
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1887
self.assertEqual(name, 'name.~4~')
1890
class TestFileMover(tests.TestCaseWithTransport):
1892
def test_file_mover(self):
1893
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1894
mover = _FileMover()
1895
mover.rename('a', 'q')
1896
self.failUnlessExists('q')
1897
self.failIfExists('a')
1898
self.failUnlessExists('q/b')
1899
self.failUnlessExists('c')
1900
self.failUnlessExists('c/d')
1902
def test_pre_delete_rollback(self):
1903
self.build_tree(['a/'])
1904
mover = _FileMover()
1905
mover.pre_delete('a', 'q')
1906
self.failUnlessExists('q')
1907
self.failIfExists('a')
1909
self.failIfExists('q')
1910
self.failUnlessExists('a')
1912
def test_apply_deletions(self):
1913
self.build_tree(['a/', 'b/'])
1914
mover = _FileMover()
1915
mover.pre_delete('a', 'q')
1916
mover.pre_delete('b', 'r')
1917
self.failUnlessExists('q')
1918
self.failUnlessExists('r')
1919
self.failIfExists('a')
1920
self.failIfExists('b')
1921
mover.apply_deletions()
1922
self.failIfExists('q')
1923
self.failIfExists('r')
1924
self.failIfExists('a')
1925
self.failIfExists('b')
1927
def test_file_mover_rollback(self):
1928
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1929
mover = _FileMover()
1930
mover.rename('c/d', 'c/f')
1931
mover.rename('c/e', 'c/d')
1933
mover.rename('a', 'c')
1934
except errors.FileExists, e:
1936
self.failUnlessExists('a')
1937
self.failUnlessExists('c/d')
1940
class Bogus(Exception):
1944
class TestTransformRollback(tests.TestCaseWithTransport):
1946
class ExceptionFileMover(_FileMover):
1948
def __init__(self, bad_source=None, bad_target=None):
1949
_FileMover.__init__(self)
1950
self.bad_source = bad_source
1951
self.bad_target = bad_target
1953
def rename(self, source, target):
1954
if (self.bad_source is not None and
1955
source.endswith(self.bad_source)):
1957
elif (self.bad_target is not None and
1958
target.endswith(self.bad_target)):
1961
_FileMover.rename(self, source, target)
1963
def test_rollback_rename(self):
1964
tree = self.make_branch_and_tree('.')
1965
self.build_tree(['a/', 'a/b'])
1966
tt = TreeTransform(tree)
1967
self.addCleanup(tt.finalize)
1968
a_id = tt.trans_id_tree_path('a')
1969
tt.adjust_path('c', tt.root, a_id)
1970
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1971
self.assertRaises(Bogus, tt.apply,
1972
_mover=self.ExceptionFileMover(bad_source='a'))
1973
self.failUnlessExists('a')
1974
self.failUnlessExists('a/b')
1976
self.failUnlessExists('c')
1977
self.failUnlessExists('c/d')
1979
def test_rollback_rename_into_place(self):
1980
tree = self.make_branch_and_tree('.')
1981
self.build_tree(['a/', 'a/b'])
1982
tt = TreeTransform(tree)
1983
self.addCleanup(tt.finalize)
1984
a_id = tt.trans_id_tree_path('a')
1985
tt.adjust_path('c', tt.root, a_id)
1986
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1987
self.assertRaises(Bogus, tt.apply,
1988
_mover=self.ExceptionFileMover(bad_target='c/d'))
1989
self.failUnlessExists('a')
1990
self.failUnlessExists('a/b')
1992
self.failUnlessExists('c')
1993
self.failUnlessExists('c/d')
1995
def test_rollback_deletion(self):
1996
tree = self.make_branch_and_tree('.')
1997
self.build_tree(['a/', 'a/b'])
1998
tt = TreeTransform(tree)
1999
self.addCleanup(tt.finalize)
2000
a_id = tt.trans_id_tree_path('a')
2001
tt.delete_contents(a_id)
2002
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2003
self.assertRaises(Bogus, tt.apply,
2004
_mover=self.ExceptionFileMover(bad_target='d'))
2005
self.failUnlessExists('a')
2006
self.failUnlessExists('a/b')
2008
def test_resolve_no_parent(self):
2009
wt = self.make_branch_and_tree('.')
2010
tt = TreeTransform(wt)
2011
self.addCleanup(tt.finalize)
2012
parent = tt.trans_id_file_id('parent-id')
2013
tt.new_file('file', parent, 'Contents')
2014
resolve_conflicts(tt)
2017
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2018
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2020
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2021
('', ''), ('directory', 'directory'), (False, None))
2024
class TestTransformPreview(tests.TestCaseWithTransport):
2026
def create_tree(self):
2027
tree = self.make_branch_and_tree('.')
2028
self.build_tree_contents([('a', 'content 1')])
2029
tree.add('a', 'a-id')
2030
tree.commit('rev1', rev_id='rev1')
2031
return tree.branch.repository.revision_tree('rev1')
2033
def get_empty_preview(self):
2034
repository = self.make_repository('repo')
2035
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2036
preview = TransformPreview(tree)
2037
self.addCleanup(preview.finalize)
2040
def test_transform_preview(self):
2041
revision_tree = self.create_tree()
2042
preview = TransformPreview(revision_tree)
2043
self.addCleanup(preview.finalize)
2045
def test_transform_preview_tree(self):
2046
revision_tree = self.create_tree()
2047
preview = TransformPreview(revision_tree)
2048
self.addCleanup(preview.finalize)
2049
preview.get_preview_tree()
2051
def test_transform_new_file(self):
2052
revision_tree = self.create_tree()
2053
preview = TransformPreview(revision_tree)
2054
self.addCleanup(preview.finalize)
2055
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2056
preview_tree = preview.get_preview_tree()
2057
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2059
preview_tree.get_file('file2-id').read(), 'content B\n')
2061
def test_diff_preview_tree(self):
2062
revision_tree = self.create_tree()
2063
preview = TransformPreview(revision_tree)
2064
self.addCleanup(preview.finalize)
2065
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2066
preview_tree = preview.get_preview_tree()
2068
show_diff_trees(revision_tree, preview_tree, out)
2069
lines = out.getvalue().splitlines()
2070
self.assertEqual(lines[0], "=== added file 'file2'")
2071
# 3 lines of diff administrivia
2072
self.assertEqual(lines[4], "+content B")
2074
def test_transform_conflicts(self):
2075
revision_tree = self.create_tree()
2076
preview = TransformPreview(revision_tree)
2077
self.addCleanup(preview.finalize)
2078
preview.new_file('a', preview.root, 'content 2')
2079
resolve_conflicts(preview)
2080
trans_id = preview.trans_id_file_id('a-id')
2081
self.assertEqual('a.moved', preview.final_name(trans_id))
2083
def get_tree_and_preview_tree(self):
2084
revision_tree = self.create_tree()
2085
preview = TransformPreview(revision_tree)
2086
self.addCleanup(preview.finalize)
2087
a_trans_id = preview.trans_id_file_id('a-id')
2088
preview.delete_contents(a_trans_id)
2089
preview.create_file('b content', a_trans_id)
2090
preview_tree = preview.get_preview_tree()
2091
return revision_tree, preview_tree
2093
def test_iter_changes(self):
2094
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2095
root = revision_tree.inventory.root.file_id
2096
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2097
(root, root), ('a', 'a'), ('file', 'file'),
2099
list(preview_tree.iter_changes(revision_tree)))
2101
def test_include_unchanged_succeeds(self):
2102
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2103
changes = preview_tree.iter_changes(revision_tree,
2104
include_unchanged=True)
2105
root = revision_tree.inventory.root.file_id
2107
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2109
def test_specific_files(self):
2110
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2111
changes = preview_tree.iter_changes(revision_tree,
2112
specific_files=[''])
2113
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2115
def test_want_unversioned(self):
2116
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2117
changes = preview_tree.iter_changes(revision_tree,
2118
want_unversioned=True)
2119
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2121
def test_ignore_extra_trees_no_specific_files(self):
2122
# extra_trees is harmless without specific_files, so we'll silently
2123
# accept it, even though we won't use it.
2124
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2125
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2127
def test_ignore_require_versioned_no_specific_files(self):
2128
# require_versioned is meaningless without specific_files.
2129
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2130
preview_tree.iter_changes(revision_tree, require_versioned=False)
2132
def test_ignore_pb(self):
2133
# pb could be supported, but TT.iter_changes doesn't support it.
2134
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2135
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2137
def test_kind(self):
2138
revision_tree = self.create_tree()
2139
preview = TransformPreview(revision_tree)
2140
self.addCleanup(preview.finalize)
2141
preview.new_file('file', preview.root, 'contents', 'file-id')
2142
preview.new_directory('directory', preview.root, 'dir-id')
2143
preview_tree = preview.get_preview_tree()
2144
self.assertEqual('file', preview_tree.kind('file-id'))
2145
self.assertEqual('directory', preview_tree.kind('dir-id'))
2147
def test_get_file_mtime(self):
2148
preview = self.get_empty_preview()
2149
file_trans_id = preview.new_file('file', preview.root, 'contents',
2151
limbo_path = preview._limbo_name(file_trans_id)
2152
preview_tree = preview.get_preview_tree()
2153
self.assertEqual(os.stat(limbo_path).st_mtime,
2154
preview_tree.get_file_mtime('file-id'))
2156
def test_get_file(self):
2157
preview = self.get_empty_preview()
2158
preview.new_file('file', preview.root, 'contents', 'file-id')
2159
preview_tree = preview.get_preview_tree()
2160
tree_file = preview_tree.get_file('file-id')
2162
self.assertEqual('contents', tree_file.read())
2166
def test_get_symlink_target(self):
2167
self.requireFeature(SymlinkFeature)
2168
preview = self.get_empty_preview()
2169
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2170
preview_tree = preview.get_preview_tree()
2171
self.assertEqual('target',
2172
preview_tree.get_symlink_target('symlink-id'))
2174
def test_all_file_ids(self):
2175
tree = self.make_branch_and_tree('tree')
2176
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2177
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2178
preview = TransformPreview(tree)
2179
self.addCleanup(preview.finalize)
2180
preview.unversion_file(preview.trans_id_file_id('b-id'))
2181
c_trans_id = preview.trans_id_file_id('c-id')
2182
preview.unversion_file(c_trans_id)
2183
preview.version_file('c-id', c_trans_id)
2184
preview_tree = preview.get_preview_tree()
2185
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2186
preview_tree.all_file_ids())
2188
def test_path2id_deleted_unchanged(self):
2189
tree = self.make_branch_and_tree('tree')
2190
self.build_tree(['tree/unchanged', 'tree/deleted'])
2191
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2192
preview = TransformPreview(tree)
2193
self.addCleanup(preview.finalize)
2194
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2195
preview_tree = preview.get_preview_tree()
2196
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2197
self.assertIs(None, preview_tree.path2id('deleted'))
2199
def test_path2id_created(self):
2200
tree = self.make_branch_and_tree('tree')
2201
self.build_tree(['tree/unchanged'])
2202
tree.add(['unchanged'], ['unchanged-id'])
2203
preview = TransformPreview(tree)
2204
self.addCleanup(preview.finalize)
2205
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2206
'contents', 'new-id')
2207
preview_tree = preview.get_preview_tree()
2208
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2210
def test_path2id_moved(self):
2211
tree = self.make_branch_and_tree('tree')
2212
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2213
tree.add(['old_parent', 'old_parent/child'],
2214
['old_parent-id', 'child-id'])
2215
preview = TransformPreview(tree)
2216
self.addCleanup(preview.finalize)
2217
new_parent = preview.new_directory('new_parent', preview.root,
2219
preview.adjust_path('child', new_parent,
2220
preview.trans_id_file_id('child-id'))
2221
preview_tree = preview.get_preview_tree()
2222
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2223
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2225
def test_path2id_renamed_parent(self):
2226
tree = self.make_branch_and_tree('tree')
2227
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2228
tree.add(['old_name', 'old_name/child'],
2229
['parent-id', 'child-id'])
2230
preview = TransformPreview(tree)
2231
self.addCleanup(preview.finalize)
2232
preview.adjust_path('new_name', preview.root,
2233
preview.trans_id_file_id('parent-id'))
2234
preview_tree = preview.get_preview_tree()
2235
self.assertIs(None, preview_tree.path2id('old_name/child'))
2236
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2238
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2239
preview_tree = tt.get_preview_tree()
2240
preview_result = list(preview_tree.iter_entries_by_dir(
2244
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2245
self.assertEqual(actual_result, preview_result)
2247
def test_iter_entries_by_dir_new(self):
2248
tree = self.make_branch_and_tree('tree')
2249
tt = TreeTransform(tree)
2250
tt.new_file('new', tt.root, 'contents', 'new-id')
2251
self.assertMatchingIterEntries(tt)
2253
def test_iter_entries_by_dir_deleted(self):
2254
tree = self.make_branch_and_tree('tree')
2255
self.build_tree(['tree/deleted'])
2256
tree.add('deleted', 'deleted-id')
2257
tt = TreeTransform(tree)
2258
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2259
self.assertMatchingIterEntries(tt)
2261
def test_iter_entries_by_dir_unversioned(self):
2262
tree = self.make_branch_and_tree('tree')
2263
self.build_tree(['tree/removed'])
2264
tree.add('removed', 'removed-id')
2265
tt = TreeTransform(tree)
2266
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2267
self.assertMatchingIterEntries(tt)
2269
def test_iter_entries_by_dir_moved(self):
2270
tree = self.make_branch_and_tree('tree')
2271
self.build_tree(['tree/moved', 'tree/new_parent/'])
2272
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2273
tt = TreeTransform(tree)
2274
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2275
tt.trans_id_file_id('moved-id'))
2276
self.assertMatchingIterEntries(tt)
2278
def test_iter_entries_by_dir_specific_file_ids(self):
2279
tree = self.make_branch_and_tree('tree')
2280
tree.set_root_id('tree-root-id')
2281
self.build_tree(['tree/parent/', 'tree/parent/child'])
2282
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2283
tt = TreeTransform(tree)
2284
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2286
def test_symlink_content_summary(self):
2287
self.requireFeature(SymlinkFeature)
2288
preview = self.get_empty_preview()
2289
preview.new_symlink('path', preview.root, 'target', 'path-id')
2290
summary = preview.get_preview_tree().path_content_summary('path')
2291
self.assertEqual(('symlink', None, None, 'target'), summary)
2293
def test_missing_content_summary(self):
2294
preview = self.get_empty_preview()
2295
summary = preview.get_preview_tree().path_content_summary('path')
2296
self.assertEqual(('missing', None, None, None), summary)
2298
def test_deleted_content_summary(self):
2299
tree = self.make_branch_and_tree('tree')
2300
self.build_tree(['tree/path/'])
2302
preview = TransformPreview(tree)
2303
self.addCleanup(preview.finalize)
2304
preview.delete_contents(preview.trans_id_tree_path('path'))
2305
summary = preview.get_preview_tree().path_content_summary('path')
2306
self.assertEqual(('missing', None, None, None), summary)
2308
def test_file_content_summary_executable(self):
2309
if not osutils.supports_executable():
2310
raise TestNotApplicable()
2311
preview = self.get_empty_preview()
2312
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2313
preview.set_executability(True, path_id)
2314
summary = preview.get_preview_tree().path_content_summary('path')
2315
self.assertEqual(4, len(summary))
2316
self.assertEqual('file', summary[0])
2317
# size must be known
2318
self.assertEqual(len('contents'), summary[1])
2320
self.assertEqual(True, summary[2])
2321
# will not have hash (not cheap to determine)
2322
self.assertIs(None, summary[3])
2324
def test_change_executability(self):
2325
if not osutils.supports_executable():
2326
raise TestNotApplicable()
2327
tree = self.make_branch_and_tree('tree')
2328
self.build_tree(['tree/path'])
2330
preview = TransformPreview(tree)
2331
self.addCleanup(preview.finalize)
2332
path_id = preview.trans_id_tree_path('path')
2333
preview.set_executability(True, path_id)
2334
summary = preview.get_preview_tree().path_content_summary('path')
2335
self.assertEqual(True, summary[2])
2337
def test_file_content_summary_non_exec(self):
2338
preview = self.get_empty_preview()
2339
preview.new_file('path', preview.root, 'contents', 'path-id')
2340
summary = preview.get_preview_tree().path_content_summary('path')
2341
self.assertEqual(4, len(summary))
2342
self.assertEqual('file', summary[0])
2343
# size must be known
2344
self.assertEqual(len('contents'), summary[1])
2346
if osutils.supports_executable():
2347
self.assertEqual(False, summary[2])
2349
self.assertEqual(None, summary[2])
2350
# will not have hash (not cheap to determine)
2351
self.assertIs(None, summary[3])
2353
def test_dir_content_summary(self):
2354
preview = self.get_empty_preview()
2355
preview.new_directory('path', preview.root, 'path-id')
2356
summary = preview.get_preview_tree().path_content_summary('path')
2357
self.assertEqual(('directory', None, None, None), summary)
2359
def test_tree_content_summary(self):
2360
preview = self.get_empty_preview()
2361
path = preview.new_directory('path', preview.root, 'path-id')
2362
preview.set_tree_reference('rev-1', path)
2363
summary = preview.get_preview_tree().path_content_summary('path')
2364
self.assertEqual(4, len(summary))
2365
self.assertEqual('tree-reference', summary[0])
2367
def test_annotate(self):
2368
tree = self.make_branch_and_tree('tree')
2369
self.build_tree_contents([('tree/file', 'a\n')])
2370
tree.add('file', 'file-id')
2371
tree.commit('a', rev_id='one')
2372
self.build_tree_contents([('tree/file', 'a\nb\n')])
2373
preview = TransformPreview(tree)
2374
self.addCleanup(preview.finalize)
2375
file_trans_id = preview.trans_id_file_id('file-id')
2376
preview.delete_contents(file_trans_id)
2377
preview.create_file('a\nb\nc\n', file_trans_id)
2378
preview_tree = preview.get_preview_tree()
2384
annotation = preview_tree.annotate_iter('file-id', 'me:')
2385
self.assertEqual(expected, annotation)
2387
def test_annotate_missing(self):
2388
preview = self.get_empty_preview()
2389
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2390
preview_tree = preview.get_preview_tree()
2396
annotation = preview_tree.annotate_iter('file-id', 'me:')
2397
self.assertEqual(expected, annotation)
2399
def test_annotate_rename(self):
2400
tree = self.make_branch_and_tree('tree')
2401
self.build_tree_contents([('tree/file', 'a\n')])
2402
tree.add('file', 'file-id')
2403
tree.commit('a', rev_id='one')
2404
preview = TransformPreview(tree)
2405
self.addCleanup(preview.finalize)
2406
file_trans_id = preview.trans_id_file_id('file-id')
2407
preview.adjust_path('newname', preview.root, file_trans_id)
2408
preview_tree = preview.get_preview_tree()
2412
annotation = preview_tree.annotate_iter('file-id', 'me:')
2413
self.assertEqual(expected, annotation)
2415
def test_annotate_deleted(self):
2416
tree = self.make_branch_and_tree('tree')
2417
self.build_tree_contents([('tree/file', 'a\n')])
2418
tree.add('file', 'file-id')
2419
tree.commit('a', rev_id='one')
2420
self.build_tree_contents([('tree/file', 'a\nb\n')])
2421
preview = TransformPreview(tree)
2422
self.addCleanup(preview.finalize)
2423
file_trans_id = preview.trans_id_file_id('file-id')
2424
preview.delete_contents(file_trans_id)
2425
preview_tree = preview.get_preview_tree()
2426
annotation = preview_tree.annotate_iter('file-id', 'me:')
2427
self.assertIs(None, annotation)
2429
def test_stored_kind(self):
2430
preview = self.get_empty_preview()
2431
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2432
preview_tree = preview.get_preview_tree()
2433
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2435
def test_is_executable(self):
2436
preview = self.get_empty_preview()
2437
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2438
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2439
preview_tree = preview.get_preview_tree()
2440
self.assertEqual(True, preview_tree.is_executable('file-id'))
2442
def test_get_set_parent_ids(self):
2443
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2444
self.assertEqual([], preview_tree.get_parent_ids())
2445
preview_tree.set_parent_ids(['rev-1'])
2446
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2448
def test_plan_file_merge(self):
2449
work_a = self.make_branch_and_tree('wta')
2450
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2451
work_a.add('file', 'file-id')
2452
base_id = work_a.commit('base version')
2453
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2454
preview = TransformPreview(work_a)
2455
self.addCleanup(preview.finalize)
2456
trans_id = preview.trans_id_file_id('file-id')
2457
preview.delete_contents(trans_id)
2458
preview.create_file('b\nc\nd\ne\n', trans_id)
2459
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2460
tree_a = preview.get_preview_tree()
2461
tree_a.set_parent_ids([base_id])
2463
('killed-a', 'a\n'),
2464
('killed-b', 'b\n'),
2465
('unchanged', 'c\n'),
2466
('unchanged', 'd\n'),
2469
], list(tree_a.plan_file_merge('file-id', tree_b)))
2471
def test_plan_file_merge_revision_tree(self):
2472
work_a = self.make_branch_and_tree('wta')
2473
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2474
work_a.add('file', 'file-id')
2475
base_id = work_a.commit('base version')
2476
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2477
preview = TransformPreview(work_a.basis_tree())
2478
self.addCleanup(preview.finalize)
2479
trans_id = preview.trans_id_file_id('file-id')
2480
preview.delete_contents(trans_id)
2481
preview.create_file('b\nc\nd\ne\n', trans_id)
2482
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2483
tree_a = preview.get_preview_tree()
2484
tree_a.set_parent_ids([base_id])
2486
('killed-a', 'a\n'),
2487
('killed-b', 'b\n'),
2488
('unchanged', 'c\n'),
2489
('unchanged', 'd\n'),
2492
], list(tree_a.plan_file_merge('file-id', tree_b)))
2494
def test_walkdirs(self):
2495
preview = self.get_empty_preview()
2496
preview.version_file('tree-root', preview.root)
2497
preview_tree = preview.get_preview_tree()
2498
file_trans_id = preview.new_file('a', preview.root, 'contents',
2500
expected = [(('', 'tree-root'),
2501
[('a', 'a', 'file', None, 'a-id', 'file')])]
2502
self.assertEqual(expected, list(preview_tree.walkdirs()))
2504
def test_extras(self):
2505
work_tree = self.make_branch_and_tree('tree')
2506
self.build_tree(['tree/removed-file', 'tree/existing-file',
2507
'tree/not-removed-file'])
2508
work_tree.add(['removed-file', 'not-removed-file'])
2509
preview = TransformPreview(work_tree)
2510
self.addCleanup(preview.finalize)
2511
preview.new_file('new-file', preview.root, 'contents')
2512
preview.new_file('new-versioned-file', preview.root, 'contents',
2514
tree = preview.get_preview_tree()
2515
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2516
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2519
def test_merge_into_preview(self):
2520
work_tree = self.make_branch_and_tree('tree')
2521
self.build_tree_contents([('tree/file','b\n')])
2522
work_tree.add('file', 'file-id')
2523
work_tree.commit('first commit')
2524
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2525
self.build_tree_contents([('child/file','b\nc\n')])
2526
child_tree.commit('child commit')
2527
child_tree.lock_write()
2528
self.addCleanup(child_tree.unlock)
2529
work_tree.lock_write()
2530
self.addCleanup(work_tree.unlock)
2531
preview = TransformPreview(work_tree)
2532
self.addCleanup(preview.finalize)
2533
preview_tree = preview.get_preview_tree()
2534
file_trans_id = preview.trans_id_file_id('file-id')
2535
preview.delete_contents(file_trans_id)
2536
preview.create_file('a\nb\n', file_trans_id)
2537
pb = progress.DummyProgress()
2538
merger = Merger.from_revision_ids(pb, preview_tree,
2539
child_tree.branch.last_revision(),
2540
other_branch=child_tree.branch,
2541
tree_branch=work_tree.branch)
2542
merger.merge_type = Merge3Merger
2543
tt = merger.make_merger().make_preview_transform()
2544
self.addCleanup(tt.finalize)
2545
final_tree = tt.get_preview_tree()
2546
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2548
def test_merge_preview_into_workingtree(self):
2549
tree = self.make_branch_and_tree('tree')
2550
tt = TransformPreview(tree)
2551
self.addCleanup(tt.finalize)
2552
tt.new_file('name', tt.root, 'content', 'file-id')
2553
tree2 = self.make_branch_and_tree('tree2')
2554
pb = progress.DummyProgress()
2555
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2556
pb, tree.basis_tree())
2557
merger.merge_type = Merge3Merger
2560
def test_merge_preview_into_workingtree_handles_conflicts(self):
2561
tree = self.make_branch_and_tree('tree')
2562
self.build_tree_contents([('tree/foo', 'bar')])
2563
tree.add('foo', 'foo-id')
2565
tt = TransformPreview(tree)
2566
self.addCleanup(tt.finalize)
2567
trans_id = tt.trans_id_file_id('foo-id')
2568
tt.delete_contents(trans_id)
2569
tt.create_file('baz', trans_id)
2570
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2571
self.build_tree_contents([('tree2/foo', 'qux')])
2572
pb = progress.DummyProgress()
2573
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2574
pb, tree.basis_tree())
2575
merger.merge_type = Merge3Merger
2578
def test_is_executable(self):
2579
tree = self.make_branch_and_tree('tree')
2580
preview = TransformPreview(tree)
2581
self.addCleanup(preview.finalize)
2582
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2583
preview_tree = preview.get_preview_tree()
2584
self.assertEqual(False, preview_tree.is_executable('baz-id',
2586
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2589
class FakeSerializer(object):
2590
"""Serializer implementation that simply returns the input.
2592
The input is returned in the order used by pack.ContainerPushParser.
2595
def bytes_record(bytes, names):
2599
class TestSerializeTransform(tests.TestCaseWithTransport):
2601
_test_needs_features = [tests.UnicodeFilenameFeature]
2603
def get_preview(self, tree=None):
2605
tree = self.make_branch_and_tree('tree')
2606
tt = TransformPreview(tree)
2607
self.addCleanup(tt.finalize)
2610
def assertSerializesTo(self, expected, tt):
2611
records = list(tt.serialize(FakeSerializer()))
2612
self.assertEqual(expected, records)
2615
def default_attribs():
2620
'_new_executability': {},
2622
'_tree_path_ids': {'': 'new-0'},
2624
'_removed_contents': [],
2625
'_non_present_ids': {},
2628
def make_records(self, attribs, contents):
2630
(((('attribs'),),), bencode.bencode(attribs))]
2631
records.extend([(((n, k),), c) for n, k, c in contents])
2634
def creation_records(self):
2635
attribs = self.default_attribs()
2636
attribs['_id_number'] = 3
2637
attribs['_new_name'] = {
2638
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2639
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2640
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2641
attribs['_new_executability'] = {'new-1': 1}
2643
('new-1', 'file', 'i 1\nbar\n'),
2644
('new-2', 'directory', ''),
2646
return self.make_records(attribs, contents)
2648
def test_serialize_creation(self):
2649
tt = self.get_preview()
2650
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2651
tt.new_directory('qux', tt.root, 'quxx')
2652
self.assertSerializesTo(self.creation_records(), tt)
2654
def test_deserialize_creation(self):
2655
tt = self.get_preview()
2656
tt.deserialize(iter(self.creation_records()))
2657
self.assertEqual(3, tt._id_number)
2658
self.assertEqual({'new-1': u'foo\u1234',
2659
'new-2': 'qux'}, tt._new_name)
2660
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2661
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2662
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2663
self.assertEqual({'new-1': True}, tt._new_executability)
2664
self.assertEqual({'new-1': 'file',
2665
'new-2': 'directory'}, tt._new_contents)
2666
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2668
foo_content = foo_limbo.read()
2671
self.assertEqual('bar', foo_content)
2673
def symlink_creation_records(self):
2674
attribs = self.default_attribs()
2675
attribs['_id_number'] = 2
2676
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2677
attribs['_new_parent'] = {'new-1': 'new-0'}
2678
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2679
return self.make_records(attribs, contents)
2681
def test_serialize_symlink_creation(self):
2682
self.requireFeature(tests.SymlinkFeature)
2683
tt = self.get_preview()
2684
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2685
self.assertSerializesTo(self.symlink_creation_records(), tt)
2687
def test_deserialize_symlink_creation(self):
2688
tt = self.get_preview()
2689
tt.deserialize(iter(self.symlink_creation_records()))
2690
# XXX readlink should be returning unicode, not utf-8
2691
foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
2692
self.assertEqual(u'bar\u1234', foo_content)
2694
def make_destruction_preview(self):
2695
tree = self.make_branch_and_tree('.')
2696
self.build_tree([u'foo\u1234', 'bar'])
2697
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2698
return self.get_preview(tree)
2700
def destruction_records(self):
2701
attribs = self.default_attribs()
2702
attribs['_id_number'] = 3
2703
attribs['_removed_id'] = ['new-1']
2704
attribs['_removed_contents'] = ['new-2']
2705
attribs['_tree_path_ids'] = {
2707
u'foo\u1234'.encode('utf-8'): 'new-1',
2710
return self.make_records(attribs, [])
2712
def test_serialize_destruction(self):
2713
tt = self.make_destruction_preview()
2714
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2715
tt.unversion_file(foo_trans_id)
2716
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2717
tt.delete_contents(bar_trans_id)
2718
self.assertSerializesTo(self.destruction_records(), tt)
2720
def test_deserialize_destruction(self):
2721
tt = self.make_destruction_preview()
2722
tt.deserialize(iter(self.destruction_records()))
2723
self.assertEqual({u'foo\u1234': 'new-1',
2725
'': tt.root}, tt._tree_path_ids)
2726
self.assertEqual({'new-1': u'foo\u1234',
2728
tt.root: ''}, tt._tree_id_paths)
2729
self.assertEqual(set(['new-1']), tt._removed_id)
2730
self.assertEqual(set(['new-2']), tt._removed_contents)
2732
def missing_records(self):
2733
attribs = self.default_attribs()
2734
attribs['_id_number'] = 2
2735
attribs['_non_present_ids'] = {
2737
return self.make_records(attribs, [])
2739
def test_serialize_missing(self):
2740
tt = self.get_preview()
2741
boo_trans_id = tt.trans_id_file_id('boo')
2742
self.assertSerializesTo(self.missing_records(), tt)
2744
def test_deserialize_missing(self):
2745
tt = self.get_preview()
2746
tt.deserialize(iter(self.missing_records()))
2747
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
2749
def make_modification_preview(self):
2750
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2751
LINES_TWO = 'z\nbb\nx\ndd\n'
2752
tree = self.make_branch_and_tree('tree')
2753
self.build_tree_contents([('tree/file', LINES_ONE)])
2754
tree.add('file', 'file-id')
2755
return self.get_preview(tree), LINES_TWO
2757
def modification_records(self):
2758
attribs = self.default_attribs()
2759
attribs['_id_number'] = 2
2760
attribs['_tree_path_ids'] = {
2763
attribs['_removed_contents'] = ['new-1']
2764
contents = [('new-1', 'file',
2765
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
2766
return self.make_records(attribs, contents)
2768
def test_serialize_modification(self):
2769
tt, LINES = self.make_modification_preview()
2770
trans_id = tt.trans_id_file_id('file-id')
2771
tt.delete_contents(trans_id)
2772
tt.create_file(LINES, trans_id)
2773
self.assertSerializesTo(self.modification_records(), tt)
2775
def test_deserialize_modification(self):
2776
tt, LINES = self.make_modification_preview()
2777
tt.deserialize(iter(self.modification_records()))
2778
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2780
def make_kind_change_preview(self):
2781
LINES = 'a\nb\nc\nd\n'
2782
tree = self.make_branch_and_tree('tree')
2783
self.build_tree(['tree/foo/'])
2784
tree.add('foo', 'foo-id')
2785
return self.get_preview(tree), LINES
2787
def kind_change_records(self):
2788
attribs = self.default_attribs()
2789
attribs['_id_number'] = 2
2790
attribs['_tree_path_ids'] = {
2793
attribs['_removed_contents'] = ['new-1']
2794
contents = [('new-1', 'file',
2795
'i 4\na\nb\nc\nd\n\n')]
2796
return self.make_records(attribs, contents)
2798
def test_serialize_kind_change(self):
2799
tt, LINES = self.make_kind_change_preview()
2800
trans_id = tt.trans_id_file_id('foo-id')
2801
tt.delete_contents(trans_id)
2802
tt.create_file(LINES, trans_id)
2803
self.assertSerializesTo(self.kind_change_records(), tt)
2805
def test_deserialize_kind_change(self):
2806
tt, LINES = self.make_kind_change_preview()
2807
tt.deserialize(iter(self.kind_change_records()))
2808
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2810
def make_add_contents_preview(self):
2811
LINES = 'a\nb\nc\nd\n'
2812
tree = self.make_branch_and_tree('tree')
2813
self.build_tree(['tree/foo'])
2815
os.unlink('tree/foo')
2816
return self.get_preview(tree), LINES
2818
def add_contents_records(self):
2819
attribs = self.default_attribs()
2820
attribs['_id_number'] = 2
2821
attribs['_tree_path_ids'] = {
2824
contents = [('new-1', 'file',
2825
'i 4\na\nb\nc\nd\n\n')]
2826
return self.make_records(attribs, contents)
2828
def test_serialize_add_contents(self):
2829
tt, LINES = self.make_add_contents_preview()
2830
trans_id = tt.trans_id_tree_path('foo')
2831
tt.create_file(LINES, trans_id)
2832
self.assertSerializesTo(self.add_contents_records(), tt)
2834
def test_deserialize_add_contents(self):
2835
tt, LINES = self.make_add_contents_preview()
2836
tt.deserialize(iter(self.add_contents_records()))
2837
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
2839
def test_get_parents_lines(self):
2840
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2841
LINES_TWO = 'z\nbb\nx\ndd\n'
2842
tree = self.make_branch_and_tree('tree')
2843
self.build_tree_contents([('tree/file', LINES_ONE)])
2844
tree.add('file', 'file-id')
2845
tt = self.get_preview(tree)
2846
trans_id = tt.trans_id_tree_path('file')
2847
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
2848
tt._get_parents_lines(trans_id))
2850
def test_get_parents_texts(self):
2851
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2852
LINES_TWO = 'z\nbb\nx\ndd\n'
2853
tree = self.make_branch_and_tree('tree')
2854
self.build_tree_contents([('tree/file', LINES_ONE)])
2855
tree.add('file', 'file-id')
2856
tt = self.get_preview(tree)
2857
trans_id = tt.trans_id_tree_path('file')
2858
self.assertEqual((LINES_ONE,),
2859
tt._get_parents_texts(trans_id))