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
28
revision as _mod_revision,
33
from bzrlib.bzrdir import BzrDir
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
35
UnversionedParent, ParentLoop, DeletingParent,
37
from bzrlib.diff import show_diff_trees
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
40
PathsNotVersionedError, ExistingLimbo,
41
ExistingPendingDeletion, ImmortalLimbo,
42
ImmortalPendingDeletion, LockError)
43
from bzrlib.osutils import file_kind, pathjoin
44
from bzrlib.merge import Merge3Merger, Merger
45
from bzrlib.tests import (
46
CaseInsensitiveFilesystemFeature,
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
54
resolve_conflicts, cook_conflicts,
55
build_tree, get_backup_name,
56
_FileMover, resolve_checkout,
57
TransformPreview, create_from_tree)
58
from bzrlib.util import bencode
61
class TestTreeTransform(tests.TestCaseWithTransport):
64
super(TestTreeTransform, self).setUp()
65
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
68
def get_transform(self):
69
transform = TreeTransform(self.wt)
70
self.addCleanup(transform.finalize)
71
return transform, transform.root
73
def test_existing_limbo(self):
74
transform, root = self.get_transform()
75
limbo_name = transform._limbodir
76
deletion_path = transform._deletiondir
77
os.mkdir(pathjoin(limbo_name, 'hehe'))
78
self.assertRaises(ImmortalLimbo, transform.apply)
79
self.assertRaises(LockError, self.wt.unlock)
80
self.assertRaises(ExistingLimbo, self.get_transform)
81
self.assertRaises(LockError, self.wt.unlock)
82
os.rmdir(pathjoin(limbo_name, 'hehe'))
84
os.rmdir(deletion_path)
85
transform, root = self.get_transform()
88
def test_existing_pending_deletion(self):
89
transform, root = self.get_transform()
90
deletion_path = self._limbodir = urlutils.local_path_from_url(
91
transform._tree._transport.abspath('pending-deletion'))
92
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
93
self.assertRaises(ImmortalPendingDeletion, transform.apply)
94
self.assertRaises(LockError, self.wt.unlock)
95
self.assertRaises(ExistingPendingDeletion, self.get_transform)
98
transform, root = self.get_transform()
99
self.wt.lock_tree_write()
100
self.addCleanup(self.wt.unlock)
101
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
102
imaginary_id = transform.trans_id_tree_path('imaginary')
103
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
104
self.assertEqual(imaginary_id, imaginary_id2)
105
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
106
self.assertEqual(transform.final_kind(root), 'directory')
107
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
108
trans_id = transform.create_path('name', root)
109
self.assertIs(transform.final_file_id(trans_id), None)
110
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
111
transform.create_file('contents', trans_id)
112
transform.set_executability(True, trans_id)
113
transform.version_file('my_pretties', trans_id)
114
self.assertRaises(DuplicateKey, transform.version_file,
115
'my_pretties', trans_id)
116
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
117
self.assertEqual(transform.final_parent(trans_id), root)
118
self.assertIs(transform.final_parent(root), ROOT_PARENT)
119
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
120
oz_id = transform.create_path('oz', root)
121
transform.create_directory(oz_id)
122
transform.version_file('ozzie', oz_id)
123
trans_id2 = transform.create_path('name2', root)
124
transform.create_file('contents', trans_id2)
125
transform.set_executability(False, trans_id2)
126
transform.version_file('my_pretties2', trans_id2)
127
modified_paths = transform.apply().modified_paths
128
self.assertEqual('contents', self.wt.get_file_byname('name').read())
129
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
130
self.assertIs(self.wt.is_executable('my_pretties'), True)
131
self.assertIs(self.wt.is_executable('my_pretties2'), False)
132
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
133
self.assertEqual(len(modified_paths), 3)
134
tree_mod_paths = [self.wt.id2abspath(f) for f in
135
('ozzie', 'my_pretties', 'my_pretties2')]
136
self.assertSubset(tree_mod_paths, modified_paths)
137
# is it safe to finalize repeatedly?
141
def test_hardlink(self):
142
self.requireFeature(HardlinkFeature)
143
transform, root = self.get_transform()
144
transform.new_file('file1', root, 'contents')
146
target = self.make_branch_and_tree('target')
147
target_transform = TreeTransform(target)
148
trans_id = target_transform.create_path('file1', target_transform.root)
149
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
150
target_transform.apply()
151
self.failUnlessExists('target/file1')
152
source_stat = os.stat(self.wt.abspath('file1'))
153
target_stat = os.stat('target/file1')
154
self.assertEqual(source_stat, target_stat)
156
def test_convenience(self):
157
transform, root = self.get_transform()
158
self.wt.lock_tree_write()
159
self.addCleanup(self.wt.unlock)
160
trans_id = transform.new_file('name', root, 'contents',
162
oz = transform.new_directory('oz', root, 'oz-id')
163
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
164
toto = transform.new_file('toto', dorothy, 'toto-contents',
167
self.assertEqual(len(transform.find_conflicts()), 0)
169
self.assertRaises(ReusingTransform, transform.find_conflicts)
170
self.assertEqual('contents', file(self.wt.abspath('name')).read())
171
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
172
self.assertIs(self.wt.is_executable('my_pretties'), True)
173
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
174
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
175
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
177
self.assertEqual('toto-contents',
178
self.wt.get_file_byname('oz/dorothy/toto').read())
179
self.assertIs(self.wt.is_executable('toto-id'), False)
181
def test_tree_reference(self):
182
transform, root = self.get_transform()
183
tree = transform._tree
184
trans_id = transform.new_directory('reference', root, 'subtree-id')
185
transform.set_tree_reference('subtree-revision', trans_id)
188
self.addCleanup(tree.unlock)
189
self.assertEqual('subtree-revision',
190
tree.inventory['subtree-id'].reference_revision)
192
def test_conflicts(self):
193
transform, root = self.get_transform()
194
trans_id = transform.new_file('name', root, 'contents',
196
self.assertEqual(len(transform.find_conflicts()), 0)
197
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
198
self.assertEqual(transform.find_conflicts(),
199
[('duplicate', trans_id, trans_id2, 'name')])
200
self.assertRaises(MalformedTransform, transform.apply)
201
transform.adjust_path('name', trans_id, trans_id2)
202
self.assertEqual(transform.find_conflicts(),
203
[('non-directory parent', trans_id)])
204
tinman_id = transform.trans_id_tree_path('tinman')
205
transform.adjust_path('name', tinman_id, trans_id2)
206
self.assertEqual(transform.find_conflicts(),
207
[('unversioned parent', tinman_id),
208
('missing parent', tinman_id)])
209
lion_id = transform.create_path('lion', root)
210
self.assertEqual(transform.find_conflicts(),
211
[('unversioned parent', tinman_id),
212
('missing parent', tinman_id)])
213
transform.adjust_path('name', lion_id, trans_id2)
214
self.assertEqual(transform.find_conflicts(),
215
[('unversioned parent', lion_id),
216
('missing parent', lion_id)])
217
transform.version_file("Courage", lion_id)
218
self.assertEqual(transform.find_conflicts(),
219
[('missing parent', lion_id),
220
('versioning no contents', lion_id)])
221
transform.adjust_path('name2', root, trans_id2)
222
self.assertEqual(transform.find_conflicts(),
223
[('versioning no contents', lion_id)])
224
transform.create_file('Contents, okay?', lion_id)
225
transform.adjust_path('name2', trans_id2, trans_id2)
226
self.assertEqual(transform.find_conflicts(),
227
[('parent loop', trans_id2),
228
('non-directory parent', trans_id2)])
229
transform.adjust_path('name2', root, trans_id2)
230
oz_id = transform.new_directory('oz', root)
231
transform.set_executability(True, oz_id)
232
self.assertEqual(transform.find_conflicts(),
233
[('unversioned executability', oz_id)])
234
transform.version_file('oz-id', oz_id)
235
self.assertEqual(transform.find_conflicts(),
236
[('non-file executability', oz_id)])
237
transform.set_executability(None, oz_id)
238
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
240
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
241
self.assertEqual('contents', file(self.wt.abspath('name')).read())
242
transform2, root = self.get_transform()
243
oz_id = transform2.trans_id_tree_file_id('oz-id')
244
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
245
result = transform2.find_conflicts()
246
fp = FinalPaths(transform2)
247
self.assert_('oz/tip' in transform2._tree_path_ids)
248
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
249
self.assertEqual(len(result), 2)
250
self.assertEqual((result[0][0], result[0][1]),
251
('duplicate', newtip))
252
self.assertEqual((result[1][0], result[1][2]),
253
('duplicate id', newtip))
254
transform2.finalize()
255
transform3 = TreeTransform(self.wt)
256
self.addCleanup(transform3.finalize)
257
oz_id = transform3.trans_id_tree_file_id('oz-id')
258
transform3.delete_contents(oz_id)
259
self.assertEqual(transform3.find_conflicts(),
260
[('missing parent', oz_id)])
261
root_id = transform3.root
262
tip_id = transform3.trans_id_tree_file_id('tip-id')
263
transform3.adjust_path('tip', root_id, tip_id)
266
def test_conflict_on_case_insensitive(self):
267
tree = self.make_branch_and_tree('tree')
268
# Don't try this at home, kids!
269
# Force the tree to report that it is case sensitive, for conflict
271
tree.case_sensitive = True
272
transform = TreeTransform(tree)
273
self.addCleanup(transform.finalize)
274
transform.new_file('file', transform.root, 'content')
275
transform.new_file('FiLe', transform.root, 'content')
276
result = transform.find_conflicts()
277
self.assertEqual([], result)
279
# Force the tree to report that it is case insensitive, for conflict
281
tree.case_sensitive = False
282
transform = TreeTransform(tree)
283
self.addCleanup(transform.finalize)
284
transform.new_file('file', transform.root, 'content')
285
transform.new_file('FiLe', transform.root, 'content')
286
result = transform.find_conflicts()
287
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
289
def test_conflict_on_case_insensitive_existing(self):
290
tree = self.make_branch_and_tree('tree')
291
self.build_tree(['tree/FiLe'])
292
# Don't try this at home, kids!
293
# Force the tree to report that it is case sensitive, for conflict
295
tree.case_sensitive = True
296
transform = TreeTransform(tree)
297
self.addCleanup(transform.finalize)
298
transform.new_file('file', transform.root, 'content')
299
result = transform.find_conflicts()
300
self.assertEqual([], result)
302
# Force the tree to report that it is case insensitive, for conflict
304
tree.case_sensitive = False
305
transform = TreeTransform(tree)
306
self.addCleanup(transform.finalize)
307
transform.new_file('file', transform.root, 'content')
308
result = transform.find_conflicts()
309
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
311
def test_resolve_case_insensitive_conflict(self):
312
tree = self.make_branch_and_tree('tree')
313
# Don't try this at home, kids!
314
# Force the tree to report that it is case insensitive, for conflict
316
tree.case_sensitive = False
317
transform = TreeTransform(tree)
318
self.addCleanup(transform.finalize)
319
transform.new_file('file', transform.root, 'content')
320
transform.new_file('FiLe', transform.root, 'content')
321
resolve_conflicts(transform)
323
self.failUnlessExists('tree/file')
324
self.failUnlessExists('tree/FiLe.moved')
326
def test_resolve_checkout_case_conflict(self):
327
tree = self.make_branch_and_tree('tree')
328
# Don't try this at home, kids!
329
# Force the tree to report that it is case insensitive, for conflict
331
tree.case_sensitive = False
332
transform = TreeTransform(tree)
333
self.addCleanup(transform.finalize)
334
transform.new_file('file', transform.root, 'content')
335
transform.new_file('FiLe', transform.root, 'content')
336
resolve_conflicts(transform,
337
pass_func=lambda t, c: resolve_checkout(t, c, []))
339
self.failUnlessExists('tree/file')
340
self.failUnlessExists('tree/FiLe.moved')
342
def test_apply_case_conflict(self):
343
"""Ensure that a transform with case conflicts can always be applied"""
344
tree = self.make_branch_and_tree('tree')
345
transform = TreeTransform(tree)
346
self.addCleanup(transform.finalize)
347
transform.new_file('file', transform.root, 'content')
348
transform.new_file('FiLe', transform.root, 'content')
349
dir = transform.new_directory('dir', transform.root)
350
transform.new_file('dirfile', dir, 'content')
351
transform.new_file('dirFiLe', dir, 'content')
352
resolve_conflicts(transform)
354
self.failUnlessExists('tree/file')
355
if not os.path.exists('tree/FiLe.moved'):
356
self.failUnlessExists('tree/FiLe')
357
self.failUnlessExists('tree/dir/dirfile')
358
if not os.path.exists('tree/dir/dirFiLe.moved'):
359
self.failUnlessExists('tree/dir/dirFiLe')
361
def test_case_insensitive_limbo(self):
362
tree = self.make_branch_and_tree('tree')
363
# Don't try this at home, kids!
364
# Force the tree to report that it is case insensitive
365
tree.case_sensitive = False
366
transform = TreeTransform(tree)
367
self.addCleanup(transform.finalize)
368
dir = transform.new_directory('dir', transform.root)
369
first = transform.new_file('file', dir, 'content')
370
second = transform.new_file('FiLe', dir, 'content')
371
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
372
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
374
def test_add_del(self):
375
start, root = self.get_transform()
376
start.new_directory('a', root, 'a')
378
transform, root = self.get_transform()
379
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
380
transform.new_directory('a', root, 'a')
383
def test_unversioning(self):
384
create_tree, root = self.get_transform()
385
parent_id = create_tree.new_directory('parent', root, 'parent-id')
386
create_tree.new_file('child', parent_id, 'child', 'child-id')
388
unversion = TreeTransform(self.wt)
389
self.addCleanup(unversion.finalize)
390
parent = unversion.trans_id_tree_path('parent')
391
unversion.unversion_file(parent)
392
self.assertEqual(unversion.find_conflicts(),
393
[('unversioned parent', parent_id)])
394
file_id = unversion.trans_id_tree_file_id('child-id')
395
unversion.unversion_file(file_id)
398
def test_name_invariants(self):
399
create_tree, root = self.get_transform()
401
root = create_tree.root
402
create_tree.new_file('name1', root, 'hello1', 'name1')
403
create_tree.new_file('name2', root, 'hello2', 'name2')
404
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
405
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
406
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
407
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
410
mangle_tree,root = self.get_transform()
411
root = mangle_tree.root
413
name1 = mangle_tree.trans_id_tree_file_id('name1')
414
name2 = mangle_tree.trans_id_tree_file_id('name2')
415
mangle_tree.adjust_path('name2', root, name1)
416
mangle_tree.adjust_path('name1', root, name2)
418
#tests for deleting parent directories
419
ddir = mangle_tree.trans_id_tree_file_id('ddir')
420
mangle_tree.delete_contents(ddir)
421
dfile = mangle_tree.trans_id_tree_file_id('dfile')
422
mangle_tree.delete_versioned(dfile)
423
mangle_tree.unversion_file(dfile)
424
mfile = mangle_tree.trans_id_tree_file_id('mfile')
425
mangle_tree.adjust_path('mfile', root, mfile)
427
#tests for adding parent directories
428
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
429
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
430
mangle_tree.adjust_path('mfile2', newdir, mfile2)
431
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
432
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
433
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
434
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
436
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
437
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
438
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
439
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
440
self.assertEqual(file(mfile2_path).read(), 'later2')
441
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
442
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
443
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
444
self.assertEqual(file(newfile_path).read(), 'hello3')
445
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
446
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
447
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
449
def test_both_rename(self):
450
create_tree,root = self.get_transform()
451
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
452
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
454
mangle_tree,root = self.get_transform()
455
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
456
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
457
mangle_tree.adjust_path('test', root, selftest)
458
mangle_tree.adjust_path('test_too_much', root, selftest)
459
mangle_tree.set_executability(True, blackbox)
462
def test_both_rename2(self):
463
create_tree,root = self.get_transform()
464
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
465
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
466
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
467
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
470
mangle_tree,root = self.get_transform()
471
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
472
tests = mangle_tree.trans_id_tree_file_id('tests-id')
473
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
474
mangle_tree.adjust_path('selftest', bzrlib, tests)
475
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
476
mangle_tree.set_executability(True, test_too_much)
479
def test_both_rename3(self):
480
create_tree,root = self.get_transform()
481
tests = create_tree.new_directory('tests', root, 'tests-id')
482
create_tree.new_file('test_too_much.py', tests, 'hello1',
485
mangle_tree,root = self.get_transform()
486
tests = mangle_tree.trans_id_tree_file_id('tests-id')
487
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
488
mangle_tree.adjust_path('selftest', root, tests)
489
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
490
mangle_tree.set_executability(True, test_too_much)
493
def test_move_dangling_ie(self):
494
create_tree, root = self.get_transform()
496
root = create_tree.root
497
create_tree.new_file('name1', root, 'hello1', 'name1')
499
delete_contents, root = self.get_transform()
500
file = delete_contents.trans_id_tree_file_id('name1')
501
delete_contents.delete_contents(file)
502
delete_contents.apply()
503
move_id, root = self.get_transform()
504
name1 = move_id.trans_id_tree_file_id('name1')
505
newdir = move_id.new_directory('dir', root, 'newdir')
506
move_id.adjust_path('name2', newdir, name1)
509
def test_replace_dangling_ie(self):
510
create_tree, root = self.get_transform()
512
root = create_tree.root
513
create_tree.new_file('name1', root, 'hello1', 'name1')
515
delete_contents = TreeTransform(self.wt)
516
self.addCleanup(delete_contents.finalize)
517
file = delete_contents.trans_id_tree_file_id('name1')
518
delete_contents.delete_contents(file)
519
delete_contents.apply()
520
delete_contents.finalize()
521
replace = TreeTransform(self.wt)
522
self.addCleanup(replace.finalize)
523
name2 = replace.new_file('name2', root, 'hello2', 'name1')
524
conflicts = replace.find_conflicts()
525
name1 = replace.trans_id_tree_file_id('name1')
526
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
527
resolve_conflicts(replace)
530
def test_symlinks(self):
531
self.requireFeature(SymlinkFeature)
532
transform,root = self.get_transform()
533
oz_id = transform.new_directory('oz', root, 'oz-id')
534
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
536
wiz_id = transform.create_path('wizard2', oz_id)
537
transform.create_symlink('behind_curtain', wiz_id)
538
transform.version_file('wiz-id2', wiz_id)
539
transform.set_executability(True, wiz_id)
540
self.assertEqual(transform.find_conflicts(),
541
[('non-file executability', wiz_id)])
542
transform.set_executability(None, wiz_id)
544
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
545
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
546
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
548
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
551
def test_unable_create_symlink(self):
553
wt = self.make_branch_and_tree('.')
554
tt = TreeTransform(wt) # TreeTransform obtains write lock
556
tt.new_symlink('foo', tt.root, 'bar')
560
os_symlink = getattr(os, 'symlink', None)
563
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
565
"Unable to create symlink 'foo' on this platform",
569
os.symlink = os_symlink
571
def get_conflicted(self):
572
create,root = self.get_transform()
573
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
574
oz = create.new_directory('oz', root, 'oz-id')
575
create.new_directory('emeraldcity', oz, 'emerald-id')
577
conflicts,root = self.get_transform()
578
# set up duplicate entry, duplicate id
579
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
581
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
582
oz = conflicts.trans_id_tree_file_id('oz-id')
583
# set up DeletedParent parent conflict
584
conflicts.delete_versioned(oz)
585
emerald = conflicts.trans_id_tree_file_id('emerald-id')
586
# set up MissingParent conflict
587
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
588
conflicts.adjust_path('munchkincity', root, munchkincity)
589
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
591
conflicts.adjust_path('emeraldcity', emerald, emerald)
592
return conflicts, emerald, oz, old_dorothy, new_dorothy
594
def test_conflict_resolution(self):
595
conflicts, emerald, oz, old_dorothy, new_dorothy =\
596
self.get_conflicted()
597
resolve_conflicts(conflicts)
598
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
599
self.assertIs(conflicts.final_file_id(old_dorothy), None)
600
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
601
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
602
self.assertEqual(conflicts.final_parent(emerald), oz)
605
def test_cook_conflicts(self):
606
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
607
raw_conflicts = resolve_conflicts(tt)
608
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
609
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
610
'dorothy', None, 'dorothy-id')
611
self.assertEqual(cooked_conflicts[0], duplicate)
612
duplicate_id = DuplicateID('Unversioned existing file',
613
'dorothy.moved', 'dorothy', None,
615
self.assertEqual(cooked_conflicts[1], duplicate_id)
616
missing_parent = MissingParent('Created directory', 'munchkincity',
618
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
619
self.assertEqual(cooked_conflicts[2], missing_parent)
620
unversioned_parent = UnversionedParent('Versioned directory',
623
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
625
self.assertEqual(cooked_conflicts[3], unversioned_parent)
626
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
627
'oz/emeraldcity', 'emerald-id', 'emerald-id')
628
self.assertEqual(cooked_conflicts[4], deleted_parent)
629
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
630
self.assertEqual(cooked_conflicts[6], parent_loop)
631
self.assertEqual(len(cooked_conflicts), 7)
634
def test_string_conflicts(self):
635
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
636
raw_conflicts = resolve_conflicts(tt)
637
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
639
conflicts_s = [str(c) for c in cooked_conflicts]
640
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
641
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
642
'Moved existing file to '
644
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
645
'Unversioned existing file '
647
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
648
' munchkincity. Created directory.')
649
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
650
' versioned, but has versioned'
651
' children. Versioned directory.')
652
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
653
" is not empty. Not deleting.")
654
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
655
' versioned, but has versioned'
656
' children. Versioned directory.')
657
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
658
' oz/emeraldcity. Cancelled move.')
660
def prepare_wrong_parent_kind(self):
661
tt, root = self.get_transform()
662
tt.new_file('parent', root, 'contents', 'parent-id')
664
tt, root = self.get_transform()
665
parent_id = tt.trans_id_file_id('parent-id')
666
tt.new_file('child,', parent_id, 'contents2', 'file-id')
669
def test_find_conflicts_wrong_parent_kind(self):
670
tt = self.prepare_wrong_parent_kind()
673
def test_resolve_conflicts_wrong_existing_parent_kind(self):
674
tt = self.prepare_wrong_parent_kind()
675
raw_conflicts = resolve_conflicts(tt)
676
self.assertEqual(set([('non-directory parent', 'Created directory',
677
'new-3')]), raw_conflicts)
678
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
679
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
680
'parent-id')], cooked_conflicts)
682
self.assertEqual(None, self.wt.path2id('parent'))
683
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
685
def test_resolve_conflicts_wrong_new_parent_kind(self):
686
tt, root = self.get_transform()
687
parent_id = tt.new_directory('parent', root, 'parent-id')
688
tt.new_file('child,', parent_id, 'contents2', 'file-id')
690
tt, root = self.get_transform()
691
parent_id = tt.trans_id_file_id('parent-id')
692
tt.delete_contents(parent_id)
693
tt.create_file('contents', parent_id)
694
raw_conflicts = resolve_conflicts(tt)
695
self.assertEqual(set([('non-directory parent', 'Created directory',
696
'new-3')]), raw_conflicts)
698
self.assertEqual(None, self.wt.path2id('parent'))
699
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
701
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
702
tt, root = self.get_transform()
703
parent_id = tt.new_directory('parent', root)
704
tt.new_file('child,', parent_id, 'contents2')
706
tt, root = self.get_transform()
707
parent_id = tt.trans_id_tree_path('parent')
708
tt.delete_contents(parent_id)
709
tt.create_file('contents', parent_id)
710
resolve_conflicts(tt)
712
self.assertIs(None, self.wt.path2id('parent'))
713
self.assertIs(None, self.wt.path2id('parent.new'))
715
def test_moving_versioned_directories(self):
716
create, root = self.get_transform()
717
kansas = create.new_directory('kansas', root, 'kansas-id')
718
create.new_directory('house', kansas, 'house-id')
719
create.new_directory('oz', root, 'oz-id')
721
cyclone, root = self.get_transform()
722
oz = cyclone.trans_id_tree_file_id('oz-id')
723
house = cyclone.trans_id_tree_file_id('house-id')
724
cyclone.adjust_path('house', oz, house)
727
def test_moving_root(self):
728
create, root = self.get_transform()
729
fun = create.new_directory('fun', root, 'fun-id')
730
create.new_directory('sun', root, 'sun-id')
731
create.new_directory('moon', root, 'moon')
733
transform, root = self.get_transform()
734
transform.adjust_root_path('oldroot', fun)
735
new_root=transform.trans_id_tree_path('')
736
transform.version_file('new-root', new_root)
739
def test_renames(self):
740
create, root = self.get_transform()
741
old = create.new_directory('old-parent', root, 'old-id')
742
intermediate = create.new_directory('intermediate', old, 'im-id')
743
myfile = create.new_file('myfile', intermediate, 'myfile-text',
746
rename, root = self.get_transform()
747
old = rename.trans_id_file_id('old-id')
748
rename.adjust_path('new', root, old)
749
myfile = rename.trans_id_file_id('myfile-id')
750
rename.set_executability(True, myfile)
753
def test_set_executability_order(self):
754
"""Ensure that executability behaves the same, no matter what order.
756
- create file and set executability simultaneously
757
- create file and set executability afterward
758
- unsetting the executability of a file whose executability has not been
759
declared should throw an exception (this may happen when a
760
merge attempts to create a file with a duplicate ID)
762
transform, root = self.get_transform()
765
self.addCleanup(wt.unlock)
766
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
768
sac = transform.new_file('set_after_creation', root,
769
'Set after creation', 'sac')
770
transform.set_executability(True, sac)
771
uws = transform.new_file('unset_without_set', root, 'Unset badly',
773
self.assertRaises(KeyError, transform.set_executability, None, uws)
775
self.assertTrue(wt.is_executable('soc'))
776
self.assertTrue(wt.is_executable('sac'))
778
def test_preserve_mode(self):
779
"""File mode is preserved when replacing content"""
780
if sys.platform == 'win32':
781
raise TestSkipped('chmod has no effect on win32')
782
transform, root = self.get_transform()
783
transform.new_file('file1', root, 'contents', 'file1-id', True)
786
self.addCleanup(self.wt.unlock)
787
self.assertTrue(self.wt.is_executable('file1-id'))
788
transform, root = self.get_transform()
789
file1_id = transform.trans_id_tree_file_id('file1-id')
790
transform.delete_contents(file1_id)
791
transform.create_file('contents2', file1_id)
793
self.assertTrue(self.wt.is_executable('file1-id'))
795
def test__set_mode_stats_correctly(self):
796
"""_set_mode stats to determine file mode."""
797
if sys.platform == 'win32':
798
raise TestSkipped('chmod has no effect on win32')
802
def instrumented_stat(path):
803
stat_paths.append(path)
804
return real_stat(path)
806
transform, root = self.get_transform()
808
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
809
file_id='bar-id-1', executable=False)
812
transform, root = self.get_transform()
813
bar1_id = transform.trans_id_tree_path('bar')
814
bar2_id = transform.trans_id_tree_path('bar2')
816
os.stat = instrumented_stat
817
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
822
bar1_abspath = self.wt.abspath('bar')
823
self.assertEqual([bar1_abspath], stat_paths)
825
def test_iter_changes(self):
826
self.wt.set_root_id('eert_toor')
827
transform, root = self.get_transform()
828
transform.new_file('old', root, 'blah', 'id-1', True)
830
transform, root = self.get_transform()
832
self.assertEqual([], list(transform.iter_changes()))
833
old = transform.trans_id_tree_file_id('id-1')
834
transform.unversion_file(old)
835
self.assertEqual([('id-1', ('old', None), False, (True, False),
836
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
837
(True, True))], list(transform.iter_changes()))
838
transform.new_directory('new', root, 'id-1')
839
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
840
('eert_toor', 'eert_toor'), ('old', 'new'),
841
('file', 'directory'),
842
(True, False))], list(transform.iter_changes()))
846
def test_iter_changes_new(self):
847
self.wt.set_root_id('eert_toor')
848
transform, root = self.get_transform()
849
transform.new_file('old', root, 'blah')
851
transform, root = self.get_transform()
853
old = transform.trans_id_tree_path('old')
854
transform.version_file('id-1', old)
855
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
856
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
857
(False, False))], list(transform.iter_changes()))
861
def test_iter_changes_modifications(self):
862
self.wt.set_root_id('eert_toor')
863
transform, root = self.get_transform()
864
transform.new_file('old', root, 'blah', 'id-1')
865
transform.new_file('new', root, 'blah')
866
transform.new_directory('subdir', root, 'subdir-id')
868
transform, root = self.get_transform()
870
old = transform.trans_id_tree_path('old')
871
subdir = transform.trans_id_tree_file_id('subdir-id')
872
new = transform.trans_id_tree_path('new')
873
self.assertEqual([], list(transform.iter_changes()))
876
transform.delete_contents(old)
877
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
878
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
879
(False, False))], list(transform.iter_changes()))
882
transform.create_file('blah', old)
883
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
884
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
885
(False, False))], list(transform.iter_changes()))
886
transform.cancel_deletion(old)
887
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
888
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
889
(False, False))], list(transform.iter_changes()))
890
transform.cancel_creation(old)
892
# move file_id to a different file
893
self.assertEqual([], list(transform.iter_changes()))
894
transform.unversion_file(old)
895
transform.version_file('id-1', new)
896
transform.adjust_path('old', root, new)
897
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
898
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
899
(False, False))], list(transform.iter_changes()))
900
transform.cancel_versioning(new)
901
transform._removed_id = set()
904
self.assertEqual([], list(transform.iter_changes()))
905
transform.set_executability(True, old)
906
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
907
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
908
(False, True))], list(transform.iter_changes()))
909
transform.set_executability(None, old)
912
self.assertEqual([], list(transform.iter_changes()))
913
transform.adjust_path('new', root, old)
914
transform._new_parent = {}
915
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
916
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
917
(False, False))], list(transform.iter_changes()))
918
transform._new_name = {}
921
self.assertEqual([], list(transform.iter_changes()))
922
transform.adjust_path('new', subdir, old)
923
transform._new_name = {}
924
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
925
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
926
('file', 'file'), (False, False))],
927
list(transform.iter_changes()))
928
transform._new_path = {}
933
def test_iter_changes_modified_bleed(self):
934
self.wt.set_root_id('eert_toor')
935
"""Modified flag should not bleed from one change to another"""
936
# unfortunately, we have no guarantee that file1 (which is modified)
937
# will be applied before file2. And if it's applied after file2, it
938
# obviously can't bleed into file2's change output. But for now, it
940
transform, root = self.get_transform()
941
transform.new_file('file1', root, 'blah', 'id-1')
942
transform.new_file('file2', root, 'blah', 'id-2')
944
transform, root = self.get_transform()
946
transform.delete_contents(transform.trans_id_file_id('id-1'))
947
transform.set_executability(True,
948
transform.trans_id_file_id('id-2'))
949
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
950
('eert_toor', 'eert_toor'), ('file1', u'file1'),
951
('file', None), (False, False)),
952
('id-2', (u'file2', u'file2'), False, (True, True),
953
('eert_toor', 'eert_toor'), ('file2', u'file2'),
954
('file', 'file'), (False, True))],
955
list(transform.iter_changes()))
959
def test_iter_changes_move_missing(self):
960
"""Test moving ids with no files around"""
961
self.wt.set_root_id('toor_eert')
962
# Need two steps because versioning a non-existant file is a conflict.
963
transform, root = self.get_transform()
964
transform.new_directory('floater', root, 'floater-id')
966
transform, root = self.get_transform()
967
transform.delete_contents(transform.trans_id_tree_path('floater'))
969
transform, root = self.get_transform()
970
floater = transform.trans_id_tree_path('floater')
972
transform.adjust_path('flitter', root, floater)
973
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
974
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
975
(None, None), (False, False))], list(transform.iter_changes()))
979
def test_iter_changes_pointless(self):
980
"""Ensure that no-ops are not treated as modifications"""
981
self.wt.set_root_id('eert_toor')
982
transform, root = self.get_transform()
983
transform.new_file('old', root, 'blah', 'id-1')
984
transform.new_directory('subdir', root, 'subdir-id')
986
transform, root = self.get_transform()
988
old = transform.trans_id_tree_path('old')
989
subdir = transform.trans_id_tree_file_id('subdir-id')
990
self.assertEqual([], list(transform.iter_changes()))
991
transform.delete_contents(subdir)
992
transform.create_directory(subdir)
993
transform.set_executability(False, old)
994
transform.unversion_file(old)
995
transform.version_file('id-1', old)
996
transform.adjust_path('old', root, old)
997
self.assertEqual([], list(transform.iter_changes()))
1001
def test_rename_count(self):
1002
transform, root = self.get_transform()
1003
transform.new_file('name1', root, 'contents')
1004
self.assertEqual(transform.rename_count, 0)
1006
self.assertEqual(transform.rename_count, 1)
1007
transform2, root = self.get_transform()
1008
transform2.adjust_path('name2', root,
1009
transform2.trans_id_tree_path('name1'))
1010
self.assertEqual(transform2.rename_count, 0)
1012
self.assertEqual(transform2.rename_count, 2)
1014
def test_change_parent(self):
1015
"""Ensure that after we change a parent, the results are still right.
1017
Renames and parent changes on pending transforms can happen as part
1018
of conflict resolution, and are explicitly permitted by the
1021
This test ensures they work correctly with the rename-avoidance
1024
transform, root = self.get_transform()
1025
parent1 = transform.new_directory('parent1', root)
1026
child1 = transform.new_file('child1', parent1, 'contents')
1027
parent2 = transform.new_directory('parent2', root)
1028
transform.adjust_path('child1', parent2, child1)
1030
self.failIfExists(self.wt.abspath('parent1/child1'))
1031
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1032
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1033
# no rename for child1 (counting only renames during apply)
1034
self.failUnlessEqual(2, transform.rename_count)
1036
def test_cancel_parent(self):
1037
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1039
This is like the test_change_parent, except that we cancel the parent
1040
before adjusting the path. The transform must detect that the
1041
directory is non-empty, and move children to safe locations.
1043
transform, root = self.get_transform()
1044
parent1 = transform.new_directory('parent1', root)
1045
child1 = transform.new_file('child1', parent1, 'contents')
1046
child2 = transform.new_file('child2', parent1, 'contents')
1048
transform.cancel_creation(parent1)
1050
self.fail('Failed to move child1 before deleting parent1')
1051
transform.cancel_creation(child2)
1052
transform.create_directory(parent1)
1054
transform.cancel_creation(parent1)
1055
# If the transform incorrectly believes that child2 is still in
1056
# parent1's limbo directory, it will try to rename it and fail
1057
# because was already moved by the first cancel_creation.
1059
self.fail('Transform still thinks child2 is a child of parent1')
1060
parent2 = transform.new_directory('parent2', root)
1061
transform.adjust_path('child1', parent2, child1)
1063
self.failIfExists(self.wt.abspath('parent1'))
1064
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1065
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1066
self.failUnlessEqual(2, transform.rename_count)
1068
def test_adjust_and_cancel(self):
1069
"""Make sure adjust_path keeps track of limbo children properly"""
1070
transform, root = self.get_transform()
1071
parent1 = transform.new_directory('parent1', root)
1072
child1 = transform.new_file('child1', parent1, 'contents')
1073
parent2 = transform.new_directory('parent2', root)
1074
transform.adjust_path('child1', parent2, child1)
1075
transform.cancel_creation(child1)
1077
transform.cancel_creation(parent1)
1078
# if the transform thinks child1 is still in parent1's limbo
1079
# directory, it will attempt to move it and fail.
1081
self.fail('Transform still thinks child1 is a child of parent1')
1082
transform.finalize()
1084
def test_noname_contents(self):
1085
"""TreeTransform should permit deferring naming files."""
1086
transform, root = self.get_transform()
1087
parent = transform.trans_id_file_id('parent-id')
1089
transform.create_directory(parent)
1091
self.fail("Can't handle contents with no name")
1092
transform.finalize()
1094
def test_noname_contents_nested(self):
1095
"""TreeTransform should permit deferring naming files."""
1096
transform, root = self.get_transform()
1097
parent = transform.trans_id_file_id('parent-id')
1099
transform.create_directory(parent)
1101
self.fail("Can't handle contents with no name")
1102
child = transform.new_directory('child', parent)
1103
transform.adjust_path('parent', root, parent)
1105
self.failUnlessExists(self.wt.abspath('parent/child'))
1106
self.assertEqual(1, transform.rename_count)
1108
def test_reuse_name(self):
1109
"""Avoid reusing the same limbo name for different files"""
1110
transform, root = self.get_transform()
1111
parent = transform.new_directory('parent', root)
1112
child1 = transform.new_directory('child', parent)
1114
child2 = transform.new_directory('child', parent)
1116
self.fail('Tranform tried to use the same limbo name twice')
1117
transform.adjust_path('child2', parent, child2)
1119
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1120
# child2 is put into top-level limbo because child1 has already
1121
# claimed the direct limbo path when child2 is created. There is no
1122
# advantage in renaming files once they're in top-level limbo, except
1124
self.assertEqual(2, transform.rename_count)
1126
def test_reuse_when_first_moved(self):
1127
"""Don't avoid direct paths when it is safe to use them"""
1128
transform, root = self.get_transform()
1129
parent = transform.new_directory('parent', root)
1130
child1 = transform.new_directory('child', parent)
1131
transform.adjust_path('child1', parent, child1)
1132
child2 = transform.new_directory('child', parent)
1134
# limbo/new-1 => parent
1135
self.assertEqual(1, transform.rename_count)
1137
def test_reuse_after_cancel(self):
1138
"""Don't avoid direct paths when it is safe to use them"""
1139
transform, root = self.get_transform()
1140
parent2 = transform.new_directory('parent2', root)
1141
child1 = transform.new_directory('child1', parent2)
1142
transform.cancel_creation(parent2)
1143
transform.create_directory(parent2)
1144
child2 = transform.new_directory('child1', parent2)
1145
transform.adjust_path('child2', parent2, child1)
1147
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1148
self.assertEqual(2, transform.rename_count)
1150
def test_finalize_order(self):
1151
"""Finalize must be done in child-to-parent order"""
1152
transform, root = self.get_transform()
1153
parent = transform.new_directory('parent', root)
1154
child = transform.new_directory('child', parent)
1156
transform.finalize()
1158
self.fail('Tried to remove parent before child1')
1160
def test_cancel_with_cancelled_child_should_succeed(self):
1161
transform, root = self.get_transform()
1162
parent = transform.new_directory('parent', root)
1163
child = transform.new_directory('child', parent)
1164
transform.cancel_creation(child)
1165
transform.cancel_creation(parent)
1166
transform.finalize()
1168
def test_rollback_on_directory_clash(self):
1170
wt = self.make_branch_and_tree('.')
1171
tt = TreeTransform(wt) # TreeTransform obtains write lock
1173
foo = tt.new_directory('foo', tt.root)
1174
tt.new_file('bar', foo, 'foobar')
1175
baz = tt.new_directory('baz', tt.root)
1176
tt.new_file('qux', baz, 'quux')
1177
# Ask for a rename 'foo' -> 'baz'
1178
tt.adjust_path('baz', tt.root, foo)
1179
# Lie to tt that we've already resolved all conflicts.
1180
tt.apply(no_conflicts=True)
1184
# The rename will fail because the target directory is not empty (but
1185
# raises FileExists anyway).
1186
err = self.assertRaises(errors.FileExists, tt_helper)
1187
self.assertContainsRe(str(err),
1188
"^File exists: .+/baz")
1190
def test_two_directories_clash(self):
1192
wt = self.make_branch_and_tree('.')
1193
tt = TreeTransform(wt) # TreeTransform obtains write lock
1195
foo_1 = tt.new_directory('foo', tt.root)
1196
tt.new_directory('bar', foo_1)
1197
# Adding the same directory with a different content
1198
foo_2 = tt.new_directory('foo', tt.root)
1199
tt.new_directory('baz', foo_2)
1200
# Lie to tt that we've already resolved all conflicts.
1201
tt.apply(no_conflicts=True)
1205
err = self.assertRaises(errors.FileExists, tt_helper)
1206
self.assertContainsRe(str(err),
1207
"^File exists: .+/foo")
1209
def test_two_directories_clash_finalize(self):
1211
wt = self.make_branch_and_tree('.')
1212
tt = TreeTransform(wt) # TreeTransform obtains write lock
1214
foo_1 = tt.new_directory('foo', tt.root)
1215
tt.new_directory('bar', foo_1)
1216
# Adding the same directory with a different content
1217
foo_2 = tt.new_directory('foo', tt.root)
1218
tt.new_directory('baz', foo_2)
1219
# Lie to tt that we've already resolved all conflicts.
1220
tt.apply(no_conflicts=True)
1224
err = self.assertRaises(errors.FileExists, tt_helper)
1225
self.assertContainsRe(str(err),
1226
"^File exists: .+/foo")
1228
def test_file_to_directory(self):
1229
wt = self.make_branch_and_tree('.')
1230
self.build_tree(['foo'])
1233
tt = TreeTransform(wt)
1234
self.addCleanup(tt.finalize)
1235
foo_trans_id = tt.trans_id_tree_path("foo")
1236
tt.delete_contents(foo_trans_id)
1237
tt.create_directory(foo_trans_id)
1238
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1239
tt.create_file(["aa\n"], bar_trans_id)
1240
tt.version_file("bar-1", bar_trans_id)
1242
self.failUnlessExists("foo/bar")
1245
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1250
changes = wt.changes_from(wt.basis_tree())
1251
self.assertFalse(changes.has_changed(), changes)
1253
def test_file_to_symlink(self):
1254
self.requireFeature(SymlinkFeature)
1255
wt = self.make_branch_and_tree('.')
1256
self.build_tree(['foo'])
1259
tt = TreeTransform(wt)
1260
self.addCleanup(tt.finalize)
1261
foo_trans_id = tt.trans_id_tree_path("foo")
1262
tt.delete_contents(foo_trans_id)
1263
tt.create_symlink("bar", foo_trans_id)
1265
self.failUnlessExists("foo")
1267
self.addCleanup(wt.unlock)
1268
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1271
def test_dir_to_file(self):
1272
wt = self.make_branch_and_tree('.')
1273
self.build_tree(['foo/', 'foo/bar'])
1274
wt.add(['foo', 'foo/bar'])
1276
tt = TreeTransform(wt)
1277
self.addCleanup(tt.finalize)
1278
foo_trans_id = tt.trans_id_tree_path("foo")
1279
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1280
tt.delete_contents(foo_trans_id)
1281
tt.delete_versioned(bar_trans_id)
1282
tt.create_file(["aa\n"], foo_trans_id)
1284
self.failUnlessExists("foo")
1286
self.addCleanup(wt.unlock)
1287
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1290
def test_dir_to_hardlink(self):
1291
self.requireFeature(HardlinkFeature)
1292
wt = self.make_branch_and_tree('.')
1293
self.build_tree(['foo/', 'foo/bar'])
1294
wt.add(['foo', 'foo/bar'])
1296
tt = TreeTransform(wt)
1297
self.addCleanup(tt.finalize)
1298
foo_trans_id = tt.trans_id_tree_path("foo")
1299
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1300
tt.delete_contents(foo_trans_id)
1301
tt.delete_versioned(bar_trans_id)
1302
self.build_tree(['baz'])
1303
tt.create_hardlink("baz", foo_trans_id)
1305
self.failUnlessExists("foo")
1306
self.failUnlessExists("baz")
1308
self.addCleanup(wt.unlock)
1309
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1312
def test_no_final_path(self):
1313
transform, root = self.get_transform()
1314
trans_id = transform.trans_id_file_id('foo')
1315
transform.create_file('bar', trans_id)
1316
transform.cancel_creation(trans_id)
1319
def test_create_from_tree(self):
1320
tree1 = self.make_branch_and_tree('tree1')
1321
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1322
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1323
tree2 = self.make_branch_and_tree('tree2')
1324
tt = TreeTransform(tree2)
1325
foo_trans_id = tt.create_path('foo', tt.root)
1326
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1327
bar_trans_id = tt.create_path('bar', tt.root)
1328
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1330
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1331
self.assertFileEqual('baz', 'tree2/bar')
1333
def test_create_from_tree_bytes(self):
1334
"""Provided lines are used instead of tree content."""
1335
tree1 = self.make_branch_and_tree('tree1')
1336
self.build_tree_contents([('tree1/foo', 'bar'),])
1337
tree1.add('foo', 'foo-id')
1338
tree2 = self.make_branch_and_tree('tree2')
1339
tt = TreeTransform(tree2)
1340
foo_trans_id = tt.create_path('foo', tt.root)
1341
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1343
self.assertFileEqual('qux', 'tree2/foo')
1345
def test_create_from_tree_symlink(self):
1346
self.requireFeature(SymlinkFeature)
1347
tree1 = self.make_branch_and_tree('tree1')
1348
os.symlink('bar', 'tree1/foo')
1349
tree1.add('foo', 'foo-id')
1350
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1351
foo_trans_id = tt.create_path('foo', tt.root)
1352
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1354
self.assertEqual('bar', os.readlink('tree2/foo'))
1357
class TransformGroup(object):
1359
def __init__(self, dirname, root_id):
1362
self.wt = BzrDir.create_standalone_workingtree(dirname)
1363
self.wt.set_root_id(root_id)
1364
self.b = self.wt.branch
1365
self.tt = TreeTransform(self.wt)
1366
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1369
def conflict_text(tree, merge):
1370
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1371
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1374
class TestTransformMerge(TestCaseInTempDir):
1376
def test_text_merge(self):
1377
root_id = generate_ids.gen_root_id()
1378
base = TransformGroup("base", root_id)
1379
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1380
base.tt.new_file('b', base.root, 'b1', 'b')
1381
base.tt.new_file('c', base.root, 'c', 'c')
1382
base.tt.new_file('d', base.root, 'd', 'd')
1383
base.tt.new_file('e', base.root, 'e', 'e')
1384
base.tt.new_file('f', base.root, 'f', 'f')
1385
base.tt.new_directory('g', base.root, 'g')
1386
base.tt.new_directory('h', base.root, 'h')
1388
other = TransformGroup("other", root_id)
1389
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1390
other.tt.new_file('b', other.root, 'b2', 'b')
1391
other.tt.new_file('c', other.root, 'c2', 'c')
1392
other.tt.new_file('d', other.root, 'd', 'd')
1393
other.tt.new_file('e', other.root, 'e2', 'e')
1394
other.tt.new_file('f', other.root, 'f', 'f')
1395
other.tt.new_file('g', other.root, 'g', 'g')
1396
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1397
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1399
this = TransformGroup("this", root_id)
1400
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1401
this.tt.new_file('b', this.root, 'b', 'b')
1402
this.tt.new_file('c', this.root, 'c', 'c')
1403
this.tt.new_file('d', this.root, 'd2', 'd')
1404
this.tt.new_file('e', this.root, 'e2', 'e')
1405
this.tt.new_file('f', this.root, 'f', 'f')
1406
this.tt.new_file('g', this.root, 'g', 'g')
1407
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1408
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1410
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1413
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1414
# three-way text conflict
1415
self.assertEqual(this.wt.get_file('b').read(),
1416
conflict_text('b', 'b2'))
1418
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1420
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1421
# Ambigious clean merge
1422
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1424
self.assertEqual(this.wt.get_file('f').read(), 'f')
1425
# Correct correct results when THIS == OTHER
1426
self.assertEqual(this.wt.get_file('g').read(), 'g')
1427
# Text conflict when THIS & OTHER are text and BASE is dir
1428
self.assertEqual(this.wt.get_file('h').read(),
1429
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1430
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1432
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1434
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1435
self.assertEqual(this.wt.get_file('i').read(),
1436
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1437
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1439
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1441
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1442
modified = ['a', 'b', 'c', 'h', 'i']
1443
merge_modified = this.wt.merge_modified()
1444
self.assertSubset(merge_modified, modified)
1445
self.assertEqual(len(merge_modified), len(modified))
1446
file(this.wt.id2abspath('a'), 'wb').write('booga')
1448
merge_modified = this.wt.merge_modified()
1449
self.assertSubset(merge_modified, modified)
1450
self.assertEqual(len(merge_modified), len(modified))
1454
def test_file_merge(self):
1455
self.requireFeature(SymlinkFeature)
1456
root_id = generate_ids.gen_root_id()
1457
base = TransformGroup("BASE", root_id)
1458
this = TransformGroup("THIS", root_id)
1459
other = TransformGroup("OTHER", root_id)
1460
for tg in this, base, other:
1461
tg.tt.new_directory('a', tg.root, 'a')
1462
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1463
tg.tt.new_file('c', tg.root, 'c', 'c')
1464
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1465
targets = ((base, 'base-e', 'base-f', None, None),
1466
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1467
(other, 'other-e', None, 'other-g', 'other-h'))
1468
for tg, e_target, f_target, g_target, h_target in targets:
1469
for link, target in (('e', e_target), ('f', f_target),
1470
('g', g_target), ('h', h_target)):
1471
if target is not None:
1472
tg.tt.new_symlink(link, tg.root, target, link)
1474
for tg in this, base, other:
1476
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1477
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1478
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1479
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1480
for suffix in ('THIS', 'BASE', 'OTHER'):
1481
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1482
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1483
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1484
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1485
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1486
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1487
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1488
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1489
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1490
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1491
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1492
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1493
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1494
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1496
def test_filename_merge(self):
1497
root_id = generate_ids.gen_root_id()
1498
base = TransformGroup("BASE", root_id)
1499
this = TransformGroup("THIS", root_id)
1500
other = TransformGroup("OTHER", root_id)
1501
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1502
for t in [base, this, other]]
1503
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1504
for t in [base, this, other]]
1505
base.tt.new_directory('c', base_a, 'c')
1506
this.tt.new_directory('c1', this_a, 'c')
1507
other.tt.new_directory('c', other_b, 'c')
1509
base.tt.new_directory('d', base_a, 'd')
1510
this.tt.new_directory('d1', this_b, 'd')
1511
other.tt.new_directory('d', other_a, 'd')
1513
base.tt.new_directory('e', base_a, 'e')
1514
this.tt.new_directory('e', this_a, 'e')
1515
other.tt.new_directory('e1', other_b, 'e')
1517
base.tt.new_directory('f', base_a, 'f')
1518
this.tt.new_directory('f1', this_b, 'f')
1519
other.tt.new_directory('f1', other_b, 'f')
1521
for tg in [this, base, other]:
1523
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1524
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1525
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1526
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1527
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1529
def test_filename_merge_conflicts(self):
1530
root_id = generate_ids.gen_root_id()
1531
base = TransformGroup("BASE", root_id)
1532
this = TransformGroup("THIS", root_id)
1533
other = TransformGroup("OTHER", root_id)
1534
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1535
for t in [base, this, other]]
1536
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1537
for t in [base, this, other]]
1539
base.tt.new_file('g', base_a, 'g', 'g')
1540
other.tt.new_file('g1', other_b, 'g1', 'g')
1542
base.tt.new_file('h', base_a, 'h', 'h')
1543
this.tt.new_file('h1', this_b, 'h1', 'h')
1545
base.tt.new_file('i', base.root, 'i', 'i')
1546
other.tt.new_directory('i1', this_b, 'i')
1548
for tg in [this, base, other]:
1550
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1552
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1553
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1554
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1555
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1556
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1557
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1558
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1561
class TestBuildTree(tests.TestCaseWithTransport):
1563
def test_build_tree_with_symlinks(self):
1564
self.requireFeature(SymlinkFeature)
1566
a = BzrDir.create_standalone_workingtree('a')
1568
file('a/foo/bar', 'wb').write('contents')
1569
os.symlink('a/foo/bar', 'a/foo/baz')
1570
a.add(['foo', 'foo/bar', 'foo/baz'])
1571
a.commit('initial commit')
1572
b = BzrDir.create_standalone_workingtree('b')
1573
basis = a.basis_tree()
1575
self.addCleanup(basis.unlock)
1576
build_tree(basis, b)
1577
self.assertIs(os.path.isdir('b/foo'), True)
1578
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1579
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1581
def test_build_with_references(self):
1582
tree = self.make_branch_and_tree('source',
1583
format='dirstate-with-subtree')
1584
subtree = self.make_branch_and_tree('source/subtree',
1585
format='dirstate-with-subtree')
1586
tree.add_reference(subtree)
1587
tree.commit('a revision')
1588
tree.branch.create_checkout('target')
1589
self.failUnlessExists('target')
1590
self.failUnlessExists('target/subtree')
1592
def test_file_conflict_handling(self):
1593
"""Ensure that when building trees, conflict handling is done"""
1594
source = self.make_branch_and_tree('source')
1595
target = self.make_branch_and_tree('target')
1596
self.build_tree(['source/file', 'target/file'])
1597
source.add('file', 'new-file')
1598
source.commit('added file')
1599
build_tree(source.basis_tree(), target)
1600
self.assertEqual([DuplicateEntry('Moved existing file to',
1601
'file.moved', 'file', None, 'new-file')],
1603
target2 = self.make_branch_and_tree('target2')
1604
target_file = file('target2/file', 'wb')
1606
source_file = file('source/file', 'rb')
1608
target_file.write(source_file.read())
1613
build_tree(source.basis_tree(), target2)
1614
self.assertEqual([], target2.conflicts())
1616
def test_symlink_conflict_handling(self):
1617
"""Ensure that when building trees, conflict handling is done"""
1618
self.requireFeature(SymlinkFeature)
1619
source = self.make_branch_and_tree('source')
1620
os.symlink('foo', 'source/symlink')
1621
source.add('symlink', 'new-symlink')
1622
source.commit('added file')
1623
target = self.make_branch_and_tree('target')
1624
os.symlink('bar', 'target/symlink')
1625
build_tree(source.basis_tree(), target)
1626
self.assertEqual([DuplicateEntry('Moved existing file to',
1627
'symlink.moved', 'symlink', None, 'new-symlink')],
1629
target = self.make_branch_and_tree('target2')
1630
os.symlink('foo', 'target2/symlink')
1631
build_tree(source.basis_tree(), target)
1632
self.assertEqual([], target.conflicts())
1634
def test_directory_conflict_handling(self):
1635
"""Ensure that when building trees, conflict handling is done"""
1636
source = self.make_branch_and_tree('source')
1637
target = self.make_branch_and_tree('target')
1638
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1639
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1640
source.commit('added file')
1641
build_tree(source.basis_tree(), target)
1642
self.assertEqual([], target.conflicts())
1643
self.failUnlessExists('target/dir1/file')
1645
# Ensure contents are merged
1646
target = self.make_branch_and_tree('target2')
1647
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1648
build_tree(source.basis_tree(), target)
1649
self.assertEqual([], target.conflicts())
1650
self.failUnlessExists('target2/dir1/file2')
1651
self.failUnlessExists('target2/dir1/file')
1653
# Ensure new contents are suppressed for existing branches
1654
target = self.make_branch_and_tree('target3')
1655
self.make_branch('target3/dir1')
1656
self.build_tree(['target3/dir1/file2'])
1657
build_tree(source.basis_tree(), target)
1658
self.failIfExists('target3/dir1/file')
1659
self.failUnlessExists('target3/dir1/file2')
1660
self.failUnlessExists('target3/dir1.diverted/file')
1661
self.assertEqual([DuplicateEntry('Diverted to',
1662
'dir1.diverted', 'dir1', 'new-dir1', None)],
1665
target = self.make_branch_and_tree('target4')
1666
self.build_tree(['target4/dir1/'])
1667
self.make_branch('target4/dir1/file')
1668
build_tree(source.basis_tree(), target)
1669
self.failUnlessExists('target4/dir1/file')
1670
self.assertEqual('directory', file_kind('target4/dir1/file'))
1671
self.failUnlessExists('target4/dir1/file.diverted')
1672
self.assertEqual([DuplicateEntry('Diverted to',
1673
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1676
def test_mixed_conflict_handling(self):
1677
"""Ensure that when building trees, conflict handling is done"""
1678
source = self.make_branch_and_tree('source')
1679
target = self.make_branch_and_tree('target')
1680
self.build_tree(['source/name', 'target/name/'])
1681
source.add('name', 'new-name')
1682
source.commit('added file')
1683
build_tree(source.basis_tree(), target)
1684
self.assertEqual([DuplicateEntry('Moved existing file to',
1685
'name.moved', 'name', None, 'new-name')], target.conflicts())
1687
def test_raises_in_populated(self):
1688
source = self.make_branch_and_tree('source')
1689
self.build_tree(['source/name'])
1691
source.commit('added name')
1692
target = self.make_branch_and_tree('target')
1693
self.build_tree(['target/name'])
1695
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1696
build_tree, source.basis_tree(), target)
1698
def test_build_tree_rename_count(self):
1699
source = self.make_branch_and_tree('source')
1700
self.build_tree(['source/file1', 'source/dir1/'])
1701
source.add(['file1', 'dir1'])
1702
source.commit('add1')
1703
target1 = self.make_branch_and_tree('target1')
1704
transform_result = build_tree(source.basis_tree(), target1)
1705
self.assertEqual(2, transform_result.rename_count)
1707
self.build_tree(['source/dir1/file2'])
1708
source.add(['dir1/file2'])
1709
source.commit('add3')
1710
target2 = self.make_branch_and_tree('target2')
1711
transform_result = build_tree(source.basis_tree(), target2)
1712
# children of non-root directories should not be renamed
1713
self.assertEqual(2, transform_result.rename_count)
1715
def create_ab_tree(self):
1716
"""Create a committed test tree with two files"""
1717
source = self.make_branch_and_tree('source')
1718
self.build_tree_contents([('source/file1', 'A')])
1719
self.build_tree_contents([('source/file2', 'B')])
1720
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1721
source.commit('commit files')
1723
self.addCleanup(source.unlock)
1726
def test_build_tree_accelerator_tree(self):
1727
source = self.create_ab_tree()
1728
self.build_tree_contents([('source/file2', 'C')])
1730
real_source_get_file = source.get_file
1731
def get_file(file_id, path=None):
1732
calls.append(file_id)
1733
return real_source_get_file(file_id, path)
1734
source.get_file = get_file
1735
target = self.make_branch_and_tree('target')
1736
revision_tree = source.basis_tree()
1737
revision_tree.lock_read()
1738
self.addCleanup(revision_tree.unlock)
1739
build_tree(revision_tree, target, source)
1740
self.assertEqual(['file1-id'], calls)
1742
self.addCleanup(target.unlock)
1743
self.assertEqual([], list(target.iter_changes(revision_tree)))
1745
def test_build_tree_accelerator_tree_missing_file(self):
1746
source = self.create_ab_tree()
1747
os.unlink('source/file1')
1748
source.remove(['file2'])
1749
target = self.make_branch_and_tree('target')
1750
revision_tree = source.basis_tree()
1751
revision_tree.lock_read()
1752
self.addCleanup(revision_tree.unlock)
1753
build_tree(revision_tree, target, source)
1755
self.addCleanup(target.unlock)
1756
self.assertEqual([], list(target.iter_changes(revision_tree)))
1758
def test_build_tree_accelerator_wrong_kind(self):
1759
self.requireFeature(SymlinkFeature)
1760
source = self.make_branch_and_tree('source')
1761
self.build_tree_contents([('source/file1', '')])
1762
self.build_tree_contents([('source/file2', '')])
1763
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1764
source.commit('commit files')
1765
os.unlink('source/file2')
1766
self.build_tree_contents([('source/file2/', 'C')])
1767
os.unlink('source/file1')
1768
os.symlink('file2', 'source/file1')
1770
real_source_get_file = source.get_file
1771
def get_file(file_id, path=None):
1772
calls.append(file_id)
1773
return real_source_get_file(file_id, path)
1774
source.get_file = get_file
1775
target = self.make_branch_and_tree('target')
1776
revision_tree = source.basis_tree()
1777
revision_tree.lock_read()
1778
self.addCleanup(revision_tree.unlock)
1779
build_tree(revision_tree, target, source)
1780
self.assertEqual([], calls)
1782
self.addCleanup(target.unlock)
1783
self.assertEqual([], list(target.iter_changes(revision_tree)))
1785
def test_build_tree_hardlink(self):
1786
self.requireFeature(HardlinkFeature)
1787
source = self.create_ab_tree()
1788
target = self.make_branch_and_tree('target')
1789
revision_tree = source.basis_tree()
1790
revision_tree.lock_read()
1791
self.addCleanup(revision_tree.unlock)
1792
build_tree(revision_tree, target, source, hardlink=True)
1794
self.addCleanup(target.unlock)
1795
self.assertEqual([], list(target.iter_changes(revision_tree)))
1796
source_stat = os.stat('source/file1')
1797
target_stat = os.stat('target/file1')
1798
self.assertEqual(source_stat, target_stat)
1800
# Explicitly disallowing hardlinks should prevent them.
1801
target2 = self.make_branch_and_tree('target2')
1802
build_tree(revision_tree, target2, source, hardlink=False)
1804
self.addCleanup(target2.unlock)
1805
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1806
source_stat = os.stat('source/file1')
1807
target2_stat = os.stat('target2/file1')
1808
self.assertNotEqual(source_stat, target2_stat)
1810
def test_build_tree_accelerator_tree_moved(self):
1811
source = self.make_branch_and_tree('source')
1812
self.build_tree_contents([('source/file1', 'A')])
1813
source.add(['file1'], ['file1-id'])
1814
source.commit('commit files')
1815
source.rename_one('file1', 'file2')
1817
self.addCleanup(source.unlock)
1818
target = self.make_branch_and_tree('target')
1819
revision_tree = source.basis_tree()
1820
revision_tree.lock_read()
1821
self.addCleanup(revision_tree.unlock)
1822
build_tree(revision_tree, target, source)
1824
self.addCleanup(target.unlock)
1825
self.assertEqual([], list(target.iter_changes(revision_tree)))
1827
def test_build_tree_hardlinks_preserve_execute(self):
1828
self.requireFeature(HardlinkFeature)
1829
source = self.create_ab_tree()
1830
tt = TreeTransform(source)
1831
trans_id = tt.trans_id_tree_file_id('file1-id')
1832
tt.set_executability(True, trans_id)
1834
self.assertTrue(source.is_executable('file1-id'))
1835
target = self.make_branch_and_tree('target')
1836
revision_tree = source.basis_tree()
1837
revision_tree.lock_read()
1838
self.addCleanup(revision_tree.unlock)
1839
build_tree(revision_tree, target, source, hardlink=True)
1841
self.addCleanup(target.unlock)
1842
self.assertEqual([], list(target.iter_changes(revision_tree)))
1843
self.assertTrue(source.is_executable('file1-id'))
1845
def test_case_insensitive_build_tree_inventory(self):
1846
source = self.make_branch_and_tree('source')
1847
self.build_tree(['source/file', 'source/FILE'])
1848
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1849
source.commit('added files')
1850
# Don't try this at home, kids!
1851
# Force the tree to report that it is case insensitive
1852
target = self.make_branch_and_tree('target')
1853
target.case_sensitive = False
1854
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
1855
self.assertEqual('file.moved', target.id2path('lower-id'))
1856
self.assertEqual('FILE', target.id2path('upper-id'))
1859
class MockTransform(object):
1861
def has_named_child(self, by_parent, parent_id, name):
1862
for child_id in by_parent[parent_id]:
1866
elif name == "name.~%s~" % child_id:
1871
class MockEntry(object):
1873
object.__init__(self)
1877
class TestGetBackupName(TestCase):
1878
def test_get_backup_name(self):
1879
tt = MockTransform()
1880
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1881
self.assertEqual(name, 'name.~1~')
1882
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1883
self.assertEqual(name, 'name.~2~')
1884
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1885
self.assertEqual(name, 'name.~1~')
1886
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1887
self.assertEqual(name, 'name.~1~')
1888
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1889
self.assertEqual(name, 'name.~4~')
1892
class TestFileMover(tests.TestCaseWithTransport):
1894
def test_file_mover(self):
1895
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1896
mover = _FileMover()
1897
mover.rename('a', 'q')
1898
self.failUnlessExists('q')
1899
self.failIfExists('a')
1900
self.failUnlessExists('q/b')
1901
self.failUnlessExists('c')
1902
self.failUnlessExists('c/d')
1904
def test_pre_delete_rollback(self):
1905
self.build_tree(['a/'])
1906
mover = _FileMover()
1907
mover.pre_delete('a', 'q')
1908
self.failUnlessExists('q')
1909
self.failIfExists('a')
1911
self.failIfExists('q')
1912
self.failUnlessExists('a')
1914
def test_apply_deletions(self):
1915
self.build_tree(['a/', 'b/'])
1916
mover = _FileMover()
1917
mover.pre_delete('a', 'q')
1918
mover.pre_delete('b', 'r')
1919
self.failUnlessExists('q')
1920
self.failUnlessExists('r')
1921
self.failIfExists('a')
1922
self.failIfExists('b')
1923
mover.apply_deletions()
1924
self.failIfExists('q')
1925
self.failIfExists('r')
1926
self.failIfExists('a')
1927
self.failIfExists('b')
1929
def test_file_mover_rollback(self):
1930
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1931
mover = _FileMover()
1932
mover.rename('c/d', 'c/f')
1933
mover.rename('c/e', 'c/d')
1935
mover.rename('a', 'c')
1936
except errors.FileExists, e:
1938
self.failUnlessExists('a')
1939
self.failUnlessExists('c/d')
1942
class Bogus(Exception):
1946
class TestTransformRollback(tests.TestCaseWithTransport):
1948
class ExceptionFileMover(_FileMover):
1950
def __init__(self, bad_source=None, bad_target=None):
1951
_FileMover.__init__(self)
1952
self.bad_source = bad_source
1953
self.bad_target = bad_target
1955
def rename(self, source, target):
1956
if (self.bad_source is not None and
1957
source.endswith(self.bad_source)):
1959
elif (self.bad_target is not None and
1960
target.endswith(self.bad_target)):
1963
_FileMover.rename(self, source, target)
1965
def test_rollback_rename(self):
1966
tree = self.make_branch_and_tree('.')
1967
self.build_tree(['a/', 'a/b'])
1968
tt = TreeTransform(tree)
1969
self.addCleanup(tt.finalize)
1970
a_id = tt.trans_id_tree_path('a')
1971
tt.adjust_path('c', tt.root, a_id)
1972
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1973
self.assertRaises(Bogus, tt.apply,
1974
_mover=self.ExceptionFileMover(bad_source='a'))
1975
self.failUnlessExists('a')
1976
self.failUnlessExists('a/b')
1978
self.failUnlessExists('c')
1979
self.failUnlessExists('c/d')
1981
def test_rollback_rename_into_place(self):
1982
tree = self.make_branch_and_tree('.')
1983
self.build_tree(['a/', 'a/b'])
1984
tt = TreeTransform(tree)
1985
self.addCleanup(tt.finalize)
1986
a_id = tt.trans_id_tree_path('a')
1987
tt.adjust_path('c', tt.root, a_id)
1988
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1989
self.assertRaises(Bogus, tt.apply,
1990
_mover=self.ExceptionFileMover(bad_target='c/d'))
1991
self.failUnlessExists('a')
1992
self.failUnlessExists('a/b')
1994
self.failUnlessExists('c')
1995
self.failUnlessExists('c/d')
1997
def test_rollback_deletion(self):
1998
tree = self.make_branch_and_tree('.')
1999
self.build_tree(['a/', 'a/b'])
2000
tt = TreeTransform(tree)
2001
self.addCleanup(tt.finalize)
2002
a_id = tt.trans_id_tree_path('a')
2003
tt.delete_contents(a_id)
2004
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2005
self.assertRaises(Bogus, tt.apply,
2006
_mover=self.ExceptionFileMover(bad_target='d'))
2007
self.failUnlessExists('a')
2008
self.failUnlessExists('a/b')
2010
def test_resolve_no_parent(self):
2011
wt = self.make_branch_and_tree('.')
2012
tt = TreeTransform(wt)
2013
self.addCleanup(tt.finalize)
2014
parent = tt.trans_id_file_id('parent-id')
2015
tt.new_file('file', parent, 'Contents')
2016
resolve_conflicts(tt)
2019
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2020
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2022
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2023
('', ''), ('directory', 'directory'), (False, None))
2026
class TestTransformPreview(tests.TestCaseWithTransport):
2028
def create_tree(self):
2029
tree = self.make_branch_and_tree('.')
2030
self.build_tree_contents([('a', 'content 1')])
2031
tree.add('a', 'a-id')
2032
tree.commit('rev1', rev_id='rev1')
2033
return tree.branch.repository.revision_tree('rev1')
2035
def get_empty_preview(self):
2036
repository = self.make_repository('repo')
2037
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2038
preview = TransformPreview(tree)
2039
self.addCleanup(preview.finalize)
2042
def test_transform_preview(self):
2043
revision_tree = self.create_tree()
2044
preview = TransformPreview(revision_tree)
2045
self.addCleanup(preview.finalize)
2047
def test_transform_preview_tree(self):
2048
revision_tree = self.create_tree()
2049
preview = TransformPreview(revision_tree)
2050
self.addCleanup(preview.finalize)
2051
preview.get_preview_tree()
2053
def test_transform_new_file(self):
2054
revision_tree = self.create_tree()
2055
preview = TransformPreview(revision_tree)
2056
self.addCleanup(preview.finalize)
2057
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2058
preview_tree = preview.get_preview_tree()
2059
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2061
preview_tree.get_file('file2-id').read(), 'content B\n')
2063
def test_diff_preview_tree(self):
2064
revision_tree = self.create_tree()
2065
preview = TransformPreview(revision_tree)
2066
self.addCleanup(preview.finalize)
2067
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2068
preview_tree = preview.get_preview_tree()
2070
show_diff_trees(revision_tree, preview_tree, out)
2071
lines = out.getvalue().splitlines()
2072
self.assertEqual(lines[0], "=== added file 'file2'")
2073
# 3 lines of diff administrivia
2074
self.assertEqual(lines[4], "+content B")
2076
def test_transform_conflicts(self):
2077
revision_tree = self.create_tree()
2078
preview = TransformPreview(revision_tree)
2079
self.addCleanup(preview.finalize)
2080
preview.new_file('a', preview.root, 'content 2')
2081
resolve_conflicts(preview)
2082
trans_id = preview.trans_id_file_id('a-id')
2083
self.assertEqual('a.moved', preview.final_name(trans_id))
2085
def get_tree_and_preview_tree(self):
2086
revision_tree = self.create_tree()
2087
preview = TransformPreview(revision_tree)
2088
self.addCleanup(preview.finalize)
2089
a_trans_id = preview.trans_id_file_id('a-id')
2090
preview.delete_contents(a_trans_id)
2091
preview.create_file('b content', a_trans_id)
2092
preview_tree = preview.get_preview_tree()
2093
return revision_tree, preview_tree
2095
def test_iter_changes(self):
2096
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2097
root = revision_tree.inventory.root.file_id
2098
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2099
(root, root), ('a', 'a'), ('file', 'file'),
2101
list(preview_tree.iter_changes(revision_tree)))
2103
def test_include_unchanged_succeeds(self):
2104
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2105
changes = preview_tree.iter_changes(revision_tree,
2106
include_unchanged=True)
2107
root = revision_tree.inventory.root.file_id
2109
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2111
def test_specific_files(self):
2112
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2113
changes = preview_tree.iter_changes(revision_tree,
2114
specific_files=[''])
2115
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2117
def test_want_unversioned(self):
2118
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2119
changes = preview_tree.iter_changes(revision_tree,
2120
want_unversioned=True)
2121
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2123
def test_ignore_extra_trees_no_specific_files(self):
2124
# extra_trees is harmless without specific_files, so we'll silently
2125
# accept it, even though we won't use it.
2126
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2127
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2129
def test_ignore_require_versioned_no_specific_files(self):
2130
# require_versioned is meaningless without specific_files.
2131
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2132
preview_tree.iter_changes(revision_tree, require_versioned=False)
2134
def test_ignore_pb(self):
2135
# pb could be supported, but TT.iter_changes doesn't support it.
2136
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2137
preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
2139
def test_kind(self):
2140
revision_tree = self.create_tree()
2141
preview = TransformPreview(revision_tree)
2142
self.addCleanup(preview.finalize)
2143
preview.new_file('file', preview.root, 'contents', 'file-id')
2144
preview.new_directory('directory', preview.root, 'dir-id')
2145
preview_tree = preview.get_preview_tree()
2146
self.assertEqual('file', preview_tree.kind('file-id'))
2147
self.assertEqual('directory', preview_tree.kind('dir-id'))
2149
def test_get_file_mtime(self):
2150
preview = self.get_empty_preview()
2151
file_trans_id = preview.new_file('file', preview.root, 'contents',
2153
limbo_path = preview._limbo_name(file_trans_id)
2154
preview_tree = preview.get_preview_tree()
2155
self.assertEqual(os.stat(limbo_path).st_mtime,
2156
preview_tree.get_file_mtime('file-id'))
2158
def test_get_file(self):
2159
preview = self.get_empty_preview()
2160
preview.new_file('file', preview.root, 'contents', 'file-id')
2161
preview_tree = preview.get_preview_tree()
2162
tree_file = preview_tree.get_file('file-id')
2164
self.assertEqual('contents', tree_file.read())
2168
def test_get_symlink_target(self):
2169
self.requireFeature(SymlinkFeature)
2170
preview = self.get_empty_preview()
2171
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2172
preview_tree = preview.get_preview_tree()
2173
self.assertEqual('target',
2174
preview_tree.get_symlink_target('symlink-id'))
2176
def test_all_file_ids(self):
2177
tree = self.make_branch_and_tree('tree')
2178
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2179
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2180
preview = TransformPreview(tree)
2181
self.addCleanup(preview.finalize)
2182
preview.unversion_file(preview.trans_id_file_id('b-id'))
2183
c_trans_id = preview.trans_id_file_id('c-id')
2184
preview.unversion_file(c_trans_id)
2185
preview.version_file('c-id', c_trans_id)
2186
preview_tree = preview.get_preview_tree()
2187
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2188
preview_tree.all_file_ids())
2190
def test_path2id_deleted_unchanged(self):
2191
tree = self.make_branch_and_tree('tree')
2192
self.build_tree(['tree/unchanged', 'tree/deleted'])
2193
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2194
preview = TransformPreview(tree)
2195
self.addCleanup(preview.finalize)
2196
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2197
preview_tree = preview.get_preview_tree()
2198
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2199
self.assertIs(None, preview_tree.path2id('deleted'))
2201
def test_path2id_created(self):
2202
tree = self.make_branch_and_tree('tree')
2203
self.build_tree(['tree/unchanged'])
2204
tree.add(['unchanged'], ['unchanged-id'])
2205
preview = TransformPreview(tree)
2206
self.addCleanup(preview.finalize)
2207
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2208
'contents', 'new-id')
2209
preview_tree = preview.get_preview_tree()
2210
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2212
def test_path2id_moved(self):
2213
tree = self.make_branch_and_tree('tree')
2214
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2215
tree.add(['old_parent', 'old_parent/child'],
2216
['old_parent-id', 'child-id'])
2217
preview = TransformPreview(tree)
2218
self.addCleanup(preview.finalize)
2219
new_parent = preview.new_directory('new_parent', preview.root,
2221
preview.adjust_path('child', new_parent,
2222
preview.trans_id_file_id('child-id'))
2223
preview_tree = preview.get_preview_tree()
2224
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2225
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2227
def test_path2id_renamed_parent(self):
2228
tree = self.make_branch_and_tree('tree')
2229
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2230
tree.add(['old_name', 'old_name/child'],
2231
['parent-id', 'child-id'])
2232
preview = TransformPreview(tree)
2233
self.addCleanup(preview.finalize)
2234
preview.adjust_path('new_name', preview.root,
2235
preview.trans_id_file_id('parent-id'))
2236
preview_tree = preview.get_preview_tree()
2237
self.assertIs(None, preview_tree.path2id('old_name/child'))
2238
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2240
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2241
preview_tree = tt.get_preview_tree()
2242
preview_result = list(preview_tree.iter_entries_by_dir(
2246
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2247
self.assertEqual(actual_result, preview_result)
2249
def test_iter_entries_by_dir_new(self):
2250
tree = self.make_branch_and_tree('tree')
2251
tt = TreeTransform(tree)
2252
tt.new_file('new', tt.root, 'contents', 'new-id')
2253
self.assertMatchingIterEntries(tt)
2255
def test_iter_entries_by_dir_deleted(self):
2256
tree = self.make_branch_and_tree('tree')
2257
self.build_tree(['tree/deleted'])
2258
tree.add('deleted', 'deleted-id')
2259
tt = TreeTransform(tree)
2260
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2261
self.assertMatchingIterEntries(tt)
2263
def test_iter_entries_by_dir_unversioned(self):
2264
tree = self.make_branch_and_tree('tree')
2265
self.build_tree(['tree/removed'])
2266
tree.add('removed', 'removed-id')
2267
tt = TreeTransform(tree)
2268
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2269
self.assertMatchingIterEntries(tt)
2271
def test_iter_entries_by_dir_moved(self):
2272
tree = self.make_branch_and_tree('tree')
2273
self.build_tree(['tree/moved', 'tree/new_parent/'])
2274
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2275
tt = TreeTransform(tree)
2276
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2277
tt.trans_id_file_id('moved-id'))
2278
self.assertMatchingIterEntries(tt)
2280
def test_iter_entries_by_dir_specific_file_ids(self):
2281
tree = self.make_branch_and_tree('tree')
2282
tree.set_root_id('tree-root-id')
2283
self.build_tree(['tree/parent/', 'tree/parent/child'])
2284
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2285
tt = TreeTransform(tree)
2286
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2288
def test_symlink_content_summary(self):
2289
self.requireFeature(SymlinkFeature)
2290
preview = self.get_empty_preview()
2291
preview.new_symlink('path', preview.root, 'target', 'path-id')
2292
summary = preview.get_preview_tree().path_content_summary('path')
2293
self.assertEqual(('symlink', None, None, 'target'), summary)
2295
def test_missing_content_summary(self):
2296
preview = self.get_empty_preview()
2297
summary = preview.get_preview_tree().path_content_summary('path')
2298
self.assertEqual(('missing', None, None, None), summary)
2300
def test_deleted_content_summary(self):
2301
tree = self.make_branch_and_tree('tree')
2302
self.build_tree(['tree/path/'])
2304
preview = TransformPreview(tree)
2305
self.addCleanup(preview.finalize)
2306
preview.delete_contents(preview.trans_id_tree_path('path'))
2307
summary = preview.get_preview_tree().path_content_summary('path')
2308
self.assertEqual(('missing', None, None, None), summary)
2310
def test_file_content_summary_executable(self):
2311
if not osutils.supports_executable():
2312
raise TestNotApplicable()
2313
preview = self.get_empty_preview()
2314
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2315
preview.set_executability(True, path_id)
2316
summary = preview.get_preview_tree().path_content_summary('path')
2317
self.assertEqual(4, len(summary))
2318
self.assertEqual('file', summary[0])
2319
# size must be known
2320
self.assertEqual(len('contents'), summary[1])
2322
self.assertEqual(True, summary[2])
2323
# will not have hash (not cheap to determine)
2324
self.assertIs(None, summary[3])
2326
def test_change_executability(self):
2327
if not osutils.supports_executable():
2328
raise TestNotApplicable()
2329
tree = self.make_branch_and_tree('tree')
2330
self.build_tree(['tree/path'])
2332
preview = TransformPreview(tree)
2333
self.addCleanup(preview.finalize)
2334
path_id = preview.trans_id_tree_path('path')
2335
preview.set_executability(True, path_id)
2336
summary = preview.get_preview_tree().path_content_summary('path')
2337
self.assertEqual(True, summary[2])
2339
def test_file_content_summary_non_exec(self):
2340
preview = self.get_empty_preview()
2341
preview.new_file('path', preview.root, 'contents', 'path-id')
2342
summary = preview.get_preview_tree().path_content_summary('path')
2343
self.assertEqual(4, len(summary))
2344
self.assertEqual('file', summary[0])
2345
# size must be known
2346
self.assertEqual(len('contents'), summary[1])
2348
if osutils.supports_executable():
2349
self.assertEqual(False, summary[2])
2351
self.assertEqual(None, summary[2])
2352
# will not have hash (not cheap to determine)
2353
self.assertIs(None, summary[3])
2355
def test_dir_content_summary(self):
2356
preview = self.get_empty_preview()
2357
preview.new_directory('path', preview.root, 'path-id')
2358
summary = preview.get_preview_tree().path_content_summary('path')
2359
self.assertEqual(('directory', None, None, None), summary)
2361
def test_tree_content_summary(self):
2362
preview = self.get_empty_preview()
2363
path = preview.new_directory('path', preview.root, 'path-id')
2364
preview.set_tree_reference('rev-1', path)
2365
summary = preview.get_preview_tree().path_content_summary('path')
2366
self.assertEqual(4, len(summary))
2367
self.assertEqual('tree-reference', summary[0])
2369
def test_annotate(self):
2370
tree = self.make_branch_and_tree('tree')
2371
self.build_tree_contents([('tree/file', 'a\n')])
2372
tree.add('file', 'file-id')
2373
tree.commit('a', rev_id='one')
2374
self.build_tree_contents([('tree/file', 'a\nb\n')])
2375
preview = TransformPreview(tree)
2376
self.addCleanup(preview.finalize)
2377
file_trans_id = preview.trans_id_file_id('file-id')
2378
preview.delete_contents(file_trans_id)
2379
preview.create_file('a\nb\nc\n', file_trans_id)
2380
preview_tree = preview.get_preview_tree()
2386
annotation = preview_tree.annotate_iter('file-id', 'me:')
2387
self.assertEqual(expected, annotation)
2389
def test_annotate_missing(self):
2390
preview = self.get_empty_preview()
2391
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2392
preview_tree = preview.get_preview_tree()
2398
annotation = preview_tree.annotate_iter('file-id', 'me:')
2399
self.assertEqual(expected, annotation)
2401
def test_annotate_rename(self):
2402
tree = self.make_branch_and_tree('tree')
2403
self.build_tree_contents([('tree/file', 'a\n')])
2404
tree.add('file', 'file-id')
2405
tree.commit('a', rev_id='one')
2406
preview = TransformPreview(tree)
2407
self.addCleanup(preview.finalize)
2408
file_trans_id = preview.trans_id_file_id('file-id')
2409
preview.adjust_path('newname', preview.root, file_trans_id)
2410
preview_tree = preview.get_preview_tree()
2414
annotation = preview_tree.annotate_iter('file-id', 'me:')
2415
self.assertEqual(expected, annotation)
2417
def test_annotate_deleted(self):
2418
tree = self.make_branch_and_tree('tree')
2419
self.build_tree_contents([('tree/file', 'a\n')])
2420
tree.add('file', 'file-id')
2421
tree.commit('a', rev_id='one')
2422
self.build_tree_contents([('tree/file', 'a\nb\n')])
2423
preview = TransformPreview(tree)
2424
self.addCleanup(preview.finalize)
2425
file_trans_id = preview.trans_id_file_id('file-id')
2426
preview.delete_contents(file_trans_id)
2427
preview_tree = preview.get_preview_tree()
2428
annotation = preview_tree.annotate_iter('file-id', 'me:')
2429
self.assertIs(None, annotation)
2431
def test_stored_kind(self):
2432
preview = self.get_empty_preview()
2433
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2434
preview_tree = preview.get_preview_tree()
2435
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2437
def test_is_executable(self):
2438
preview = self.get_empty_preview()
2439
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2440
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2441
preview_tree = preview.get_preview_tree()
2442
self.assertEqual(True, preview_tree.is_executable('file-id'))
2444
def test_get_set_parent_ids(self):
2445
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2446
self.assertEqual([], preview_tree.get_parent_ids())
2447
preview_tree.set_parent_ids(['rev-1'])
2448
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2450
def test_plan_file_merge(self):
2451
work_a = self.make_branch_and_tree('wta')
2452
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2453
work_a.add('file', 'file-id')
2454
base_id = work_a.commit('base version')
2455
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2456
preview = TransformPreview(work_a)
2457
self.addCleanup(preview.finalize)
2458
trans_id = preview.trans_id_file_id('file-id')
2459
preview.delete_contents(trans_id)
2460
preview.create_file('b\nc\nd\ne\n', trans_id)
2461
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2462
tree_a = preview.get_preview_tree()
2463
tree_a.set_parent_ids([base_id])
2465
('killed-a', 'a\n'),
2466
('killed-b', 'b\n'),
2467
('unchanged', 'c\n'),
2468
('unchanged', 'd\n'),
2471
], list(tree_a.plan_file_merge('file-id', tree_b)))
2473
def test_plan_file_merge_revision_tree(self):
2474
work_a = self.make_branch_and_tree('wta')
2475
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2476
work_a.add('file', 'file-id')
2477
base_id = work_a.commit('base version')
2478
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2479
preview = TransformPreview(work_a.basis_tree())
2480
self.addCleanup(preview.finalize)
2481
trans_id = preview.trans_id_file_id('file-id')
2482
preview.delete_contents(trans_id)
2483
preview.create_file('b\nc\nd\ne\n', trans_id)
2484
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2485
tree_a = preview.get_preview_tree()
2486
tree_a.set_parent_ids([base_id])
2488
('killed-a', 'a\n'),
2489
('killed-b', 'b\n'),
2490
('unchanged', 'c\n'),
2491
('unchanged', 'd\n'),
2494
], list(tree_a.plan_file_merge('file-id', tree_b)))
2496
def test_walkdirs(self):
2497
preview = self.get_empty_preview()
2498
preview.version_file('tree-root', preview.root)
2499
preview_tree = preview.get_preview_tree()
2500
file_trans_id = preview.new_file('a', preview.root, 'contents',
2502
expected = [(('', 'tree-root'),
2503
[('a', 'a', 'file', None, 'a-id', 'file')])]
2504
self.assertEqual(expected, list(preview_tree.walkdirs()))
2506
def test_extras(self):
2507
work_tree = self.make_branch_and_tree('tree')
2508
self.build_tree(['tree/removed-file', 'tree/existing-file',
2509
'tree/not-removed-file'])
2510
work_tree.add(['removed-file', 'not-removed-file'])
2511
preview = TransformPreview(work_tree)
2512
self.addCleanup(preview.finalize)
2513
preview.new_file('new-file', preview.root, 'contents')
2514
preview.new_file('new-versioned-file', preview.root, 'contents',
2516
tree = preview.get_preview_tree()
2517
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2518
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2521
def test_merge_into_preview(self):
2522
work_tree = self.make_branch_and_tree('tree')
2523
self.build_tree_contents([('tree/file','b\n')])
2524
work_tree.add('file', 'file-id')
2525
work_tree.commit('first commit')
2526
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2527
self.build_tree_contents([('child/file','b\nc\n')])
2528
child_tree.commit('child commit')
2529
child_tree.lock_write()
2530
self.addCleanup(child_tree.unlock)
2531
work_tree.lock_write()
2532
self.addCleanup(work_tree.unlock)
2533
preview = TransformPreview(work_tree)
2534
self.addCleanup(preview.finalize)
2535
preview_tree = preview.get_preview_tree()
2536
file_trans_id = preview.trans_id_file_id('file-id')
2537
preview.delete_contents(file_trans_id)
2538
preview.create_file('a\nb\n', file_trans_id)
2539
pb = progress.DummyProgress()
2540
merger = Merger.from_revision_ids(pb, preview_tree,
2541
child_tree.branch.last_revision(),
2542
other_branch=child_tree.branch,
2543
tree_branch=work_tree.branch)
2544
merger.merge_type = Merge3Merger
2545
tt = merger.make_merger().make_preview_transform()
2546
self.addCleanup(tt.finalize)
2547
final_tree = tt.get_preview_tree()
2548
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2550
def test_merge_preview_into_workingtree(self):
2551
tree = self.make_branch_and_tree('tree')
2552
tt = TransformPreview(tree)
2553
self.addCleanup(tt.finalize)
2554
tt.new_file('name', tt.root, 'content', 'file-id')
2555
tree2 = self.make_branch_and_tree('tree2')
2556
pb = progress.DummyProgress()
2557
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2558
pb, tree.basis_tree())
2559
merger.merge_type = Merge3Merger
2562
def test_merge_preview_into_workingtree_handles_conflicts(self):
2563
tree = self.make_branch_and_tree('tree')
2564
self.build_tree_contents([('tree/foo', 'bar')])
2565
tree.add('foo', 'foo-id')
2567
tt = TransformPreview(tree)
2568
self.addCleanup(tt.finalize)
2569
trans_id = tt.trans_id_file_id('foo-id')
2570
tt.delete_contents(trans_id)
2571
tt.create_file('baz', trans_id)
2572
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2573
self.build_tree_contents([('tree2/foo', 'qux')])
2574
pb = progress.DummyProgress()
2575
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2576
pb, tree.basis_tree())
2577
merger.merge_type = Merge3Merger
2580
def test_is_executable(self):
2581
tree = self.make_branch_and_tree('tree')
2582
preview = TransformPreview(tree)
2583
self.addCleanup(preview.finalize)
2584
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2585
preview_tree = preview.get_preview_tree()
2586
self.assertEqual(False, preview_tree.is_executable('baz-id',
2588
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2591
class FakeSerializer(object):
2592
"""Serializer implementation that simply returns the input.
2594
The input is returned in the order used by pack.ContainerPushParser.
2597
def bytes_record(bytes, names):
2601
class TestSerializeTransform(tests.TestCaseWithTransport):
2603
def get_preview(self):
2604
tree = self.make_branch_and_tree('tree')
2605
tt = TransformPreview(tree)
2606
self.addCleanup(tt.finalize)
2609
def get_two_previews(self, tree):
2610
tt = TransformPreview(tree)
2611
self.addCleanup(tt.finalize)
2612
tt2 = TransformPreview(tree)
2613
self.addCleanup(tt2.finalize)
2617
def reserialize(tt, tt2):
2618
serializer = pack.ContainerSerialiser()
2619
parser = pack.ContainerPushParser()
2620
parser.accept_bytes(serializer.begin())
2621
for bytes in tt.serialize(serializer):
2622
parser.accept_bytes(bytes)
2623
parser.accept_bytes(serializer.end())
2624
tt2.deserialize(iter(parser.read_pending_records()))
2627
def default_attribs():
2632
'_new_executability': {},
2634
'_tree_path_ids': {'': 'new-0'},
2636
'_removed_contents': [],
2637
'_non_present_ids': {},
2640
def creation_records(self):
2641
attribs = self.default_attribs()
2642
attribs['_id_number'] = 3
2643
attribs['_new_name'] = {
2644
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2645
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2646
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2647
attribs['_new_executability'] = {'new-1': 1}
2649
('new-1', 'file', 'i 1\nbar\n'),
2650
('new-2', 'directory', ''),
2652
return self.make_records(attribs, contents)
2654
def symlink_creation_records(self):
2655
attribs = self.default_attribs()
2656
attribs['_id_number'] = 2
2657
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
2658
attribs['_new_parent'] = {'new-1': 'new-0'}
2659
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
2660
return self.make_records(attribs, contents)
2662
def make_records(self, attribs, contents):
2664
(((('attribs'),),), bencode.bencode(attribs))]
2665
records.extend([(((n, k),), c) for n, k, c in contents])
2668
def test_serialize_creation(self):
2669
tt = self.get_preview()
2670
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2671
tt.new_directory('qux', tt.root, 'quxx')
2672
records = tt.serialize(FakeSerializer())
2673
self.assertEqual(self.creation_records(), list(records))
2675
def test_deserialize_creation(self):
2676
tt = self.get_preview()
2677
tt.deserialize(iter(self.creation_records()))
2678
self.assertEqual(3, tt._id_number)
2679
self.assertEqual({'new-1': u'foo\u1234',
2680
'new-2': 'qux'}, tt._new_name)
2681
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2682
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2683
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2684
self.assertEqual({'new-1': True}, tt._new_executability)
2685
self.assertEqual({'new-1': 'file',
2686
'new-2': 'directory'}, tt._new_contents)
2687
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2689
foo_content = foo_limbo.read()
2692
self.assertEqual('bar', foo_content)
2694
def assertEqualRecords(self, a, b):
2695
from textwrap import fill
2696
self.assertEqualDiff(fill(repr(a)), fill(repr(list(b))))
2698
def test_serialize_symlink_creation(self):
2699
self.requireFeature(tests.SymlinkFeature)
2700
tt = self.get_preview()
2701
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
2702
records = tt.serialize(FakeSerializer())
2703
self.assertEqual(self.symlink_creation_records(), list(records))
2705
def test_deserialize_symlink_creation(self):
2706
tt = self.get_preview()
2707
tt.deserialize(iter(self.symlink_creation_records()))
2708
foo_content = os.readlink(tt._limbo_name('new-1'))
2709
self.assertEqual(u'bar\u1234'.encode('utf-8'), foo_content)
2711
def test_roundtrip_destruction(self):
2712
tree = self.make_branch_and_tree('.')
2713
self.build_tree([u'foo\u1234', 'bar'])
2714
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
2715
tt, tt2 = self.get_two_previews(tree)
2716
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
2717
tt.unversion_file(foo_trans_id)
2718
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
2719
tt.delete_contents(bar_trans_id)
2720
self.reserialize(tt, tt2)
2721
self.assertEqual({u'foo\u1234': foo_trans_id,
2722
'bar': bar_trans_id,
2723
'': tt.root}, tt2._tree_path_ids)
2724
self.assertEqual({foo_trans_id: u'foo\u1234',
2725
bar_trans_id: 'bar',
2726
tt.root: ''}, tt2._tree_id_paths)
2727
self.assertEqual(set([foo_trans_id]), tt2._removed_id)
2728
self.assertEqual(set([bar_trans_id]), tt2._removed_contents)
2730
def test_roundtrip_missing(self):
2731
tree = self.make_branch_and_tree('.')
2732
tt, tt2 = self.get_two_previews(tree)
2733
boo_trans_id = tt.trans_id_file_id('boo')
2734
self.reserialize(tt, tt2)
2735
self.assertEqual({'boo': boo_trans_id}, tt2._non_present_ids)
2737
def test_roundtrip_modification(self):
2738
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2739
LINES_TWO = 'z\nbb\nx\ndd\n'
2740
tree = self.make_branch_and_tree('tree')
2741
self.build_tree_contents([('tree/file', LINES_ONE)])
2742
tree.add('file', 'file-id')
2743
tt, tt2 = self.get_two_previews(tree)
2744
trans_id = tt.trans_id_file_id('file-id')
2745
tt.delete_contents(trans_id)
2746
tt.create_file(LINES_TWO, trans_id)
2747
self.reserialize(tt, tt2)
2748
self.assertFileEqual(LINES_TWO, tt2._limbo_name(trans_id))
2750
def test_roundtrip_kind_change(self):
2751
LINES_ONE = 'a\nb\nc\nd\n'
2752
tree = self.make_branch_and_tree('tree')
2753
self.build_tree(['tree/foo/'])
2754
tree.add('foo', 'foo-id')
2755
tt, tt2 = self.get_two_previews(tree)
2756
trans_id = tt.trans_id_file_id('foo-id')
2757
tt.delete_contents(trans_id)
2758
tt.create_file(LINES_ONE, trans_id)
2759
self.reserialize(tt, tt2)
2760
self.assertFileEqual(LINES_ONE, tt2._limbo_name(trans_id))
2762
def test_roundtrip_add_contents(self):
2763
LINES_ONE = 'a\nb\nc\nd\n'
2764
tree = self.make_branch_and_tree('tree')
2765
self.build_tree(['tree/foo'])
2767
os.unlink('tree/foo')
2768
tt, tt2 = self.get_two_previews(tree)
2769
trans_id = tt.trans_id_tree_path('foo')
2770
tt.create_file(LINES_ONE, trans_id)
2771
self.reserialize(tt, tt2)
2772
self.assertFileEqual(LINES_ONE, tt2._limbo_name(trans_id))
2774
def test_get_parents_lines(self):
2775
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2776
LINES_TWO = 'z\nbb\nx\ndd\n'
2777
tree = self.make_branch_and_tree('tree')
2778
self.build_tree_contents([('tree/file', LINES_ONE)])
2779
tree.add('file', 'file-id')
2780
tt, tt2 = self.get_two_previews(tree)
2781
trans_id = tt.trans_id_tree_path('file')
2782
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
2783
tt._get_parents_lines(trans_id))
2785
def test_get_parents_texts(self):
2786
LINES_ONE = 'aa\nbb\ncc\ndd\n'
2787
LINES_TWO = 'z\nbb\nx\ndd\n'
2788
tree = self.make_branch_and_tree('tree')
2789
self.build_tree_contents([('tree/file', LINES_ONE)])
2790
tree.add('file', 'file-id')
2791
tt, tt2 = self.get_two_previews(tree)
2792
trans_id = tt.trans_id_tree_path('file')
2793
self.assertEqual((LINES_ONE,),
2794
tt._get_parents_texts(trans_id))