1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
from StringIO import StringIO
29
revision as _mod_revision,
37
from bzrlib.bzrdir import BzrDir
38
from bzrlib.conflicts import (
47
from bzrlib.diff import show_diff_trees
48
from bzrlib.errors import (
51
ExistingPendingDeletion,
53
ImmortalPendingDeletion,
58
from bzrlib.osutils import (
62
from bzrlib.merge import Merge3Merger, Merger
63
from bzrlib.tests import (
70
from bzrlib.transform import (
84
class TestTreeTransform(tests.TestCaseWithTransport):
87
super(TestTreeTransform, self).setUp()
88
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
91
def get_transform(self):
92
transform = TreeTransform(self.wt)
93
self.addCleanup(transform.finalize)
94
return transform, transform.root
96
def get_transform_for_sha1_test(self):
97
trans, root = self.get_transform()
98
self.wt.lock_tree_write()
99
self.addCleanup(self.wt.unlock)
100
contents = ['just some content\n']
101
sha1 = osutils.sha_strings(contents)
102
# Roll back the clock
103
trans._creation_mtime = time.time() - 20.0
104
return trans, root, contents, sha1
106
def test_existing_limbo(self):
107
transform, root = self.get_transform()
108
limbo_name = transform._limbodir
109
deletion_path = transform._deletiondir
110
os.mkdir(pathjoin(limbo_name, 'hehe'))
111
self.assertRaises(ImmortalLimbo, transform.apply)
112
self.assertRaises(LockError, self.wt.unlock)
113
self.assertRaises(ExistingLimbo, self.get_transform)
114
self.assertRaises(LockError, self.wt.unlock)
115
os.rmdir(pathjoin(limbo_name, 'hehe'))
117
os.rmdir(deletion_path)
118
transform, root = self.get_transform()
121
def test_existing_pending_deletion(self):
122
transform, root = self.get_transform()
123
deletion_path = self._limbodir = urlutils.local_path_from_url(
124
transform._tree._transport.abspath('pending-deletion'))
125
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
126
self.assertRaises(ImmortalPendingDeletion, transform.apply)
127
self.assertRaises(LockError, self.wt.unlock)
128
self.assertRaises(ExistingPendingDeletion, self.get_transform)
130
def test_build(self):
131
transform, root = self.get_transform()
132
self.wt.lock_tree_write()
133
self.addCleanup(self.wt.unlock)
134
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
135
imaginary_id = transform.trans_id_tree_path('imaginary')
136
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
137
self.assertEqual(imaginary_id, imaginary_id2)
138
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
139
self.assertEqual('directory', transform.final_kind(root))
140
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
141
trans_id = transform.create_path('name', root)
142
self.assertIs(transform.final_file_id(trans_id), None)
143
self.assertIs(None, transform.final_kind(trans_id))
144
transform.create_file('contents', trans_id)
145
transform.set_executability(True, trans_id)
146
transform.version_file('my_pretties', trans_id)
147
self.assertRaises(DuplicateKey, transform.version_file,
148
'my_pretties', trans_id)
149
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
150
self.assertEqual(transform.final_parent(trans_id), root)
151
self.assertIs(transform.final_parent(root), ROOT_PARENT)
152
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
153
oz_id = transform.create_path('oz', root)
154
transform.create_directory(oz_id)
155
transform.version_file('ozzie', oz_id)
156
trans_id2 = transform.create_path('name2', root)
157
transform.create_file('contents', trans_id2)
158
transform.set_executability(False, trans_id2)
159
transform.version_file('my_pretties2', trans_id2)
160
modified_paths = transform.apply().modified_paths
161
self.assertEqual('contents', self.wt.get_file_byname('name').read())
162
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
163
self.assertIs(self.wt.is_executable('my_pretties'), True)
164
self.assertIs(self.wt.is_executable('my_pretties2'), False)
165
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
166
self.assertEqual(len(modified_paths), 3)
167
tree_mod_paths = [self.wt.id2abspath(f) for f in
168
('ozzie', 'my_pretties', 'my_pretties2')]
169
self.assertSubset(tree_mod_paths, modified_paths)
170
# is it safe to finalize repeatedly?
174
def test_apply_informs_tree_of_observed_sha1(self):
175
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
176
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
179
orig = self.wt._observed_sha1
180
def _observed_sha1(*args):
183
self.wt._observed_sha1 = _observed_sha1
185
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
188
def test_create_file_caches_sha1(self):
189
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
190
trans_id = trans.create_path('file1', root)
191
trans.create_file(contents, trans_id, sha1=sha1)
192
st_val = osutils.lstat(trans._limbo_name(trans_id))
193
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
194
self.assertEqual(o_sha1, sha1)
195
self.assertEqualStat(o_st_val, st_val)
197
def test__apply_insertions_updates_sha1(self):
198
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
199
trans_id = trans.create_path('file1', root)
200
trans.create_file(contents, trans_id, sha1=sha1)
201
st_val = osutils.lstat(trans._limbo_name(trans_id))
202
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
203
self.assertEqual(o_sha1, sha1)
204
self.assertEqualStat(o_st_val, st_val)
205
creation_mtime = trans._creation_mtime + 10.0
206
# We fake a time difference from when the file was created until now it
207
# is being renamed by using os.utime. Note that the change we actually
208
# want to see is the real ctime change from 'os.rename()', but as long
209
# as we observe a new stat value, we should be fine.
210
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
212
new_st_val = osutils.lstat(self.wt.abspath('file1'))
213
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
214
self.assertEqual(o_sha1, sha1)
215
self.assertEqualStat(o_st_val, new_st_val)
216
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
218
def test_new_file_caches_sha1(self):
219
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
220
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
222
st_val = osutils.lstat(trans._limbo_name(trans_id))
223
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
224
self.assertEqual(o_sha1, sha1)
225
self.assertEqualStat(o_st_val, st_val)
227
def test_cancel_creation_removes_observed_sha1(self):
228
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
229
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
231
self.assertTrue(trans_id in trans._observed_sha1s)
232
trans.cancel_creation(trans_id)
233
self.assertFalse(trans_id in trans._observed_sha1s)
235
def test_create_files_same_timestamp(self):
236
transform, root = self.get_transform()
237
self.wt.lock_tree_write()
238
self.addCleanup(self.wt.unlock)
239
# Roll back the clock, so that we know everything is being set to the
241
transform._creation_mtime = creation_mtime = time.time() - 20.0
242
transform.create_file('content-one',
243
transform.create_path('one', root))
244
time.sleep(1) # *ugly*
245
transform.create_file('content-two',
246
transform.create_path('two', root))
248
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
250
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
252
# We only guarantee 2s resolution
253
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
254
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
255
# But if we have more than that, all files should get the same result
256
self.assertEqual(st1.st_mtime, st2.st_mtime)
258
def test_change_root_id(self):
259
transform, root = self.get_transform()
260
self.assertNotEqual('new-root-id', self.wt.get_root_id())
261
transform.new_directory('', ROOT_PARENT, 'new-root-id')
262
transform.delete_contents(root)
263
transform.unversion_file(root)
264
transform.fixup_new_roots()
266
self.assertEqual('new-root-id', self.wt.get_root_id())
268
def test_change_root_id_add_files(self):
269
transform, root = self.get_transform()
270
self.assertNotEqual('new-root-id', self.wt.get_root_id())
271
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
272
transform.new_file('file', new_trans_id, ['new-contents\n'],
274
transform.delete_contents(root)
275
transform.unversion_file(root)
276
transform.fixup_new_roots()
278
self.assertEqual('new-root-id', self.wt.get_root_id())
279
self.assertEqual('new-file-id', self.wt.path2id('file'))
280
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
282
def test_add_two_roots(self):
283
transform, root = self.get_transform()
284
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
285
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
286
self.assertRaises(ValueError, transform.fixup_new_roots)
288
def test_retain_existing_root(self):
289
tt, root = self.get_transform()
291
tt.new_directory('', ROOT_PARENT, 'new-root-id')
293
self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
295
def test_retain_existing_root_added_file(self):
296
tt, root = self.get_transform()
297
new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
298
child = tt.new_directory('child', new_trans_id, 'child-id')
300
self.assertEqual(tt.root, tt.final_parent(child))
302
def test_add_unversioned_root(self):
303
transform, root = self.get_transform()
304
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
305
transform.delete_contents(transform.root)
306
transform.fixup_new_roots()
307
self.assertNotIn(transform.root, transform._new_id)
309
def test_remove_root_fixup(self):
310
transform, root = self.get_transform()
311
old_root_id = self.wt.get_root_id()
312
self.assertNotEqual('new-root-id', old_root_id)
313
transform.delete_contents(root)
314
transform.unversion_file(root)
315
transform.fixup_new_roots()
317
self.assertEqual(old_root_id, self.wt.get_root_id())
319
transform, root = self.get_transform()
320
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
321
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
322
self.assertRaises(ValueError, transform.fixup_new_roots)
324
def test_apply_retains_root_directory(self):
325
# Do not attempt to delete the physical root directory, because that
327
transform, root = self.get_transform()
329
transform.delete_contents(root)
330
e = self.assertRaises(AssertionError, self.assertRaises,
331
errors.TransformRenameFailed,
333
self.assertContainsRe('TransformRenameFailed not raised', str(e))
335
def test_hardlink(self):
336
self.requireFeature(HardlinkFeature)
337
transform, root = self.get_transform()
338
transform.new_file('file1', root, 'contents')
340
target = self.make_branch_and_tree('target')
341
target_transform = TreeTransform(target)
342
trans_id = target_transform.create_path('file1', target_transform.root)
343
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
344
target_transform.apply()
345
self.assertPathExists('target/file1')
346
source_stat = os.stat(self.wt.abspath('file1'))
347
target_stat = os.stat('target/file1')
348
self.assertEqual(source_stat, target_stat)
350
def test_convenience(self):
351
transform, root = self.get_transform()
352
self.wt.lock_tree_write()
353
self.addCleanup(self.wt.unlock)
354
trans_id = transform.new_file('name', root, 'contents',
356
oz = transform.new_directory('oz', root, 'oz-id')
357
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
358
toto = transform.new_file('toto', dorothy, 'toto-contents',
361
self.assertEqual(len(transform.find_conflicts()), 0)
363
self.assertRaises(ReusingTransform, transform.find_conflicts)
364
self.assertEqual('contents', file(self.wt.abspath('name')).read())
365
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
366
self.assertIs(self.wt.is_executable('my_pretties'), True)
367
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
368
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
369
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
371
self.assertEqual('toto-contents',
372
self.wt.get_file_byname('oz/dorothy/toto').read())
373
self.assertIs(self.wt.is_executable('toto-id'), False)
375
def test_tree_reference(self):
376
transform, root = self.get_transform()
377
tree = transform._tree
378
trans_id = transform.new_directory('reference', root, 'subtree-id')
379
transform.set_tree_reference('subtree-revision', trans_id)
382
self.addCleanup(tree.unlock)
383
self.assertEqual('subtree-revision',
384
tree.inventory['subtree-id'].reference_revision)
386
def test_conflicts(self):
387
transform, root = self.get_transform()
388
trans_id = transform.new_file('name', root, 'contents',
390
self.assertEqual(len(transform.find_conflicts()), 0)
391
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
392
self.assertEqual(transform.find_conflicts(),
393
[('duplicate', trans_id, trans_id2, 'name')])
394
self.assertRaises(MalformedTransform, transform.apply)
395
transform.adjust_path('name', trans_id, trans_id2)
396
self.assertEqual(transform.find_conflicts(),
397
[('non-directory parent', trans_id)])
398
tinman_id = transform.trans_id_tree_path('tinman')
399
transform.adjust_path('name', tinman_id, trans_id2)
400
self.assertEqual(transform.find_conflicts(),
401
[('unversioned parent', tinman_id),
402
('missing parent', tinman_id)])
403
lion_id = transform.create_path('lion', root)
404
self.assertEqual(transform.find_conflicts(),
405
[('unversioned parent', tinman_id),
406
('missing parent', tinman_id)])
407
transform.adjust_path('name', lion_id, trans_id2)
408
self.assertEqual(transform.find_conflicts(),
409
[('unversioned parent', lion_id),
410
('missing parent', lion_id)])
411
transform.version_file("Courage", lion_id)
412
self.assertEqual(transform.find_conflicts(),
413
[('missing parent', lion_id),
414
('versioning no contents', lion_id)])
415
transform.adjust_path('name2', root, trans_id2)
416
self.assertEqual(transform.find_conflicts(),
417
[('versioning no contents', lion_id)])
418
transform.create_file('Contents, okay?', lion_id)
419
transform.adjust_path('name2', trans_id2, trans_id2)
420
self.assertEqual(transform.find_conflicts(),
421
[('parent loop', trans_id2),
422
('non-directory parent', trans_id2)])
423
transform.adjust_path('name2', root, trans_id2)
424
oz_id = transform.new_directory('oz', root)
425
transform.set_executability(True, oz_id)
426
self.assertEqual(transform.find_conflicts(),
427
[('unversioned executability', oz_id)])
428
transform.version_file('oz-id', oz_id)
429
self.assertEqual(transform.find_conflicts(),
430
[('non-file executability', oz_id)])
431
transform.set_executability(None, oz_id)
432
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
434
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
435
self.assertEqual('contents', file(self.wt.abspath('name')).read())
436
transform2, root = self.get_transform()
437
oz_id = transform2.trans_id_tree_file_id('oz-id')
438
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
439
result = transform2.find_conflicts()
440
fp = FinalPaths(transform2)
441
self.assert_('oz/tip' in transform2._tree_path_ids)
442
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
443
self.assertEqual(len(result), 2)
444
self.assertEqual((result[0][0], result[0][1]),
445
('duplicate', newtip))
446
self.assertEqual((result[1][0], result[1][2]),
447
('duplicate id', newtip))
448
transform2.finalize()
449
transform3 = TreeTransform(self.wt)
450
self.addCleanup(transform3.finalize)
451
oz_id = transform3.trans_id_tree_file_id('oz-id')
452
transform3.delete_contents(oz_id)
453
self.assertEqual(transform3.find_conflicts(),
454
[('missing parent', oz_id)])
455
root_id = transform3.root
456
tip_id = transform3.trans_id_tree_file_id('tip-id')
457
transform3.adjust_path('tip', root_id, tip_id)
460
def test_conflict_on_case_insensitive(self):
461
tree = self.make_branch_and_tree('tree')
462
# Don't try this at home, kids!
463
# Force the tree to report that it is case sensitive, for conflict
465
tree.case_sensitive = True
466
transform = TreeTransform(tree)
467
self.addCleanup(transform.finalize)
468
transform.new_file('file', transform.root, 'content')
469
transform.new_file('FiLe', transform.root, 'content')
470
result = transform.find_conflicts()
471
self.assertEqual([], result)
473
# Force the tree to report that it is case insensitive, for conflict
475
tree.case_sensitive = False
476
transform = TreeTransform(tree)
477
self.addCleanup(transform.finalize)
478
transform.new_file('file', transform.root, 'content')
479
transform.new_file('FiLe', transform.root, 'content')
480
result = transform.find_conflicts()
481
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
483
def test_conflict_on_case_insensitive_existing(self):
484
tree = self.make_branch_and_tree('tree')
485
self.build_tree(['tree/FiLe'])
486
# Don't try this at home, kids!
487
# Force the tree to report that it is case sensitive, for conflict
489
tree.case_sensitive = True
490
transform = TreeTransform(tree)
491
self.addCleanup(transform.finalize)
492
transform.new_file('file', transform.root, 'content')
493
result = transform.find_conflicts()
494
self.assertEqual([], result)
496
# Force the tree to report that it is case insensitive, for conflict
498
tree.case_sensitive = False
499
transform = TreeTransform(tree)
500
self.addCleanup(transform.finalize)
501
transform.new_file('file', transform.root, 'content')
502
result = transform.find_conflicts()
503
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
505
def test_resolve_case_insensitive_conflict(self):
506
tree = self.make_branch_and_tree('tree')
507
# Don't try this at home, kids!
508
# Force the tree to report that it is case insensitive, for conflict
510
tree.case_sensitive = False
511
transform = TreeTransform(tree)
512
self.addCleanup(transform.finalize)
513
transform.new_file('file', transform.root, 'content')
514
transform.new_file('FiLe', transform.root, 'content')
515
resolve_conflicts(transform)
517
self.assertPathExists('tree/file')
518
self.assertPathExists('tree/FiLe.moved')
520
def test_resolve_checkout_case_conflict(self):
521
tree = self.make_branch_and_tree('tree')
522
# Don't try this at home, kids!
523
# Force the tree to report that it is case insensitive, for conflict
525
tree.case_sensitive = False
526
transform = TreeTransform(tree)
527
self.addCleanup(transform.finalize)
528
transform.new_file('file', transform.root, 'content')
529
transform.new_file('FiLe', transform.root, 'content')
530
resolve_conflicts(transform,
531
pass_func=lambda t, c: resolve_checkout(t, c, []))
533
self.assertPathExists('tree/file')
534
self.assertPathExists('tree/FiLe.moved')
536
def test_apply_case_conflict(self):
537
"""Ensure that a transform with case conflicts can always be applied"""
538
tree = self.make_branch_and_tree('tree')
539
transform = TreeTransform(tree)
540
self.addCleanup(transform.finalize)
541
transform.new_file('file', transform.root, 'content')
542
transform.new_file('FiLe', transform.root, 'content')
543
dir = transform.new_directory('dir', transform.root)
544
transform.new_file('dirfile', dir, 'content')
545
transform.new_file('dirFiLe', dir, 'content')
546
resolve_conflicts(transform)
548
self.assertPathExists('tree/file')
549
if not os.path.exists('tree/FiLe.moved'):
550
self.assertPathExists('tree/FiLe')
551
self.assertPathExists('tree/dir/dirfile')
552
if not os.path.exists('tree/dir/dirFiLe.moved'):
553
self.assertPathExists('tree/dir/dirFiLe')
555
def test_case_insensitive_limbo(self):
556
tree = self.make_branch_and_tree('tree')
557
# Don't try this at home, kids!
558
# Force the tree to report that it is case insensitive
559
tree.case_sensitive = False
560
transform = TreeTransform(tree)
561
self.addCleanup(transform.finalize)
562
dir = transform.new_directory('dir', transform.root)
563
first = transform.new_file('file', dir, 'content')
564
second = transform.new_file('FiLe', dir, 'content')
565
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
566
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
568
def test_adjust_path_updates_child_limbo_names(self):
569
tree = self.make_branch_and_tree('tree')
570
transform = TreeTransform(tree)
571
self.addCleanup(transform.finalize)
572
foo_id = transform.new_directory('foo', transform.root)
573
bar_id = transform.new_directory('bar', foo_id)
574
baz_id = transform.new_directory('baz', bar_id)
575
qux_id = transform.new_directory('qux', baz_id)
576
transform.adjust_path('quxx', foo_id, bar_id)
577
self.assertStartsWith(transform._limbo_name(qux_id),
578
transform._limbo_name(bar_id))
580
def test_add_del(self):
581
start, root = self.get_transform()
582
start.new_directory('a', root, 'a')
584
transform, root = self.get_transform()
585
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
586
transform.new_directory('a', root, 'a')
589
def test_unversioning(self):
590
create_tree, root = self.get_transform()
591
parent_id = create_tree.new_directory('parent', root, 'parent-id')
592
create_tree.new_file('child', parent_id, 'child', 'child-id')
594
unversion = TreeTransform(self.wt)
595
self.addCleanup(unversion.finalize)
596
parent = unversion.trans_id_tree_path('parent')
597
unversion.unversion_file(parent)
598
self.assertEqual(unversion.find_conflicts(),
599
[('unversioned parent', parent_id)])
600
file_id = unversion.trans_id_tree_file_id('child-id')
601
unversion.unversion_file(file_id)
604
def test_name_invariants(self):
605
create_tree, root = self.get_transform()
607
root = create_tree.root
608
create_tree.new_file('name1', root, 'hello1', 'name1')
609
create_tree.new_file('name2', root, 'hello2', 'name2')
610
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
611
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
612
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
613
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
616
mangle_tree,root = self.get_transform()
617
root = mangle_tree.root
619
name1 = mangle_tree.trans_id_tree_file_id('name1')
620
name2 = mangle_tree.trans_id_tree_file_id('name2')
621
mangle_tree.adjust_path('name2', root, name1)
622
mangle_tree.adjust_path('name1', root, name2)
624
#tests for deleting parent directories
625
ddir = mangle_tree.trans_id_tree_file_id('ddir')
626
mangle_tree.delete_contents(ddir)
627
dfile = mangle_tree.trans_id_tree_file_id('dfile')
628
mangle_tree.delete_versioned(dfile)
629
mangle_tree.unversion_file(dfile)
630
mfile = mangle_tree.trans_id_tree_file_id('mfile')
631
mangle_tree.adjust_path('mfile', root, mfile)
633
#tests for adding parent directories
634
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
635
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
636
mangle_tree.adjust_path('mfile2', newdir, mfile2)
637
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
638
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
639
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
640
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
642
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
643
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
644
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
645
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
646
self.assertEqual(file(mfile2_path).read(), 'later2')
647
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
648
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
649
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
650
self.assertEqual(file(newfile_path).read(), 'hello3')
651
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
652
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
653
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
655
def test_both_rename(self):
656
create_tree,root = self.get_transform()
657
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
658
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
660
mangle_tree,root = self.get_transform()
661
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
662
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
663
mangle_tree.adjust_path('test', root, selftest)
664
mangle_tree.adjust_path('test_too_much', root, selftest)
665
mangle_tree.set_executability(True, blackbox)
668
def test_both_rename2(self):
669
create_tree,root = self.get_transform()
670
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
671
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
672
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
673
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
676
mangle_tree,root = self.get_transform()
677
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
678
tests = mangle_tree.trans_id_tree_file_id('tests-id')
679
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
680
mangle_tree.adjust_path('selftest', bzrlib, tests)
681
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
682
mangle_tree.set_executability(True, test_too_much)
685
def test_both_rename3(self):
686
create_tree,root = self.get_transform()
687
tests = create_tree.new_directory('tests', root, 'tests-id')
688
create_tree.new_file('test_too_much.py', tests, 'hello1',
691
mangle_tree,root = self.get_transform()
692
tests = mangle_tree.trans_id_tree_file_id('tests-id')
693
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
694
mangle_tree.adjust_path('selftest', root, tests)
695
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
696
mangle_tree.set_executability(True, test_too_much)
699
def test_move_dangling_ie(self):
700
create_tree, root = self.get_transform()
702
root = create_tree.root
703
create_tree.new_file('name1', root, 'hello1', 'name1')
705
delete_contents, root = self.get_transform()
706
file = delete_contents.trans_id_tree_file_id('name1')
707
delete_contents.delete_contents(file)
708
delete_contents.apply()
709
move_id, root = self.get_transform()
710
name1 = move_id.trans_id_tree_file_id('name1')
711
newdir = move_id.new_directory('dir', root, 'newdir')
712
move_id.adjust_path('name2', newdir, name1)
715
def test_replace_dangling_ie(self):
716
create_tree, root = self.get_transform()
718
root = create_tree.root
719
create_tree.new_file('name1', root, 'hello1', 'name1')
721
delete_contents = TreeTransform(self.wt)
722
self.addCleanup(delete_contents.finalize)
723
file = delete_contents.trans_id_tree_file_id('name1')
724
delete_contents.delete_contents(file)
725
delete_contents.apply()
726
delete_contents.finalize()
727
replace = TreeTransform(self.wt)
728
self.addCleanup(replace.finalize)
729
name2 = replace.new_file('name2', root, 'hello2', 'name1')
730
conflicts = replace.find_conflicts()
731
name1 = replace.trans_id_tree_file_id('name1')
732
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
733
resolve_conflicts(replace)
736
def _test_symlinks(self, link_name1,link_target1,
737
link_name2, link_target2):
739
def ozpath(p): return 'oz/' + p
741
self.requireFeature(SymlinkFeature)
742
transform, root = self.get_transform()
743
oz_id = transform.new_directory('oz', root, 'oz-id')
744
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
746
wiz_id = transform.create_path(link_name2, oz_id)
747
transform.create_symlink(link_target2, wiz_id)
748
transform.version_file('wiz-id2', wiz_id)
749
transform.set_executability(True, wiz_id)
750
self.assertEqual(transform.find_conflicts(),
751
[('non-file executability', wiz_id)])
752
transform.set_executability(None, wiz_id)
754
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
755
self.assertEqual('symlink',
756
file_kind(self.wt.abspath(ozpath(link_name1))))
757
self.assertEqual(link_target2,
758
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
759
self.assertEqual(link_target1,
760
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
762
def test_symlinks(self):
763
self._test_symlinks('wizard', 'wizard-target',
764
'wizard2', 'behind_curtain')
766
def test_symlinks_unicode(self):
767
self.requireFeature(tests.UnicodeFilenameFeature)
768
self._test_symlinks(u'\N{Euro Sign}wizard',
769
u'wizard-targ\N{Euro Sign}t',
770
u'\N{Euro Sign}wizard2',
771
u'b\N{Euro Sign}hind_curtain')
773
def test_unable_create_symlink(self):
775
wt = self.make_branch_and_tree('.')
776
tt = TreeTransform(wt) # TreeTransform obtains write lock
778
tt.new_symlink('foo', tt.root, 'bar')
782
os_symlink = getattr(os, 'symlink', None)
785
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
787
"Unable to create symlink 'foo' on this platform",
791
os.symlink = os_symlink
793
def get_conflicted(self):
794
create,root = self.get_transform()
795
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
796
oz = create.new_directory('oz', root, 'oz-id')
797
create.new_directory('emeraldcity', oz, 'emerald-id')
799
conflicts,root = self.get_transform()
800
# set up duplicate entry, duplicate id
801
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
803
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
804
oz = conflicts.trans_id_tree_file_id('oz-id')
805
# set up DeletedParent parent conflict
806
conflicts.delete_versioned(oz)
807
emerald = conflicts.trans_id_tree_file_id('emerald-id')
808
# set up MissingParent conflict
809
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
810
conflicts.adjust_path('munchkincity', root, munchkincity)
811
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
813
conflicts.adjust_path('emeraldcity', emerald, emerald)
814
return conflicts, emerald, oz, old_dorothy, new_dorothy
816
def test_conflict_resolution(self):
817
conflicts, emerald, oz, old_dorothy, new_dorothy =\
818
self.get_conflicted()
819
resolve_conflicts(conflicts)
820
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
821
self.assertIs(conflicts.final_file_id(old_dorothy), None)
822
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
823
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
824
self.assertEqual(conflicts.final_parent(emerald), oz)
827
def test_cook_conflicts(self):
828
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
829
raw_conflicts = resolve_conflicts(tt)
830
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
831
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
832
'dorothy', None, 'dorothy-id')
833
self.assertEqual(cooked_conflicts[0], duplicate)
834
duplicate_id = DuplicateID('Unversioned existing file',
835
'dorothy.moved', 'dorothy', None,
837
self.assertEqual(cooked_conflicts[1], duplicate_id)
838
missing_parent = MissingParent('Created directory', 'munchkincity',
840
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
841
self.assertEqual(cooked_conflicts[2], missing_parent)
842
unversioned_parent = UnversionedParent('Versioned directory',
845
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
847
self.assertEqual(cooked_conflicts[3], unversioned_parent)
848
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
849
'oz/emeraldcity', 'emerald-id', 'emerald-id')
850
self.assertEqual(cooked_conflicts[4], deleted_parent)
851
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
852
self.assertEqual(cooked_conflicts[6], parent_loop)
853
self.assertEqual(len(cooked_conflicts), 7)
856
def test_string_conflicts(self):
857
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
858
raw_conflicts = resolve_conflicts(tt)
859
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
861
conflicts_s = [unicode(c) for c in cooked_conflicts]
862
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
863
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
864
'Moved existing file to '
866
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
867
'Unversioned existing file '
869
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
870
' munchkincity. Created directory.')
871
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
872
' versioned, but has versioned'
873
' children. Versioned directory.')
874
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
875
" is not empty. Not deleting.")
876
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
877
' versioned, but has versioned'
878
' children. Versioned directory.')
879
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
880
' oz/emeraldcity. Cancelled move.')
882
def prepare_wrong_parent_kind(self):
883
tt, root = self.get_transform()
884
tt.new_file('parent', root, 'contents', 'parent-id')
886
tt, root = self.get_transform()
887
parent_id = tt.trans_id_file_id('parent-id')
888
tt.new_file('child,', parent_id, 'contents2', 'file-id')
891
def test_find_conflicts_wrong_parent_kind(self):
892
tt = self.prepare_wrong_parent_kind()
895
def test_resolve_conflicts_wrong_existing_parent_kind(self):
896
tt = self.prepare_wrong_parent_kind()
897
raw_conflicts = resolve_conflicts(tt)
898
self.assertEqual(set([('non-directory parent', 'Created directory',
899
'new-3')]), raw_conflicts)
900
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
901
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
902
'parent-id')], cooked_conflicts)
904
self.assertEqual(None, self.wt.path2id('parent'))
905
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
907
def test_resolve_conflicts_wrong_new_parent_kind(self):
908
tt, root = self.get_transform()
909
parent_id = tt.new_directory('parent', root, 'parent-id')
910
tt.new_file('child,', parent_id, 'contents2', 'file-id')
912
tt, root = self.get_transform()
913
parent_id = tt.trans_id_file_id('parent-id')
914
tt.delete_contents(parent_id)
915
tt.create_file('contents', parent_id)
916
raw_conflicts = resolve_conflicts(tt)
917
self.assertEqual(set([('non-directory parent', 'Created directory',
918
'new-3')]), raw_conflicts)
920
self.assertEqual(None, self.wt.path2id('parent'))
921
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
923
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
924
tt, root = self.get_transform()
925
parent_id = tt.new_directory('parent', root)
926
tt.new_file('child,', parent_id, 'contents2')
928
tt, root = self.get_transform()
929
parent_id = tt.trans_id_tree_path('parent')
930
tt.delete_contents(parent_id)
931
tt.create_file('contents', parent_id)
932
resolve_conflicts(tt)
934
self.assertIs(None, self.wt.path2id('parent'))
935
self.assertIs(None, self.wt.path2id('parent.new'))
937
def test_resolve_conflicts_missing_parent(self):
938
wt = self.make_branch_and_tree('.')
939
tt = TreeTransform(wt)
940
self.addCleanup(tt.finalize)
941
parent = tt.trans_id_file_id('parent-id')
942
tt.new_file('file', parent, 'Contents')
943
raw_conflicts = resolve_conflicts(tt)
944
# Since the directory doesn't exist it's seen as 'missing'. So
945
# 'resolve_conflicts' create a conflict asking for it to be created.
946
self.assertLength(1, raw_conflicts)
947
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
949
# apply fail since the missing directory doesn't exist
950
self.assertRaises(errors.NoFinalPath, tt.apply)
952
def test_moving_versioned_directories(self):
953
create, root = self.get_transform()
954
kansas = create.new_directory('kansas', root, 'kansas-id')
955
create.new_directory('house', kansas, 'house-id')
956
create.new_directory('oz', root, 'oz-id')
958
cyclone, root = self.get_transform()
959
oz = cyclone.trans_id_tree_file_id('oz-id')
960
house = cyclone.trans_id_tree_file_id('house-id')
961
cyclone.adjust_path('house', oz, house)
964
def test_moving_root(self):
965
create, root = self.get_transform()
966
fun = create.new_directory('fun', root, 'fun-id')
967
create.new_directory('sun', root, 'sun-id')
968
create.new_directory('moon', root, 'moon')
970
transform, root = self.get_transform()
971
transform.adjust_root_path('oldroot', fun)
972
new_root = transform.trans_id_tree_path('')
973
transform.version_file('new-root', new_root)
976
def test_renames(self):
977
create, root = self.get_transform()
978
old = create.new_directory('old-parent', root, 'old-id')
979
intermediate = create.new_directory('intermediate', old, 'im-id')
980
myfile = create.new_file('myfile', intermediate, 'myfile-text',
983
rename, root = self.get_transform()
984
old = rename.trans_id_file_id('old-id')
985
rename.adjust_path('new', root, old)
986
myfile = rename.trans_id_file_id('myfile-id')
987
rename.set_executability(True, myfile)
990
def test_rename_fails(self):
991
self.requireFeature(features.not_running_as_root)
992
# see https://bugs.launchpad.net/bzr/+bug/491763
993
create, root_id = self.get_transform()
994
first_dir = create.new_directory('first-dir', root_id, 'first-id')
995
myfile = create.new_file('myfile', root_id, 'myfile-text',
998
if os.name == "posix" and sys.platform != "cygwin":
999
# posix filesystems fail on renaming if the readonly bit is set
1000
osutils.make_readonly(self.wt.abspath('first-dir'))
1001
elif os.name == "nt":
1002
# windows filesystems fail on renaming open files
1003
self.addCleanup(file(self.wt.abspath('myfile')).close)
1005
self.skip("Don't know how to force a permissions error on rename")
1006
# now transform to rename
1007
rename_transform, root_id = self.get_transform()
1008
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1009
dir_id = rename_transform.trans_id_file_id('first-id')
1010
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1011
e = self.assertRaises(errors.TransformRenameFailed,
1012
rename_transform.apply)
1013
# On nix looks like:
1014
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1015
# to .../first-dir/newname: [Errno 13] Permission denied"
1016
# On windows looks like:
1017
# "Failed to rename .../work/myfile to
1018
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1019
# This test isn't concerned with exactly what the error looks like,
1020
# and the strerror will vary across OS and locales, but the assert
1021
# that the exeception attributes are what we expect
1022
self.assertEqual(e.errno, errno.EACCES)
1023
if os.name == "posix":
1024
self.assertEndsWith(e.to_path, "/first-dir/newname")
1026
self.assertEqual(os.path.basename(e.from_path), "myfile")
1028
def test_set_executability_order(self):
1029
"""Ensure that executability behaves the same, no matter what order.
1031
- create file and set executability simultaneously
1032
- create file and set executability afterward
1033
- unsetting the executability of a file whose executability has not been
1034
declared should throw an exception (this may happen when a
1035
merge attempts to create a file with a duplicate ID)
1037
transform, root = self.get_transform()
1038
wt = transform._tree
1040
self.addCleanup(wt.unlock)
1041
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1043
sac = transform.new_file('set_after_creation', root,
1044
'Set after creation', 'sac')
1045
transform.set_executability(True, sac)
1046
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1048
self.assertRaises(KeyError, transform.set_executability, None, uws)
1050
self.assertTrue(wt.is_executable('soc'))
1051
self.assertTrue(wt.is_executable('sac'))
1053
def test_preserve_mode(self):
1054
"""File mode is preserved when replacing content"""
1055
if sys.platform == 'win32':
1056
raise TestSkipped('chmod has no effect on win32')
1057
transform, root = self.get_transform()
1058
transform.new_file('file1', root, 'contents', 'file1-id', True)
1060
self.wt.lock_write()
1061
self.addCleanup(self.wt.unlock)
1062
self.assertTrue(self.wt.is_executable('file1-id'))
1063
transform, root = self.get_transform()
1064
file1_id = transform.trans_id_tree_file_id('file1-id')
1065
transform.delete_contents(file1_id)
1066
transform.create_file('contents2', file1_id)
1068
self.assertTrue(self.wt.is_executable('file1-id'))
1070
def test__set_mode_stats_correctly(self):
1071
"""_set_mode stats to determine file mode."""
1072
if sys.platform == 'win32':
1073
raise TestSkipped('chmod has no effect on win32')
1077
def instrumented_stat(path):
1078
stat_paths.append(path)
1079
return real_stat(path)
1081
transform, root = self.get_transform()
1083
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1084
file_id='bar-id-1', executable=False)
1087
transform, root = self.get_transform()
1088
bar1_id = transform.trans_id_tree_path('bar')
1089
bar2_id = transform.trans_id_tree_path('bar2')
1091
os.stat = instrumented_stat
1092
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1095
transform.finalize()
1097
bar1_abspath = self.wt.abspath('bar')
1098
self.assertEqual([bar1_abspath], stat_paths)
1100
def test_iter_changes(self):
1101
self.wt.set_root_id('eert_toor')
1102
transform, root = self.get_transform()
1103
transform.new_file('old', root, 'blah', 'id-1', True)
1105
transform, root = self.get_transform()
1107
self.assertEqual([], list(transform.iter_changes()))
1108
old = transform.trans_id_tree_file_id('id-1')
1109
transform.unversion_file(old)
1110
self.assertEqual([('id-1', ('old', None), False, (True, False),
1111
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1112
(True, True))], list(transform.iter_changes()))
1113
transform.new_directory('new', root, 'id-1')
1114
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1115
('eert_toor', 'eert_toor'), ('old', 'new'),
1116
('file', 'directory'),
1117
(True, False))], list(transform.iter_changes()))
1119
transform.finalize()
1121
def test_iter_changes_new(self):
1122
self.wt.set_root_id('eert_toor')
1123
transform, root = self.get_transform()
1124
transform.new_file('old', root, 'blah')
1126
transform, root = self.get_transform()
1128
old = transform.trans_id_tree_path('old')
1129
transform.version_file('id-1', old)
1130
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1131
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1132
(False, False))], list(transform.iter_changes()))
1134
transform.finalize()
1136
def test_iter_changes_modifications(self):
1137
self.wt.set_root_id('eert_toor')
1138
transform, root = self.get_transform()
1139
transform.new_file('old', root, 'blah', 'id-1')
1140
transform.new_file('new', root, 'blah')
1141
transform.new_directory('subdir', root, 'subdir-id')
1143
transform, root = self.get_transform()
1145
old = transform.trans_id_tree_path('old')
1146
subdir = transform.trans_id_tree_file_id('subdir-id')
1147
new = transform.trans_id_tree_path('new')
1148
self.assertEqual([], list(transform.iter_changes()))
1151
transform.delete_contents(old)
1152
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1153
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1154
(False, False))], list(transform.iter_changes()))
1157
transform.create_file('blah', old)
1158
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1159
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1160
(False, False))], list(transform.iter_changes()))
1161
transform.cancel_deletion(old)
1162
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1163
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1164
(False, False))], list(transform.iter_changes()))
1165
transform.cancel_creation(old)
1167
# move file_id to a different file
1168
self.assertEqual([], list(transform.iter_changes()))
1169
transform.unversion_file(old)
1170
transform.version_file('id-1', new)
1171
transform.adjust_path('old', root, new)
1172
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1173
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1174
(False, False))], list(transform.iter_changes()))
1175
transform.cancel_versioning(new)
1176
transform._removed_id = set()
1179
self.assertEqual([], list(transform.iter_changes()))
1180
transform.set_executability(True, old)
1181
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1182
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1183
(False, True))], list(transform.iter_changes()))
1184
transform.set_executability(None, old)
1187
self.assertEqual([], list(transform.iter_changes()))
1188
transform.adjust_path('new', root, old)
1189
transform._new_parent = {}
1190
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1191
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1192
(False, False))], list(transform.iter_changes()))
1193
transform._new_name = {}
1196
self.assertEqual([], list(transform.iter_changes()))
1197
transform.adjust_path('new', subdir, old)
1198
transform._new_name = {}
1199
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1200
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1201
('file', 'file'), (False, False))],
1202
list(transform.iter_changes()))
1203
transform._new_path = {}
1206
transform.finalize()
1208
def test_iter_changes_modified_bleed(self):
1209
self.wt.set_root_id('eert_toor')
1210
"""Modified flag should not bleed from one change to another"""
1211
# unfortunately, we have no guarantee that file1 (which is modified)
1212
# will be applied before file2. And if it's applied after file2, it
1213
# obviously can't bleed into file2's change output. But for now, it
1215
transform, root = self.get_transform()
1216
transform.new_file('file1', root, 'blah', 'id-1')
1217
transform.new_file('file2', root, 'blah', 'id-2')
1219
transform, root = self.get_transform()
1221
transform.delete_contents(transform.trans_id_file_id('id-1'))
1222
transform.set_executability(True,
1223
transform.trans_id_file_id('id-2'))
1224
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1225
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1226
('file', None), (False, False)),
1227
('id-2', (u'file2', u'file2'), False, (True, True),
1228
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1229
('file', 'file'), (False, True))],
1230
list(transform.iter_changes()))
1232
transform.finalize()
1234
def test_iter_changes_move_missing(self):
1235
"""Test moving ids with no files around"""
1236
self.wt.set_root_id('toor_eert')
1237
# Need two steps because versioning a non-existant file is a conflict.
1238
transform, root = self.get_transform()
1239
transform.new_directory('floater', root, 'floater-id')
1241
transform, root = self.get_transform()
1242
transform.delete_contents(transform.trans_id_tree_path('floater'))
1244
transform, root = self.get_transform()
1245
floater = transform.trans_id_tree_path('floater')
1247
transform.adjust_path('flitter', root, floater)
1248
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1249
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1250
(None, None), (False, False))], list(transform.iter_changes()))
1252
transform.finalize()
1254
def test_iter_changes_pointless(self):
1255
"""Ensure that no-ops are not treated as modifications"""
1256
self.wt.set_root_id('eert_toor')
1257
transform, root = self.get_transform()
1258
transform.new_file('old', root, 'blah', 'id-1')
1259
transform.new_directory('subdir', root, 'subdir-id')
1261
transform, root = self.get_transform()
1263
old = transform.trans_id_tree_path('old')
1264
subdir = transform.trans_id_tree_file_id('subdir-id')
1265
self.assertEqual([], list(transform.iter_changes()))
1266
transform.delete_contents(subdir)
1267
transform.create_directory(subdir)
1268
transform.set_executability(False, old)
1269
transform.unversion_file(old)
1270
transform.version_file('id-1', old)
1271
transform.adjust_path('old', root, old)
1272
self.assertEqual([], list(transform.iter_changes()))
1274
transform.finalize()
1276
def test_rename_count(self):
1277
transform, root = self.get_transform()
1278
transform.new_file('name1', root, 'contents')
1279
self.assertEqual(transform.rename_count, 0)
1281
self.assertEqual(transform.rename_count, 1)
1282
transform2, root = self.get_transform()
1283
transform2.adjust_path('name2', root,
1284
transform2.trans_id_tree_path('name1'))
1285
self.assertEqual(transform2.rename_count, 0)
1287
self.assertEqual(transform2.rename_count, 2)
1289
def test_change_parent(self):
1290
"""Ensure that after we change a parent, the results are still right.
1292
Renames and parent changes on pending transforms can happen as part
1293
of conflict resolution, and are explicitly permitted by the
1296
This test ensures they work correctly with the rename-avoidance
1299
transform, root = self.get_transform()
1300
parent1 = transform.new_directory('parent1', root)
1301
child1 = transform.new_file('child1', parent1, 'contents')
1302
parent2 = transform.new_directory('parent2', root)
1303
transform.adjust_path('child1', parent2, child1)
1305
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1306
self.assertPathExists(self.wt.abspath('parent2/child1'))
1307
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1308
# no rename for child1 (counting only renames during apply)
1309
self.assertEqual(2, transform.rename_count)
1311
def test_cancel_parent(self):
1312
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1314
This is like the test_change_parent, except that we cancel the parent
1315
before adjusting the path. The transform must detect that the
1316
directory is non-empty, and move children to safe locations.
1318
transform, root = self.get_transform()
1319
parent1 = transform.new_directory('parent1', root)
1320
child1 = transform.new_file('child1', parent1, 'contents')
1321
child2 = transform.new_file('child2', parent1, 'contents')
1323
transform.cancel_creation(parent1)
1325
self.fail('Failed to move child1 before deleting parent1')
1326
transform.cancel_creation(child2)
1327
transform.create_directory(parent1)
1329
transform.cancel_creation(parent1)
1330
# If the transform incorrectly believes that child2 is still in
1331
# parent1's limbo directory, it will try to rename it and fail
1332
# because was already moved by the first cancel_creation.
1334
self.fail('Transform still thinks child2 is a child of parent1')
1335
parent2 = transform.new_directory('parent2', root)
1336
transform.adjust_path('child1', parent2, child1)
1338
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1339
self.assertPathExists(self.wt.abspath('parent2/child1'))
1340
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1341
self.assertEqual(2, transform.rename_count)
1343
def test_adjust_and_cancel(self):
1344
"""Make sure adjust_path keeps track of limbo children properly"""
1345
transform, root = self.get_transform()
1346
parent1 = transform.new_directory('parent1', root)
1347
child1 = transform.new_file('child1', parent1, 'contents')
1348
parent2 = transform.new_directory('parent2', root)
1349
transform.adjust_path('child1', parent2, child1)
1350
transform.cancel_creation(child1)
1352
transform.cancel_creation(parent1)
1353
# if the transform thinks child1 is still in parent1's limbo
1354
# directory, it will attempt to move it and fail.
1356
self.fail('Transform still thinks child1 is a child of parent1')
1357
transform.finalize()
1359
def test_noname_contents(self):
1360
"""TreeTransform should permit deferring naming files."""
1361
transform, root = self.get_transform()
1362
parent = transform.trans_id_file_id('parent-id')
1364
transform.create_directory(parent)
1366
self.fail("Can't handle contents with no name")
1367
transform.finalize()
1369
def test_noname_contents_nested(self):
1370
"""TreeTransform should permit deferring naming files."""
1371
transform, root = self.get_transform()
1372
parent = transform.trans_id_file_id('parent-id')
1374
transform.create_directory(parent)
1376
self.fail("Can't handle contents with no name")
1377
child = transform.new_directory('child', parent)
1378
transform.adjust_path('parent', root, parent)
1380
self.assertPathExists(self.wt.abspath('parent/child'))
1381
self.assertEqual(1, transform.rename_count)
1383
def test_reuse_name(self):
1384
"""Avoid reusing the same limbo name for different files"""
1385
transform, root = self.get_transform()
1386
parent = transform.new_directory('parent', root)
1387
child1 = transform.new_directory('child', parent)
1389
child2 = transform.new_directory('child', parent)
1391
self.fail('Tranform tried to use the same limbo name twice')
1392
transform.adjust_path('child2', parent, child2)
1394
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1395
# child2 is put into top-level limbo because child1 has already
1396
# claimed the direct limbo path when child2 is created. There is no
1397
# advantage in renaming files once they're in top-level limbo, except
1399
self.assertEqual(2, transform.rename_count)
1401
def test_reuse_when_first_moved(self):
1402
"""Don't avoid direct paths when it is safe to use them"""
1403
transform, root = self.get_transform()
1404
parent = transform.new_directory('parent', root)
1405
child1 = transform.new_directory('child', parent)
1406
transform.adjust_path('child1', parent, child1)
1407
child2 = transform.new_directory('child', parent)
1409
# limbo/new-1 => parent
1410
self.assertEqual(1, transform.rename_count)
1412
def test_reuse_after_cancel(self):
1413
"""Don't avoid direct paths when it is safe to use them"""
1414
transform, root = self.get_transform()
1415
parent2 = transform.new_directory('parent2', root)
1416
child1 = transform.new_directory('child1', parent2)
1417
transform.cancel_creation(parent2)
1418
transform.create_directory(parent2)
1419
child2 = transform.new_directory('child1', parent2)
1420
transform.adjust_path('child2', parent2, child1)
1422
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1423
self.assertEqual(2, transform.rename_count)
1425
def test_finalize_order(self):
1426
"""Finalize must be done in child-to-parent order"""
1427
transform, root = self.get_transform()
1428
parent = transform.new_directory('parent', root)
1429
child = transform.new_directory('child', parent)
1431
transform.finalize()
1433
self.fail('Tried to remove parent before child1')
1435
def test_cancel_with_cancelled_child_should_succeed(self):
1436
transform, root = self.get_transform()
1437
parent = transform.new_directory('parent', root)
1438
child = transform.new_directory('child', parent)
1439
transform.cancel_creation(child)
1440
transform.cancel_creation(parent)
1441
transform.finalize()
1443
def test_rollback_on_directory_clash(self):
1445
wt = self.make_branch_and_tree('.')
1446
tt = TreeTransform(wt) # TreeTransform obtains write lock
1448
foo = tt.new_directory('foo', tt.root)
1449
tt.new_file('bar', foo, 'foobar')
1450
baz = tt.new_directory('baz', tt.root)
1451
tt.new_file('qux', baz, 'quux')
1452
# Ask for a rename 'foo' -> 'baz'
1453
tt.adjust_path('baz', tt.root, foo)
1454
# Lie to tt that we've already resolved all conflicts.
1455
tt.apply(no_conflicts=True)
1459
# The rename will fail because the target directory is not empty (but
1460
# raises FileExists anyway).
1461
err = self.assertRaises(errors.FileExists, tt_helper)
1462
self.assertContainsRe(str(err),
1463
"^File exists: .+/baz")
1465
def test_two_directories_clash(self):
1467
wt = self.make_branch_and_tree('.')
1468
tt = TreeTransform(wt) # TreeTransform obtains write lock
1470
foo_1 = tt.new_directory('foo', tt.root)
1471
tt.new_directory('bar', foo_1)
1472
# Adding the same directory with a different content
1473
foo_2 = tt.new_directory('foo', tt.root)
1474
tt.new_directory('baz', foo_2)
1475
# Lie to tt that we've already resolved all conflicts.
1476
tt.apply(no_conflicts=True)
1480
err = self.assertRaises(errors.FileExists, tt_helper)
1481
self.assertContainsRe(str(err),
1482
"^File exists: .+/foo")
1484
def test_two_directories_clash_finalize(self):
1486
wt = self.make_branch_and_tree('.')
1487
tt = TreeTransform(wt) # TreeTransform obtains write lock
1489
foo_1 = tt.new_directory('foo', tt.root)
1490
tt.new_directory('bar', foo_1)
1491
# Adding the same directory with a different content
1492
foo_2 = tt.new_directory('foo', tt.root)
1493
tt.new_directory('baz', foo_2)
1494
# Lie to tt that we've already resolved all conflicts.
1495
tt.apply(no_conflicts=True)
1499
err = self.assertRaises(errors.FileExists, tt_helper)
1500
self.assertContainsRe(str(err),
1501
"^File exists: .+/foo")
1503
def test_file_to_directory(self):
1504
wt = self.make_branch_and_tree('.')
1505
self.build_tree(['foo'])
1508
tt = TreeTransform(wt)
1509
self.addCleanup(tt.finalize)
1510
foo_trans_id = tt.trans_id_tree_path("foo")
1511
tt.delete_contents(foo_trans_id)
1512
tt.create_directory(foo_trans_id)
1513
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1514
tt.create_file(["aa\n"], bar_trans_id)
1515
tt.version_file("bar-1", bar_trans_id)
1517
self.assertPathExists("foo/bar")
1520
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1525
changes = wt.changes_from(wt.basis_tree())
1526
self.assertFalse(changes.has_changed(), changes)
1528
def test_file_to_symlink(self):
1529
self.requireFeature(SymlinkFeature)
1530
wt = self.make_branch_and_tree('.')
1531
self.build_tree(['foo'])
1534
tt = TreeTransform(wt)
1535
self.addCleanup(tt.finalize)
1536
foo_trans_id = tt.trans_id_tree_path("foo")
1537
tt.delete_contents(foo_trans_id)
1538
tt.create_symlink("bar", foo_trans_id)
1540
self.assertPathExists("foo")
1542
self.addCleanup(wt.unlock)
1543
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1546
def test_dir_to_file(self):
1547
wt = self.make_branch_and_tree('.')
1548
self.build_tree(['foo/', 'foo/bar'])
1549
wt.add(['foo', 'foo/bar'])
1551
tt = TreeTransform(wt)
1552
self.addCleanup(tt.finalize)
1553
foo_trans_id = tt.trans_id_tree_path("foo")
1554
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1555
tt.delete_contents(foo_trans_id)
1556
tt.delete_versioned(bar_trans_id)
1557
tt.create_file(["aa\n"], foo_trans_id)
1559
self.assertPathExists("foo")
1561
self.addCleanup(wt.unlock)
1562
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1565
def test_dir_to_hardlink(self):
1566
self.requireFeature(HardlinkFeature)
1567
wt = self.make_branch_and_tree('.')
1568
self.build_tree(['foo/', 'foo/bar'])
1569
wt.add(['foo', 'foo/bar'])
1571
tt = TreeTransform(wt)
1572
self.addCleanup(tt.finalize)
1573
foo_trans_id = tt.trans_id_tree_path("foo")
1574
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1575
tt.delete_contents(foo_trans_id)
1576
tt.delete_versioned(bar_trans_id)
1577
self.build_tree(['baz'])
1578
tt.create_hardlink("baz", foo_trans_id)
1580
self.assertPathExists("foo")
1581
self.assertPathExists("baz")
1583
self.addCleanup(wt.unlock)
1584
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1587
def test_no_final_path(self):
1588
transform, root = self.get_transform()
1589
trans_id = transform.trans_id_file_id('foo')
1590
transform.create_file('bar', trans_id)
1591
transform.cancel_creation(trans_id)
1594
def test_create_from_tree(self):
1595
tree1 = self.make_branch_and_tree('tree1')
1596
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1597
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1598
tree2 = self.make_branch_and_tree('tree2')
1599
tt = TreeTransform(tree2)
1600
foo_trans_id = tt.create_path('foo', tt.root)
1601
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1602
bar_trans_id = tt.create_path('bar', tt.root)
1603
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1605
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1606
self.assertFileEqual('baz', 'tree2/bar')
1608
def test_create_from_tree_bytes(self):
1609
"""Provided lines are used instead of tree content."""
1610
tree1 = self.make_branch_and_tree('tree1')
1611
self.build_tree_contents([('tree1/foo', 'bar'),])
1612
tree1.add('foo', 'foo-id')
1613
tree2 = self.make_branch_and_tree('tree2')
1614
tt = TreeTransform(tree2)
1615
foo_trans_id = tt.create_path('foo', tt.root)
1616
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1618
self.assertFileEqual('qux', 'tree2/foo')
1620
def test_create_from_tree_symlink(self):
1621
self.requireFeature(SymlinkFeature)
1622
tree1 = self.make_branch_and_tree('tree1')
1623
os.symlink('bar', 'tree1/foo')
1624
tree1.add('foo', 'foo-id')
1625
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1626
foo_trans_id = tt.create_path('foo', tt.root)
1627
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1629
self.assertEqual('bar', os.readlink('tree2/foo'))
1632
class TransformGroup(object):
1634
def __init__(self, dirname, root_id):
1637
self.wt = BzrDir.create_standalone_workingtree(dirname)
1638
self.wt.set_root_id(root_id)
1639
self.b = self.wt.branch
1640
self.tt = TreeTransform(self.wt)
1641
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1644
def conflict_text(tree, merge):
1645
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1646
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1649
class TestInventoryAltered(tests.TestCaseWithTransport):
1651
def test_inventory_altered_unchanged(self):
1652
tree = self.make_branch_and_tree('tree')
1653
self.build_tree(['tree/foo'])
1654
tree.add('foo', 'foo-id')
1655
with TransformPreview(tree) as tt:
1656
self.assertEqual([], tt._inventory_altered())
1658
def test_inventory_altered_changed_parent_id(self):
1659
tree = self.make_branch_and_tree('tree')
1660
self.build_tree(['tree/foo'])
1661
tree.add('foo', 'foo-id')
1662
with TransformPreview(tree) as tt:
1663
tt.unversion_file(tt.root)
1664
tt.version_file('new-id', tt.root)
1665
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1666
foo_tuple = ('foo', foo_trans_id)
1667
root_tuple = ('', tt.root)
1668
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1670
def test_inventory_altered_noop_changed_parent_id(self):
1671
tree = self.make_branch_and_tree('tree')
1672
self.build_tree(['tree/foo'])
1673
tree.add('foo', 'foo-id')
1674
with TransformPreview(tree) as tt:
1675
tt.unversion_file(tt.root)
1676
tt.version_file(tree.get_root_id(), tt.root)
1677
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1678
self.assertEqual([], tt._inventory_altered())
1681
class TestTransformMerge(TestCaseInTempDir):
1683
def test_text_merge(self):
1684
root_id = generate_ids.gen_root_id()
1685
base = TransformGroup("base", root_id)
1686
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1687
base.tt.new_file('b', base.root, 'b1', 'b')
1688
base.tt.new_file('c', base.root, 'c', 'c')
1689
base.tt.new_file('d', base.root, 'd', 'd')
1690
base.tt.new_file('e', base.root, 'e', 'e')
1691
base.tt.new_file('f', base.root, 'f', 'f')
1692
base.tt.new_directory('g', base.root, 'g')
1693
base.tt.new_directory('h', base.root, 'h')
1695
other = TransformGroup("other", root_id)
1696
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1697
other.tt.new_file('b', other.root, 'b2', 'b')
1698
other.tt.new_file('c', other.root, 'c2', 'c')
1699
other.tt.new_file('d', other.root, 'd', 'd')
1700
other.tt.new_file('e', other.root, 'e2', 'e')
1701
other.tt.new_file('f', other.root, 'f', 'f')
1702
other.tt.new_file('g', other.root, 'g', 'g')
1703
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1704
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1706
this = TransformGroup("this", root_id)
1707
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1708
this.tt.new_file('b', this.root, 'b', 'b')
1709
this.tt.new_file('c', this.root, 'c', 'c')
1710
this.tt.new_file('d', this.root, 'd2', 'd')
1711
this.tt.new_file('e', this.root, 'e2', 'e')
1712
this.tt.new_file('f', this.root, 'f', 'f')
1713
this.tt.new_file('g', this.root, 'g', 'g')
1714
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1715
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1717
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1720
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1721
# three-way text conflict
1722
self.assertEqual(this.wt.get_file('b').read(),
1723
conflict_text('b', 'b2'))
1725
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1727
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1728
# Ambigious clean merge
1729
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1731
self.assertEqual(this.wt.get_file('f').read(), 'f')
1732
# Correct correct results when THIS == OTHER
1733
self.assertEqual(this.wt.get_file('g').read(), 'g')
1734
# Text conflict when THIS & OTHER are text and BASE is dir
1735
self.assertEqual(this.wt.get_file('h').read(),
1736
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1737
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1739
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1741
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1742
self.assertEqual(this.wt.get_file('i').read(),
1743
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1744
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1746
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1748
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1749
modified = ['a', 'b', 'c', 'h', 'i']
1750
merge_modified = this.wt.merge_modified()
1751
self.assertSubset(merge_modified, modified)
1752
self.assertEqual(len(merge_modified), len(modified))
1753
file(this.wt.id2abspath('a'), 'wb').write('booga')
1755
merge_modified = this.wt.merge_modified()
1756
self.assertSubset(merge_modified, modified)
1757
self.assertEqual(len(merge_modified), len(modified))
1761
def test_file_merge(self):
1762
self.requireFeature(SymlinkFeature)
1763
root_id = generate_ids.gen_root_id()
1764
base = TransformGroup("BASE", root_id)
1765
this = TransformGroup("THIS", root_id)
1766
other = TransformGroup("OTHER", root_id)
1767
for tg in this, base, other:
1768
tg.tt.new_directory('a', tg.root, 'a')
1769
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1770
tg.tt.new_file('c', tg.root, 'c', 'c')
1771
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1772
targets = ((base, 'base-e', 'base-f', None, None),
1773
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1774
(other, 'other-e', None, 'other-g', 'other-h'))
1775
for tg, e_target, f_target, g_target, h_target in targets:
1776
for link, target in (('e', e_target), ('f', f_target),
1777
('g', g_target), ('h', h_target)):
1778
if target is not None:
1779
tg.tt.new_symlink(link, tg.root, target, link)
1781
for tg in this, base, other:
1783
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1784
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1785
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1786
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1787
for suffix in ('THIS', 'BASE', 'OTHER'):
1788
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1789
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1790
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1791
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1792
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1793
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1794
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1795
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1796
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1797
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1798
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1799
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1800
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1801
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1803
def test_filename_merge(self):
1804
root_id = generate_ids.gen_root_id()
1805
base = TransformGroup("BASE", root_id)
1806
this = TransformGroup("THIS", root_id)
1807
other = TransformGroup("OTHER", root_id)
1808
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1809
for t in [base, this, other]]
1810
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1811
for t in [base, this, other]]
1812
base.tt.new_directory('c', base_a, 'c')
1813
this.tt.new_directory('c1', this_a, 'c')
1814
other.tt.new_directory('c', other_b, 'c')
1816
base.tt.new_directory('d', base_a, 'd')
1817
this.tt.new_directory('d1', this_b, 'd')
1818
other.tt.new_directory('d', other_a, 'd')
1820
base.tt.new_directory('e', base_a, 'e')
1821
this.tt.new_directory('e', this_a, 'e')
1822
other.tt.new_directory('e1', other_b, 'e')
1824
base.tt.new_directory('f', base_a, 'f')
1825
this.tt.new_directory('f1', this_b, 'f')
1826
other.tt.new_directory('f1', other_b, 'f')
1828
for tg in [this, base, other]:
1830
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1831
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1832
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1833
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1834
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1836
def test_filename_merge_conflicts(self):
1837
root_id = generate_ids.gen_root_id()
1838
base = TransformGroup("BASE", root_id)
1839
this = TransformGroup("THIS", root_id)
1840
other = TransformGroup("OTHER", root_id)
1841
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1842
for t in [base, this, other]]
1843
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1844
for t in [base, this, other]]
1846
base.tt.new_file('g', base_a, 'g', 'g')
1847
other.tt.new_file('g1', other_b, 'g1', 'g')
1849
base.tt.new_file('h', base_a, 'h', 'h')
1850
this.tt.new_file('h1', this_b, 'h1', 'h')
1852
base.tt.new_file('i', base.root, 'i', 'i')
1853
other.tt.new_directory('i1', this_b, 'i')
1855
for tg in [this, base, other]:
1857
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1859
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1860
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1861
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1862
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1863
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1864
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1865
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1868
class TestBuildTree(tests.TestCaseWithTransport):
1870
def test_build_tree_with_symlinks(self):
1871
self.requireFeature(SymlinkFeature)
1873
a = BzrDir.create_standalone_workingtree('a')
1875
file('a/foo/bar', 'wb').write('contents')
1876
os.symlink('a/foo/bar', 'a/foo/baz')
1877
a.add(['foo', 'foo/bar', 'foo/baz'])
1878
a.commit('initial commit')
1879
b = BzrDir.create_standalone_workingtree('b')
1880
basis = a.basis_tree()
1882
self.addCleanup(basis.unlock)
1883
build_tree(basis, b)
1884
self.assertIs(os.path.isdir('b/foo'), True)
1885
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1886
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1888
def test_build_with_references(self):
1889
tree = self.make_branch_and_tree('source',
1890
format='dirstate-with-subtree')
1891
subtree = self.make_branch_and_tree('source/subtree',
1892
format='dirstate-with-subtree')
1893
tree.add_reference(subtree)
1894
tree.commit('a revision')
1895
tree.branch.create_checkout('target')
1896
self.assertPathExists('target')
1897
self.assertPathExists('target/subtree')
1899
def test_file_conflict_handling(self):
1900
"""Ensure that when building trees, conflict handling is done"""
1901
source = self.make_branch_and_tree('source')
1902
target = self.make_branch_and_tree('target')
1903
self.build_tree(['source/file', 'target/file'])
1904
source.add('file', 'new-file')
1905
source.commit('added file')
1906
build_tree(source.basis_tree(), target)
1907
self.assertEqual([DuplicateEntry('Moved existing file to',
1908
'file.moved', 'file', None, 'new-file')],
1910
target2 = self.make_branch_and_tree('target2')
1911
target_file = file('target2/file', 'wb')
1913
source_file = file('source/file', 'rb')
1915
target_file.write(source_file.read())
1920
build_tree(source.basis_tree(), target2)
1921
self.assertEqual([], target2.conflicts())
1923
def test_symlink_conflict_handling(self):
1924
"""Ensure that when building trees, conflict handling is done"""
1925
self.requireFeature(SymlinkFeature)
1926
source = self.make_branch_and_tree('source')
1927
os.symlink('foo', 'source/symlink')
1928
source.add('symlink', 'new-symlink')
1929
source.commit('added file')
1930
target = self.make_branch_and_tree('target')
1931
os.symlink('bar', 'target/symlink')
1932
build_tree(source.basis_tree(), target)
1933
self.assertEqual([DuplicateEntry('Moved existing file to',
1934
'symlink.moved', 'symlink', None, 'new-symlink')],
1936
target = self.make_branch_and_tree('target2')
1937
os.symlink('foo', 'target2/symlink')
1938
build_tree(source.basis_tree(), target)
1939
self.assertEqual([], target.conflicts())
1941
def test_directory_conflict_handling(self):
1942
"""Ensure that when building trees, conflict handling is done"""
1943
source = self.make_branch_and_tree('source')
1944
target = self.make_branch_and_tree('target')
1945
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1946
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1947
source.commit('added file')
1948
build_tree(source.basis_tree(), target)
1949
self.assertEqual([], target.conflicts())
1950
self.assertPathExists('target/dir1/file')
1952
# Ensure contents are merged
1953
target = self.make_branch_and_tree('target2')
1954
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1955
build_tree(source.basis_tree(), target)
1956
self.assertEqual([], target.conflicts())
1957
self.assertPathExists('target2/dir1/file2')
1958
self.assertPathExists('target2/dir1/file')
1960
# Ensure new contents are suppressed for existing branches
1961
target = self.make_branch_and_tree('target3')
1962
self.make_branch('target3/dir1')
1963
self.build_tree(['target3/dir1/file2'])
1964
build_tree(source.basis_tree(), target)
1965
self.assertPathDoesNotExist('target3/dir1/file')
1966
self.assertPathExists('target3/dir1/file2')
1967
self.assertPathExists('target3/dir1.diverted/file')
1968
self.assertEqual([DuplicateEntry('Diverted to',
1969
'dir1.diverted', 'dir1', 'new-dir1', None)],
1972
target = self.make_branch_and_tree('target4')
1973
self.build_tree(['target4/dir1/'])
1974
self.make_branch('target4/dir1/file')
1975
build_tree(source.basis_tree(), target)
1976
self.assertPathExists('target4/dir1/file')
1977
self.assertEqual('directory', file_kind('target4/dir1/file'))
1978
self.assertPathExists('target4/dir1/file.diverted')
1979
self.assertEqual([DuplicateEntry('Diverted to',
1980
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1983
def test_mixed_conflict_handling(self):
1984
"""Ensure that when building trees, conflict handling is done"""
1985
source = self.make_branch_and_tree('source')
1986
target = self.make_branch_and_tree('target')
1987
self.build_tree(['source/name', 'target/name/'])
1988
source.add('name', 'new-name')
1989
source.commit('added file')
1990
build_tree(source.basis_tree(), target)
1991
self.assertEqual([DuplicateEntry('Moved existing file to',
1992
'name.moved', 'name', None, 'new-name')], target.conflicts())
1994
def test_raises_in_populated(self):
1995
source = self.make_branch_and_tree('source')
1996
self.build_tree(['source/name'])
1998
source.commit('added name')
1999
target = self.make_branch_and_tree('target')
2000
self.build_tree(['target/name'])
2002
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2003
build_tree, source.basis_tree(), target)
2005
def test_build_tree_rename_count(self):
2006
source = self.make_branch_and_tree('source')
2007
self.build_tree(['source/file1', 'source/dir1/'])
2008
source.add(['file1', 'dir1'])
2009
source.commit('add1')
2010
target1 = self.make_branch_and_tree('target1')
2011
transform_result = build_tree(source.basis_tree(), target1)
2012
self.assertEqual(2, transform_result.rename_count)
2014
self.build_tree(['source/dir1/file2'])
2015
source.add(['dir1/file2'])
2016
source.commit('add3')
2017
target2 = self.make_branch_and_tree('target2')
2018
transform_result = build_tree(source.basis_tree(), target2)
2019
# children of non-root directories should not be renamed
2020
self.assertEqual(2, transform_result.rename_count)
2022
def create_ab_tree(self):
2023
"""Create a committed test tree with two files"""
2024
source = self.make_branch_and_tree('source')
2025
self.build_tree_contents([('source/file1', 'A')])
2026
self.build_tree_contents([('source/file2', 'B')])
2027
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2028
source.commit('commit files')
2030
self.addCleanup(source.unlock)
2033
def test_build_tree_accelerator_tree(self):
2034
source = self.create_ab_tree()
2035
self.build_tree_contents([('source/file2', 'C')])
2037
real_source_get_file = source.get_file
2038
def get_file(file_id, path=None):
2039
calls.append(file_id)
2040
return real_source_get_file(file_id, path)
2041
source.get_file = get_file
2042
target = self.make_branch_and_tree('target')
2043
revision_tree = source.basis_tree()
2044
revision_tree.lock_read()
2045
self.addCleanup(revision_tree.unlock)
2046
build_tree(revision_tree, target, source)
2047
self.assertEqual(['file1-id'], calls)
2049
self.addCleanup(target.unlock)
2050
self.assertEqual([], list(target.iter_changes(revision_tree)))
2052
def test_build_tree_accelerator_tree_observes_sha1(self):
2053
source = self.create_ab_tree()
2054
sha1 = osutils.sha_string('A')
2055
target = self.make_branch_and_tree('target')
2057
self.addCleanup(target.unlock)
2058
state = target.current_dirstate()
2059
state._cutoff_time = time.time() + 60
2060
build_tree(source.basis_tree(), target, source)
2061
entry = state._get_entry(0, path_utf8='file1')
2062
self.assertEqual(sha1, entry[1][0][1])
2064
def test_build_tree_accelerator_tree_missing_file(self):
2065
source = self.create_ab_tree()
2066
os.unlink('source/file1')
2067
source.remove(['file2'])
2068
target = self.make_branch_and_tree('target')
2069
revision_tree = source.basis_tree()
2070
revision_tree.lock_read()
2071
self.addCleanup(revision_tree.unlock)
2072
build_tree(revision_tree, target, source)
2074
self.addCleanup(target.unlock)
2075
self.assertEqual([], list(target.iter_changes(revision_tree)))
2077
def test_build_tree_accelerator_wrong_kind(self):
2078
self.requireFeature(SymlinkFeature)
2079
source = self.make_branch_and_tree('source')
2080
self.build_tree_contents([('source/file1', '')])
2081
self.build_tree_contents([('source/file2', '')])
2082
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2083
source.commit('commit files')
2084
os.unlink('source/file2')
2085
self.build_tree_contents([('source/file2/', 'C')])
2086
os.unlink('source/file1')
2087
os.symlink('file2', 'source/file1')
2089
real_source_get_file = source.get_file
2090
def get_file(file_id, path=None):
2091
calls.append(file_id)
2092
return real_source_get_file(file_id, path)
2093
source.get_file = get_file
2094
target = self.make_branch_and_tree('target')
2095
revision_tree = source.basis_tree()
2096
revision_tree.lock_read()
2097
self.addCleanup(revision_tree.unlock)
2098
build_tree(revision_tree, target, source)
2099
self.assertEqual([], calls)
2101
self.addCleanup(target.unlock)
2102
self.assertEqual([], list(target.iter_changes(revision_tree)))
2104
def test_build_tree_hardlink(self):
2105
self.requireFeature(HardlinkFeature)
2106
source = self.create_ab_tree()
2107
target = self.make_branch_and_tree('target')
2108
revision_tree = source.basis_tree()
2109
revision_tree.lock_read()
2110
self.addCleanup(revision_tree.unlock)
2111
build_tree(revision_tree, target, source, hardlink=True)
2113
self.addCleanup(target.unlock)
2114
self.assertEqual([], list(target.iter_changes(revision_tree)))
2115
source_stat = os.stat('source/file1')
2116
target_stat = os.stat('target/file1')
2117
self.assertEqual(source_stat, target_stat)
2119
# Explicitly disallowing hardlinks should prevent them.
2120
target2 = self.make_branch_and_tree('target2')
2121
build_tree(revision_tree, target2, source, hardlink=False)
2123
self.addCleanup(target2.unlock)
2124
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2125
source_stat = os.stat('source/file1')
2126
target2_stat = os.stat('target2/file1')
2127
self.assertNotEqual(source_stat, target2_stat)
2129
def test_build_tree_accelerator_tree_moved(self):
2130
source = self.make_branch_and_tree('source')
2131
self.build_tree_contents([('source/file1', 'A')])
2132
source.add(['file1'], ['file1-id'])
2133
source.commit('commit files')
2134
source.rename_one('file1', 'file2')
2136
self.addCleanup(source.unlock)
2137
target = self.make_branch_and_tree('target')
2138
revision_tree = source.basis_tree()
2139
revision_tree.lock_read()
2140
self.addCleanup(revision_tree.unlock)
2141
build_tree(revision_tree, target, source)
2143
self.addCleanup(target.unlock)
2144
self.assertEqual([], list(target.iter_changes(revision_tree)))
2146
def test_build_tree_hardlinks_preserve_execute(self):
2147
self.requireFeature(HardlinkFeature)
2148
source = self.create_ab_tree()
2149
tt = TreeTransform(source)
2150
trans_id = tt.trans_id_tree_file_id('file1-id')
2151
tt.set_executability(True, trans_id)
2153
self.assertTrue(source.is_executable('file1-id'))
2154
target = self.make_branch_and_tree('target')
2155
revision_tree = source.basis_tree()
2156
revision_tree.lock_read()
2157
self.addCleanup(revision_tree.unlock)
2158
build_tree(revision_tree, target, source, hardlink=True)
2160
self.addCleanup(target.unlock)
2161
self.assertEqual([], list(target.iter_changes(revision_tree)))
2162
self.assertTrue(source.is_executable('file1-id'))
2164
def install_rot13_content_filter(self, pattern):
2166
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2167
# below, but that looks a bit... hard to read even if it's exactly
2169
original_registry = filters._reset_registry()
2170
def restore_registry():
2171
filters._reset_registry(original_registry)
2172
self.addCleanup(restore_registry)
2173
def rot13(chunks, context=None):
2174
return [''.join(chunks).encode('rot13')]
2175
rot13filter = filters.ContentFilter(rot13, rot13)
2176
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2177
os.mkdir(self.test_home_dir + '/.bazaar')
2178
rules_filename = self.test_home_dir + '/.bazaar/rules'
2179
f = open(rules_filename, 'wb')
2180
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2182
def uninstall_rules():
2183
os.remove(rules_filename)
2185
self.addCleanup(uninstall_rules)
2188
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2189
"""build_tree will not hardlink files that have content filtering rules
2190
applied to them (but will still hardlink other files from the same tree
2193
self.requireFeature(HardlinkFeature)
2194
self.install_rot13_content_filter('file1')
2195
source = self.create_ab_tree()
2196
target = self.make_branch_and_tree('target')
2197
revision_tree = source.basis_tree()
2198
revision_tree.lock_read()
2199
self.addCleanup(revision_tree.unlock)
2200
build_tree(revision_tree, target, source, hardlink=True)
2202
self.addCleanup(target.unlock)
2203
self.assertEqual([], list(target.iter_changes(revision_tree)))
2204
source_stat = os.stat('source/file1')
2205
target_stat = os.stat('target/file1')
2206
self.assertNotEqual(source_stat, target_stat)
2207
source_stat = os.stat('source/file2')
2208
target_stat = os.stat('target/file2')
2209
self.assertEqualStat(source_stat, target_stat)
2211
def test_case_insensitive_build_tree_inventory(self):
2212
if (tests.CaseInsensitiveFilesystemFeature.available()
2213
or tests.CaseInsCasePresFilenameFeature.available()):
2214
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2215
source = self.make_branch_and_tree('source')
2216
self.build_tree(['source/file', 'source/FILE'])
2217
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2218
source.commit('added files')
2219
# Don't try this at home, kids!
2220
# Force the tree to report that it is case insensitive
2221
target = self.make_branch_and_tree('target')
2222
target.case_sensitive = False
2223
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2224
self.assertEqual('file.moved', target.id2path('lower-id'))
2225
self.assertEqual('FILE', target.id2path('upper-id'))
2227
def test_build_tree_observes_sha(self):
2228
source = self.make_branch_and_tree('source')
2229
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2230
source.add(['file1', 'dir', 'dir/file2'],
2231
['file1-id', 'dir-id', 'file2-id'])
2232
source.commit('new files')
2233
target = self.make_branch_and_tree('target')
2235
self.addCleanup(target.unlock)
2236
# We make use of the fact that DirState caches its cutoff time. So we
2237
# set the 'safe' time to one minute in the future.
2238
state = target.current_dirstate()
2239
state._cutoff_time = time.time() + 60
2240
build_tree(source.basis_tree(), target)
2241
entry1_sha = osutils.sha_file_by_name('source/file1')
2242
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2243
# entry[1] is the state information, entry[1][0] is the state of the
2244
# working tree, entry[1][0][1] is the sha value for the current working
2246
entry1 = state._get_entry(0, path_utf8='file1')
2247
self.assertEqual(entry1_sha, entry1[1][0][1])
2248
# The 'size' field must also be set.
2249
self.assertEqual(25, entry1[1][0][2])
2250
entry1_state = entry1[1][0]
2251
entry2 = state._get_entry(0, path_utf8='dir/file2')
2252
self.assertEqual(entry2_sha, entry2[1][0][1])
2253
self.assertEqual(29, entry2[1][0][2])
2254
entry2_state = entry2[1][0]
2255
# Now, make sure that we don't have to re-read the content. The
2256
# packed_stat should match exactly.
2257
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2258
self.assertEqual(entry2_sha,
2259
target.get_file_sha1('file2-id', 'dir/file2'))
2260
self.assertEqual(entry1_state, entry1[1][0])
2261
self.assertEqual(entry2_state, entry2[1][0])
2264
class TestCommitTransform(tests.TestCaseWithTransport):
2266
def get_branch(self):
2267
tree = self.make_branch_and_tree('tree')
2269
self.addCleanup(tree.unlock)
2270
tree.commit('empty commit')
2273
def get_branch_and_transform(self):
2274
branch = self.get_branch()
2275
tt = TransformPreview(branch.basis_tree())
2276
self.addCleanup(tt.finalize)
2279
def test_commit_wrong_basis(self):
2280
branch = self.get_branch()
2281
basis = branch.repository.revision_tree(
2282
_mod_revision.NULL_REVISION)
2283
tt = TransformPreview(basis)
2284
self.addCleanup(tt.finalize)
2285
e = self.assertRaises(ValueError, tt.commit, branch, '')
2286
self.assertEqual('TreeTransform not based on branch basis: null:',
2289
def test_empy_commit(self):
2290
branch, tt = self.get_branch_and_transform()
2291
rev = tt.commit(branch, 'my message')
2292
self.assertEqual(2, branch.revno())
2293
repo = branch.repository
2294
self.assertEqual('my message', repo.get_revision(rev).message)
2296
def test_merge_parents(self):
2297
branch, tt = self.get_branch_and_transform()
2298
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2299
self.assertEqual(['rev1b', 'rev1c'],
2300
branch.basis_tree().get_parent_ids()[1:])
2302
def test_first_commit(self):
2303
branch = self.make_branch('branch')
2305
self.addCleanup(branch.unlock)
2306
tt = TransformPreview(branch.basis_tree())
2307
self.addCleanup(tt.finalize)
2308
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2309
rev = tt.commit(branch, 'my message')
2310
self.assertEqual([], branch.basis_tree().get_parent_ids())
2311
self.assertNotEqual(_mod_revision.NULL_REVISION,
2312
branch.last_revision())
2314
def test_first_commit_with_merge_parents(self):
2315
branch = self.make_branch('branch')
2317
self.addCleanup(branch.unlock)
2318
tt = TransformPreview(branch.basis_tree())
2319
self.addCleanup(tt.finalize)
2320
e = self.assertRaises(ValueError, tt.commit, branch,
2321
'my message', ['rev1b-id'])
2322
self.assertEqual('Cannot supply merge parents for first commit.',
2324
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2326
def test_add_files(self):
2327
branch, tt = self.get_branch_and_transform()
2328
tt.new_file('file', tt.root, 'contents', 'file-id')
2329
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2330
if SymlinkFeature.available():
2331
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2332
rev = tt.commit(branch, 'message')
2333
tree = branch.basis_tree()
2334
self.assertEqual('file', tree.id2path('file-id'))
2335
self.assertEqual('contents', tree.get_file_text('file-id'))
2336
self.assertEqual('dir', tree.id2path('dir-id'))
2337
if SymlinkFeature.available():
2338
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2339
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2341
def test_add_unversioned(self):
2342
branch, tt = self.get_branch_and_transform()
2343
tt.new_file('file', tt.root, 'contents')
2344
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2345
'message', strict=True)
2347
def test_modify_strict(self):
2348
branch, tt = self.get_branch_and_transform()
2349
tt.new_file('file', tt.root, 'contents', 'file-id')
2350
tt.commit(branch, 'message', strict=True)
2351
tt = TransformPreview(branch.basis_tree())
2352
self.addCleanup(tt.finalize)
2353
trans_id = tt.trans_id_file_id('file-id')
2354
tt.delete_contents(trans_id)
2355
tt.create_file('contents', trans_id)
2356
tt.commit(branch, 'message', strict=True)
2358
def test_commit_malformed(self):
2359
"""Committing a malformed transform should raise an exception.
2361
In this case, we are adding a file without adding its parent.
2363
branch, tt = self.get_branch_and_transform()
2364
parent_id = tt.trans_id_file_id('parent-id')
2365
tt.new_file('file', parent_id, 'contents', 'file-id')
2366
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2369
def test_commit_rich_revision_data(self):
2370
branch, tt = self.get_branch_and_transform()
2371
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2372
committer='me <me@example.com>',
2373
revprops={'foo': 'bar'}, revision_id='revid-1',
2374
authors=['Author1 <author1@example.com>',
2375
'Author2 <author2@example.com>',
2377
self.assertEqual('revid-1', rev_id)
2378
revision = branch.repository.get_revision(rev_id)
2379
self.assertEqual(1, revision.timestamp)
2380
self.assertEqual(43201, revision.timezone)
2381
self.assertEqual('me <me@example.com>', revision.committer)
2382
self.assertEqual(['Author1 <author1@example.com>',
2383
'Author2 <author2@example.com>'],
2384
revision.get_apparent_authors())
2385
del revision.properties['authors']
2386
self.assertEqual({'foo': 'bar',
2387
'branch-nick': 'tree'},
2388
revision.properties)
2390
def test_no_explicit_revprops(self):
2391
branch, tt = self.get_branch_and_transform()
2392
rev_id = tt.commit(branch, 'message', authors=[
2393
'Author1 <author1@example.com>',
2394
'Author2 <author2@example.com>', ])
2395
revision = branch.repository.get_revision(rev_id)
2396
self.assertEqual(['Author1 <author1@example.com>',
2397
'Author2 <author2@example.com>'],
2398
revision.get_apparent_authors())
2399
self.assertEqual('tree', revision.properties['branch-nick'])
2402
class TestBackupName(tests.TestCase):
2404
def test_deprecations(self):
2405
class MockTransform(object):
2407
def has_named_child(self, by_parent, parent_id, name):
2408
return name in by_parent.get(parent_id, [])
2410
class MockEntry(object):
2413
object.__init__(self)
2416
tt = MockTransform()
2417
name1 = self.applyDeprecated(
2418
symbol_versioning.deprecated_in((2, 3, 0)),
2419
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2420
self.assertEqual('name.~1~', name1)
2421
name2 = self.applyDeprecated(
2422
symbol_versioning.deprecated_in((2, 3, 0)),
2423
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2424
self.assertEqual('name.~2~', name2)
2427
class TestFileMover(tests.TestCaseWithTransport):
2429
def test_file_mover(self):
2430
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2431
mover = _FileMover()
2432
mover.rename('a', 'q')
2433
self.assertPathExists('q')
2434
self.assertPathDoesNotExist('a')
2435
self.assertPathExists('q/b')
2436
self.assertPathExists('c')
2437
self.assertPathExists('c/d')
2439
def test_pre_delete_rollback(self):
2440
self.build_tree(['a/'])
2441
mover = _FileMover()
2442
mover.pre_delete('a', 'q')
2443
self.assertPathExists('q')
2444
self.assertPathDoesNotExist('a')
2446
self.assertPathDoesNotExist('q')
2447
self.assertPathExists('a')
2449
def test_apply_deletions(self):
2450
self.build_tree(['a/', 'b/'])
2451
mover = _FileMover()
2452
mover.pre_delete('a', 'q')
2453
mover.pre_delete('b', 'r')
2454
self.assertPathExists('q')
2455
self.assertPathExists('r')
2456
self.assertPathDoesNotExist('a')
2457
self.assertPathDoesNotExist('b')
2458
mover.apply_deletions()
2459
self.assertPathDoesNotExist('q')
2460
self.assertPathDoesNotExist('r')
2461
self.assertPathDoesNotExist('a')
2462
self.assertPathDoesNotExist('b')
2464
def test_file_mover_rollback(self):
2465
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2466
mover = _FileMover()
2467
mover.rename('c/d', 'c/f')
2468
mover.rename('c/e', 'c/d')
2470
mover.rename('a', 'c')
2471
except errors.FileExists, e:
2473
self.assertPathExists('a')
2474
self.assertPathExists('c/d')
2477
class Bogus(Exception):
2481
class TestTransformRollback(tests.TestCaseWithTransport):
2483
class ExceptionFileMover(_FileMover):
2485
def __init__(self, bad_source=None, bad_target=None):
2486
_FileMover.__init__(self)
2487
self.bad_source = bad_source
2488
self.bad_target = bad_target
2490
def rename(self, source, target):
2491
if (self.bad_source is not None and
2492
source.endswith(self.bad_source)):
2494
elif (self.bad_target is not None and
2495
target.endswith(self.bad_target)):
2498
_FileMover.rename(self, source, target)
2500
def test_rollback_rename(self):
2501
tree = self.make_branch_and_tree('.')
2502
self.build_tree(['a/', 'a/b'])
2503
tt = TreeTransform(tree)
2504
self.addCleanup(tt.finalize)
2505
a_id = tt.trans_id_tree_path('a')
2506
tt.adjust_path('c', tt.root, a_id)
2507
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2508
self.assertRaises(Bogus, tt.apply,
2509
_mover=self.ExceptionFileMover(bad_source='a'))
2510
self.assertPathExists('a')
2511
self.assertPathExists('a/b')
2513
self.assertPathExists('c')
2514
self.assertPathExists('c/d')
2516
def test_rollback_rename_into_place(self):
2517
tree = self.make_branch_and_tree('.')
2518
self.build_tree(['a/', 'a/b'])
2519
tt = TreeTransform(tree)
2520
self.addCleanup(tt.finalize)
2521
a_id = tt.trans_id_tree_path('a')
2522
tt.adjust_path('c', tt.root, a_id)
2523
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2524
self.assertRaises(Bogus, tt.apply,
2525
_mover=self.ExceptionFileMover(bad_target='c/d'))
2526
self.assertPathExists('a')
2527
self.assertPathExists('a/b')
2529
self.assertPathExists('c')
2530
self.assertPathExists('c/d')
2532
def test_rollback_deletion(self):
2533
tree = self.make_branch_and_tree('.')
2534
self.build_tree(['a/', 'a/b'])
2535
tt = TreeTransform(tree)
2536
self.addCleanup(tt.finalize)
2537
a_id = tt.trans_id_tree_path('a')
2538
tt.delete_contents(a_id)
2539
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2540
self.assertRaises(Bogus, tt.apply,
2541
_mover=self.ExceptionFileMover(bad_target='d'))
2542
self.assertPathExists('a')
2543
self.assertPathExists('a/b')
2546
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2547
"""Ensure treetransform creation errors can be safely cleaned up after"""
2549
def _override_globals_in_method(self, instance, method_name, globals):
2550
"""Replace method on instance with one with updated globals"""
2552
func = getattr(instance, method_name).im_func
2553
new_globals = dict(func.func_globals)
2554
new_globals.update(globals)
2555
new_func = types.FunctionType(func.func_code, new_globals,
2556
func.func_name, func.func_defaults)
2557
setattr(instance, method_name,
2558
types.MethodType(new_func, instance, instance.__class__))
2559
self.addCleanup(delattr, instance, method_name)
2562
def _fake_open_raises_before(name, mode):
2563
"""Like open() but raises before doing anything"""
2567
def _fake_open_raises_after(name, mode):
2568
"""Like open() but raises after creating file without returning"""
2569
open(name, mode).close()
2572
def create_transform_and_root_trans_id(self):
2573
"""Setup a transform creating a file in limbo"""
2574
tree = self.make_branch_and_tree('.')
2575
tt = TreeTransform(tree)
2576
return tt, tt.create_path("a", tt.root)
2578
def create_transform_and_subdir_trans_id(self):
2579
"""Setup a transform creating a directory containing a file in limbo"""
2580
tree = self.make_branch_and_tree('.')
2581
tt = TreeTransform(tree)
2582
d_trans_id = tt.create_path("d", tt.root)
2583
tt.create_directory(d_trans_id)
2584
f_trans_id = tt.create_path("a", d_trans_id)
2585
tt.adjust_path("a", d_trans_id, f_trans_id)
2586
return tt, f_trans_id
2588
def test_root_create_file_open_raises_before_creation(self):
2589
tt, trans_id = self.create_transform_and_root_trans_id()
2590
self._override_globals_in_method(tt, "create_file",
2591
{"open": self._fake_open_raises_before})
2592
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2593
path = tt._limbo_name(trans_id)
2594
self.assertPathDoesNotExist(path)
2596
self.assertPathDoesNotExist(tt._limbodir)
2598
def test_root_create_file_open_raises_after_creation(self):
2599
tt, trans_id = self.create_transform_and_root_trans_id()
2600
self._override_globals_in_method(tt, "create_file",
2601
{"open": self._fake_open_raises_after})
2602
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2603
path = tt._limbo_name(trans_id)
2604
self.assertPathExists(path)
2606
self.assertPathDoesNotExist(path)
2607
self.assertPathDoesNotExist(tt._limbodir)
2609
def test_subdir_create_file_open_raises_before_creation(self):
2610
tt, trans_id = self.create_transform_and_subdir_trans_id()
2611
self._override_globals_in_method(tt, "create_file",
2612
{"open": self._fake_open_raises_before})
2613
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2614
path = tt._limbo_name(trans_id)
2615
self.assertPathDoesNotExist(path)
2617
self.assertPathDoesNotExist(tt._limbodir)
2619
def test_subdir_create_file_open_raises_after_creation(self):
2620
tt, trans_id = self.create_transform_and_subdir_trans_id()
2621
self._override_globals_in_method(tt, "create_file",
2622
{"open": self._fake_open_raises_after})
2623
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2624
path = tt._limbo_name(trans_id)
2625
self.assertPathExists(path)
2627
self.assertPathDoesNotExist(path)
2628
self.assertPathDoesNotExist(tt._limbodir)
2630
def test_rename_in_limbo_rename_raises_after_rename(self):
2631
tt, trans_id = self.create_transform_and_root_trans_id()
2632
parent1 = tt.new_directory('parent1', tt.root)
2633
child1 = tt.new_file('child1', parent1, 'contents')
2634
parent2 = tt.new_directory('parent2', tt.root)
2636
class FakeOSModule(object):
2637
def rename(self, old, new):
2640
self._override_globals_in_method(tt, "_rename_in_limbo",
2641
{"os": FakeOSModule()})
2643
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2644
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2645
self.assertPathExists(path)
2647
self.assertPathDoesNotExist(path)
2648
self.assertPathDoesNotExist(tt._limbodir)
2650
def test_rename_in_limbo_rename_raises_before_rename(self):
2651
tt, trans_id = self.create_transform_and_root_trans_id()
2652
parent1 = tt.new_directory('parent1', tt.root)
2653
child1 = tt.new_file('child1', parent1, 'contents')
2654
parent2 = tt.new_directory('parent2', tt.root)
2656
class FakeOSModule(object):
2657
def rename(self, old, new):
2659
self._override_globals_in_method(tt, "_rename_in_limbo",
2660
{"os": FakeOSModule()})
2662
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2663
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2664
self.assertPathExists(path)
2666
self.assertPathDoesNotExist(path)
2667
self.assertPathDoesNotExist(tt._limbodir)
2670
class TestTransformMissingParent(tests.TestCaseWithTransport):
2672
def make_tt_with_versioned_dir(self):
2673
wt = self.make_branch_and_tree('.')
2674
self.build_tree(['dir/',])
2675
wt.add(['dir'], ['dir-id'])
2676
wt.commit('Create dir')
2677
tt = TreeTransform(wt)
2678
self.addCleanup(tt.finalize)
2681
def test_resolve_create_parent_for_versioned_file(self):
2682
wt, tt = self.make_tt_with_versioned_dir()
2683
dir_tid = tt.trans_id_tree_file_id('dir-id')
2684
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2685
tt.delete_contents(dir_tid)
2686
tt.unversion_file(dir_tid)
2687
conflicts = resolve_conflicts(tt)
2688
# one conflict for the missing directory, one for the unversioned
2690
self.assertLength(2, conflicts)
2692
def test_non_versioned_file_create_conflict(self):
2693
wt, tt = self.make_tt_with_versioned_dir()
2694
dir_tid = tt.trans_id_tree_file_id('dir-id')
2695
tt.new_file('file', dir_tid, 'Contents')
2696
tt.delete_contents(dir_tid)
2697
tt.unversion_file(dir_tid)
2698
conflicts = resolve_conflicts(tt)
2699
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2700
self.assertLength(1, conflicts)
2701
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2705
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2706
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2708
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2709
('', ''), ('directory', 'directory'), (False, False))
2712
class TestTransformPreview(tests.TestCaseWithTransport):
2714
def create_tree(self):
2715
tree = self.make_branch_and_tree('.')
2716
self.build_tree_contents([('a', 'content 1')])
2717
tree.set_root_id('TREE_ROOT')
2718
tree.add('a', 'a-id')
2719
tree.commit('rev1', rev_id='rev1')
2720
return tree.branch.repository.revision_tree('rev1')
2722
def get_empty_preview(self):
2723
repository = self.make_repository('repo')
2724
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2725
preview = TransformPreview(tree)
2726
self.addCleanup(preview.finalize)
2729
def test_transform_preview(self):
2730
revision_tree = self.create_tree()
2731
preview = TransformPreview(revision_tree)
2732
self.addCleanup(preview.finalize)
2734
def test_transform_preview_tree(self):
2735
revision_tree = self.create_tree()
2736
preview = TransformPreview(revision_tree)
2737
self.addCleanup(preview.finalize)
2738
preview.get_preview_tree()
2740
def test_transform_new_file(self):
2741
revision_tree = self.create_tree()
2742
preview = TransformPreview(revision_tree)
2743
self.addCleanup(preview.finalize)
2744
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2745
preview_tree = preview.get_preview_tree()
2746
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2748
preview_tree.get_file('file2-id').read(), 'content B\n')
2750
def test_diff_preview_tree(self):
2751
revision_tree = self.create_tree()
2752
preview = TransformPreview(revision_tree)
2753
self.addCleanup(preview.finalize)
2754
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2755
preview_tree = preview.get_preview_tree()
2757
show_diff_trees(revision_tree, preview_tree, out)
2758
lines = out.getvalue().splitlines()
2759
self.assertEqual(lines[0], "=== added file 'file2'")
2760
# 3 lines of diff administrivia
2761
self.assertEqual(lines[4], "+content B")
2763
def test_transform_conflicts(self):
2764
revision_tree = self.create_tree()
2765
preview = TransformPreview(revision_tree)
2766
self.addCleanup(preview.finalize)
2767
preview.new_file('a', preview.root, 'content 2')
2768
resolve_conflicts(preview)
2769
trans_id = preview.trans_id_file_id('a-id')
2770
self.assertEqual('a.moved', preview.final_name(trans_id))
2772
def get_tree_and_preview_tree(self):
2773
revision_tree = self.create_tree()
2774
preview = TransformPreview(revision_tree)
2775
self.addCleanup(preview.finalize)
2776
a_trans_id = preview.trans_id_file_id('a-id')
2777
preview.delete_contents(a_trans_id)
2778
preview.create_file('b content', a_trans_id)
2779
preview_tree = preview.get_preview_tree()
2780
return revision_tree, preview_tree
2782
def test_iter_changes(self):
2783
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2784
root = revision_tree.inventory.root.file_id
2785
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2786
(root, root), ('a', 'a'), ('file', 'file'),
2788
list(preview_tree.iter_changes(revision_tree)))
2790
def test_include_unchanged_succeeds(self):
2791
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2792
changes = preview_tree.iter_changes(revision_tree,
2793
include_unchanged=True)
2794
root = revision_tree.inventory.root.file_id
2796
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2798
def test_specific_files(self):
2799
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2800
changes = preview_tree.iter_changes(revision_tree,
2801
specific_files=[''])
2802
self.assertEqual([A_ENTRY], list(changes))
2804
def test_want_unversioned(self):
2805
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2806
changes = preview_tree.iter_changes(revision_tree,
2807
want_unversioned=True)
2808
self.assertEqual([A_ENTRY], list(changes))
2810
def test_ignore_extra_trees_no_specific_files(self):
2811
# extra_trees is harmless without specific_files, so we'll silently
2812
# accept it, even though we won't use it.
2813
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2814
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2816
def test_ignore_require_versioned_no_specific_files(self):
2817
# require_versioned is meaningless without specific_files.
2818
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2819
preview_tree.iter_changes(revision_tree, require_versioned=False)
2821
def test_ignore_pb(self):
2822
# pb could be supported, but TT.iter_changes doesn't support it.
2823
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2824
preview_tree.iter_changes(revision_tree)
2826
def test_kind(self):
2827
revision_tree = self.create_tree()
2828
preview = TransformPreview(revision_tree)
2829
self.addCleanup(preview.finalize)
2830
preview.new_file('file', preview.root, 'contents', 'file-id')
2831
preview.new_directory('directory', preview.root, 'dir-id')
2832
preview_tree = preview.get_preview_tree()
2833
self.assertEqual('file', preview_tree.kind('file-id'))
2834
self.assertEqual('directory', preview_tree.kind('dir-id'))
2836
def test_get_file_mtime(self):
2837
preview = self.get_empty_preview()
2838
file_trans_id = preview.new_file('file', preview.root, 'contents',
2840
limbo_path = preview._limbo_name(file_trans_id)
2841
preview_tree = preview.get_preview_tree()
2842
self.assertEqual(os.stat(limbo_path).st_mtime,
2843
preview_tree.get_file_mtime('file-id'))
2845
def test_get_file_mtime_renamed(self):
2846
work_tree = self.make_branch_and_tree('tree')
2847
self.build_tree(['tree/file'])
2848
work_tree.add('file', 'file-id')
2849
preview = TransformPreview(work_tree)
2850
self.addCleanup(preview.finalize)
2851
file_trans_id = preview.trans_id_tree_file_id('file-id')
2852
preview.adjust_path('renamed', preview.root, file_trans_id)
2853
preview_tree = preview.get_preview_tree()
2854
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2855
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2857
def test_get_file_size(self):
2858
work_tree = self.make_branch_and_tree('tree')
2859
self.build_tree_contents([('tree/old', 'old')])
2860
work_tree.add('old', 'old-id')
2861
preview = TransformPreview(work_tree)
2862
self.addCleanup(preview.finalize)
2863
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2865
tree = preview.get_preview_tree()
2866
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2867
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2869
def test_get_file(self):
2870
preview = self.get_empty_preview()
2871
preview.new_file('file', preview.root, 'contents', 'file-id')
2872
preview_tree = preview.get_preview_tree()
2873
tree_file = preview_tree.get_file('file-id')
2875
self.assertEqual('contents', tree_file.read())
2879
def test_get_symlink_target(self):
2880
self.requireFeature(SymlinkFeature)
2881
preview = self.get_empty_preview()
2882
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2883
preview_tree = preview.get_preview_tree()
2884
self.assertEqual('target',
2885
preview_tree.get_symlink_target('symlink-id'))
2887
def test_all_file_ids(self):
2888
tree = self.make_branch_and_tree('tree')
2889
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2890
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2891
preview = TransformPreview(tree)
2892
self.addCleanup(preview.finalize)
2893
preview.unversion_file(preview.trans_id_file_id('b-id'))
2894
c_trans_id = preview.trans_id_file_id('c-id')
2895
preview.unversion_file(c_trans_id)
2896
preview.version_file('c-id', c_trans_id)
2897
preview_tree = preview.get_preview_tree()
2898
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2899
preview_tree.all_file_ids())
2901
def test_path2id_deleted_unchanged(self):
2902
tree = self.make_branch_and_tree('tree')
2903
self.build_tree(['tree/unchanged', 'tree/deleted'])
2904
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2905
preview = TransformPreview(tree)
2906
self.addCleanup(preview.finalize)
2907
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2908
preview_tree = preview.get_preview_tree()
2909
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2910
self.assertIs(None, preview_tree.path2id('deleted'))
2912
def test_path2id_created(self):
2913
tree = self.make_branch_and_tree('tree')
2914
self.build_tree(['tree/unchanged'])
2915
tree.add(['unchanged'], ['unchanged-id'])
2916
preview = TransformPreview(tree)
2917
self.addCleanup(preview.finalize)
2918
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2919
'contents', 'new-id')
2920
preview_tree = preview.get_preview_tree()
2921
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2923
def test_path2id_moved(self):
2924
tree = self.make_branch_and_tree('tree')
2925
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2926
tree.add(['old_parent', 'old_parent/child'],
2927
['old_parent-id', 'child-id'])
2928
preview = TransformPreview(tree)
2929
self.addCleanup(preview.finalize)
2930
new_parent = preview.new_directory('new_parent', preview.root,
2932
preview.adjust_path('child', new_parent,
2933
preview.trans_id_file_id('child-id'))
2934
preview_tree = preview.get_preview_tree()
2935
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2936
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2938
def test_path2id_renamed_parent(self):
2939
tree = self.make_branch_and_tree('tree')
2940
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2941
tree.add(['old_name', 'old_name/child'],
2942
['parent-id', 'child-id'])
2943
preview = TransformPreview(tree)
2944
self.addCleanup(preview.finalize)
2945
preview.adjust_path('new_name', preview.root,
2946
preview.trans_id_file_id('parent-id'))
2947
preview_tree = preview.get_preview_tree()
2948
self.assertIs(None, preview_tree.path2id('old_name/child'))
2949
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2951
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2952
preview_tree = tt.get_preview_tree()
2953
preview_result = list(preview_tree.iter_entries_by_dir(
2957
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2958
self.assertEqual(actual_result, preview_result)
2960
def test_iter_entries_by_dir_new(self):
2961
tree = self.make_branch_and_tree('tree')
2962
tt = TreeTransform(tree)
2963
tt.new_file('new', tt.root, 'contents', 'new-id')
2964
self.assertMatchingIterEntries(tt)
2966
def test_iter_entries_by_dir_deleted(self):
2967
tree = self.make_branch_and_tree('tree')
2968
self.build_tree(['tree/deleted'])
2969
tree.add('deleted', 'deleted-id')
2970
tt = TreeTransform(tree)
2971
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2972
self.assertMatchingIterEntries(tt)
2974
def test_iter_entries_by_dir_unversioned(self):
2975
tree = self.make_branch_and_tree('tree')
2976
self.build_tree(['tree/removed'])
2977
tree.add('removed', 'removed-id')
2978
tt = TreeTransform(tree)
2979
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2980
self.assertMatchingIterEntries(tt)
2982
def test_iter_entries_by_dir_moved(self):
2983
tree = self.make_branch_and_tree('tree')
2984
self.build_tree(['tree/moved', 'tree/new_parent/'])
2985
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2986
tt = TreeTransform(tree)
2987
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2988
tt.trans_id_file_id('moved-id'))
2989
self.assertMatchingIterEntries(tt)
2991
def test_iter_entries_by_dir_specific_file_ids(self):
2992
tree = self.make_branch_and_tree('tree')
2993
tree.set_root_id('tree-root-id')
2994
self.build_tree(['tree/parent/', 'tree/parent/child'])
2995
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2996
tt = TreeTransform(tree)
2997
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2999
def test_symlink_content_summary(self):
3000
self.requireFeature(SymlinkFeature)
3001
preview = self.get_empty_preview()
3002
preview.new_symlink('path', preview.root, 'target', 'path-id')
3003
summary = preview.get_preview_tree().path_content_summary('path')
3004
self.assertEqual(('symlink', None, None, 'target'), summary)
3006
def test_missing_content_summary(self):
3007
preview = self.get_empty_preview()
3008
summary = preview.get_preview_tree().path_content_summary('path')
3009
self.assertEqual(('missing', None, None, None), summary)
3011
def test_deleted_content_summary(self):
3012
tree = self.make_branch_and_tree('tree')
3013
self.build_tree(['tree/path/'])
3015
preview = TransformPreview(tree)
3016
self.addCleanup(preview.finalize)
3017
preview.delete_contents(preview.trans_id_tree_path('path'))
3018
summary = preview.get_preview_tree().path_content_summary('path')
3019
self.assertEqual(('missing', None, None, None), summary)
3021
def test_file_content_summary_executable(self):
3022
preview = self.get_empty_preview()
3023
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3024
preview.set_executability(True, path_id)
3025
summary = preview.get_preview_tree().path_content_summary('path')
3026
self.assertEqual(4, len(summary))
3027
self.assertEqual('file', summary[0])
3028
# size must be known
3029
self.assertEqual(len('contents'), summary[1])
3031
self.assertEqual(True, summary[2])
3032
# will not have hash (not cheap to determine)
3033
self.assertIs(None, summary[3])
3035
def test_change_executability(self):
3036
tree = self.make_branch_and_tree('tree')
3037
self.build_tree(['tree/path'])
3039
preview = TransformPreview(tree)
3040
self.addCleanup(preview.finalize)
3041
path_id = preview.trans_id_tree_path('path')
3042
preview.set_executability(True, path_id)
3043
summary = preview.get_preview_tree().path_content_summary('path')
3044
self.assertEqual(True, summary[2])
3046
def test_file_content_summary_non_exec(self):
3047
preview = self.get_empty_preview()
3048
preview.new_file('path', preview.root, 'contents', 'path-id')
3049
summary = preview.get_preview_tree().path_content_summary('path')
3050
self.assertEqual(4, len(summary))
3051
self.assertEqual('file', summary[0])
3052
# size must be known
3053
self.assertEqual(len('contents'), summary[1])
3055
self.assertEqual(False, summary[2])
3056
# will not have hash (not cheap to determine)
3057
self.assertIs(None, summary[3])
3059
def test_dir_content_summary(self):
3060
preview = self.get_empty_preview()
3061
preview.new_directory('path', preview.root, 'path-id')
3062
summary = preview.get_preview_tree().path_content_summary('path')
3063
self.assertEqual(('directory', None, None, None), summary)
3065
def test_tree_content_summary(self):
3066
preview = self.get_empty_preview()
3067
path = preview.new_directory('path', preview.root, 'path-id')
3068
preview.set_tree_reference('rev-1', path)
3069
summary = preview.get_preview_tree().path_content_summary('path')
3070
self.assertEqual(4, len(summary))
3071
self.assertEqual('tree-reference', summary[0])
3073
def test_annotate(self):
3074
tree = self.make_branch_and_tree('tree')
3075
self.build_tree_contents([('tree/file', 'a\n')])
3076
tree.add('file', 'file-id')
3077
tree.commit('a', rev_id='one')
3078
self.build_tree_contents([('tree/file', 'a\nb\n')])
3079
preview = TransformPreview(tree)
3080
self.addCleanup(preview.finalize)
3081
file_trans_id = preview.trans_id_file_id('file-id')
3082
preview.delete_contents(file_trans_id)
3083
preview.create_file('a\nb\nc\n', file_trans_id)
3084
preview_tree = preview.get_preview_tree()
3090
annotation = preview_tree.annotate_iter('file-id', 'me:')
3091
self.assertEqual(expected, annotation)
3093
def test_annotate_missing(self):
3094
preview = self.get_empty_preview()
3095
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3096
preview_tree = preview.get_preview_tree()
3102
annotation = preview_tree.annotate_iter('file-id', 'me:')
3103
self.assertEqual(expected, annotation)
3105
def test_annotate_rename(self):
3106
tree = self.make_branch_and_tree('tree')
3107
self.build_tree_contents([('tree/file', 'a\n')])
3108
tree.add('file', 'file-id')
3109
tree.commit('a', rev_id='one')
3110
preview = TransformPreview(tree)
3111
self.addCleanup(preview.finalize)
3112
file_trans_id = preview.trans_id_file_id('file-id')
3113
preview.adjust_path('newname', preview.root, file_trans_id)
3114
preview_tree = preview.get_preview_tree()
3118
annotation = preview_tree.annotate_iter('file-id', 'me:')
3119
self.assertEqual(expected, annotation)
3121
def test_annotate_deleted(self):
3122
tree = self.make_branch_and_tree('tree')
3123
self.build_tree_contents([('tree/file', 'a\n')])
3124
tree.add('file', 'file-id')
3125
tree.commit('a', rev_id='one')
3126
self.build_tree_contents([('tree/file', 'a\nb\n')])
3127
preview = TransformPreview(tree)
3128
self.addCleanup(preview.finalize)
3129
file_trans_id = preview.trans_id_file_id('file-id')
3130
preview.delete_contents(file_trans_id)
3131
preview_tree = preview.get_preview_tree()
3132
annotation = preview_tree.annotate_iter('file-id', 'me:')
3133
self.assertIs(None, annotation)
3135
def test_stored_kind(self):
3136
preview = self.get_empty_preview()
3137
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3138
preview_tree = preview.get_preview_tree()
3139
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3141
def test_is_executable(self):
3142
preview = self.get_empty_preview()
3143
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3144
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3145
preview_tree = preview.get_preview_tree()
3146
self.assertEqual(True, preview_tree.is_executable('file-id'))
3148
def test_get_set_parent_ids(self):
3149
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3150
self.assertEqual([], preview_tree.get_parent_ids())
3151
preview_tree.set_parent_ids(['rev-1'])
3152
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3154
def test_plan_file_merge(self):
3155
work_a = self.make_branch_and_tree('wta')
3156
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3157
work_a.add('file', 'file-id')
3158
base_id = work_a.commit('base version')
3159
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3160
preview = TransformPreview(work_a)
3161
self.addCleanup(preview.finalize)
3162
trans_id = preview.trans_id_file_id('file-id')
3163
preview.delete_contents(trans_id)
3164
preview.create_file('b\nc\nd\ne\n', trans_id)
3165
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3166
tree_a = preview.get_preview_tree()
3167
tree_a.set_parent_ids([base_id])
3169
('killed-a', 'a\n'),
3170
('killed-b', 'b\n'),
3171
('unchanged', 'c\n'),
3172
('unchanged', 'd\n'),
3175
], list(tree_a.plan_file_merge('file-id', tree_b)))
3177
def test_plan_file_merge_revision_tree(self):
3178
work_a = self.make_branch_and_tree('wta')
3179
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3180
work_a.add('file', 'file-id')
3181
base_id = work_a.commit('base version')
3182
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3183
preview = TransformPreview(work_a.basis_tree())
3184
self.addCleanup(preview.finalize)
3185
trans_id = preview.trans_id_file_id('file-id')
3186
preview.delete_contents(trans_id)
3187
preview.create_file('b\nc\nd\ne\n', trans_id)
3188
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3189
tree_a = preview.get_preview_tree()
3190
tree_a.set_parent_ids([base_id])
3192
('killed-a', 'a\n'),
3193
('killed-b', 'b\n'),
3194
('unchanged', 'c\n'),
3195
('unchanged', 'd\n'),
3198
], list(tree_a.plan_file_merge('file-id', tree_b)))
3200
def test_walkdirs(self):
3201
preview = self.get_empty_preview()
3202
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3203
# FIXME: new_directory should mark root.
3204
preview.fixup_new_roots()
3205
preview_tree = preview.get_preview_tree()
3206
file_trans_id = preview.new_file('a', preview.root, 'contents',
3208
expected = [(('', 'tree-root'),
3209
[('a', 'a', 'file', None, 'a-id', 'file')])]
3210
self.assertEqual(expected, list(preview_tree.walkdirs()))
3212
def test_extras(self):
3213
work_tree = self.make_branch_and_tree('tree')
3214
self.build_tree(['tree/removed-file', 'tree/existing-file',
3215
'tree/not-removed-file'])
3216
work_tree.add(['removed-file', 'not-removed-file'])
3217
preview = TransformPreview(work_tree)
3218
self.addCleanup(preview.finalize)
3219
preview.new_file('new-file', preview.root, 'contents')
3220
preview.new_file('new-versioned-file', preview.root, 'contents',
3222
tree = preview.get_preview_tree()
3223
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3224
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3227
def test_merge_into_preview(self):
3228
work_tree = self.make_branch_and_tree('tree')
3229
self.build_tree_contents([('tree/file','b\n')])
3230
work_tree.add('file', 'file-id')
3231
work_tree.commit('first commit')
3232
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3233
self.build_tree_contents([('child/file','b\nc\n')])
3234
child_tree.commit('child commit')
3235
child_tree.lock_write()
3236
self.addCleanup(child_tree.unlock)
3237
work_tree.lock_write()
3238
self.addCleanup(work_tree.unlock)
3239
preview = TransformPreview(work_tree)
3240
self.addCleanup(preview.finalize)
3241
file_trans_id = preview.trans_id_file_id('file-id')
3242
preview.delete_contents(file_trans_id)
3243
preview.create_file('a\nb\n', file_trans_id)
3244
preview_tree = preview.get_preview_tree()
3245
merger = Merger.from_revision_ids(None, preview_tree,
3246
child_tree.branch.last_revision(),
3247
other_branch=child_tree.branch,
3248
tree_branch=work_tree.branch)
3249
merger.merge_type = Merge3Merger
3250
tt = merger.make_merger().make_preview_transform()
3251
self.addCleanup(tt.finalize)
3252
final_tree = tt.get_preview_tree()
3253
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3255
def test_merge_preview_into_workingtree(self):
3256
tree = self.make_branch_and_tree('tree')
3257
tree.set_root_id('TREE_ROOT')
3258
tt = TransformPreview(tree)
3259
self.addCleanup(tt.finalize)
3260
tt.new_file('name', tt.root, 'content', 'file-id')
3261
tree2 = self.make_branch_and_tree('tree2')
3262
tree2.set_root_id('TREE_ROOT')
3263
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3264
None, tree.basis_tree())
3265
merger.merge_type = Merge3Merger
3268
def test_merge_preview_into_workingtree_handles_conflicts(self):
3269
tree = self.make_branch_and_tree('tree')
3270
self.build_tree_contents([('tree/foo', 'bar')])
3271
tree.add('foo', 'foo-id')
3273
tt = TransformPreview(tree)
3274
self.addCleanup(tt.finalize)
3275
trans_id = tt.trans_id_file_id('foo-id')
3276
tt.delete_contents(trans_id)
3277
tt.create_file('baz', trans_id)
3278
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3279
self.build_tree_contents([('tree2/foo', 'qux')])
3281
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3282
pb, tree.basis_tree())
3283
merger.merge_type = Merge3Merger
3286
def test_has_filename(self):
3287
wt = self.make_branch_and_tree('tree')
3288
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3289
tt = TransformPreview(wt)
3290
removed_id = tt.trans_id_tree_path('removed')
3291
tt.delete_contents(removed_id)
3292
tt.new_file('new', tt.root, 'contents')
3293
modified_id = tt.trans_id_tree_path('modified')
3294
tt.delete_contents(modified_id)
3295
tt.create_file('modified-contents', modified_id)
3296
self.addCleanup(tt.finalize)
3297
tree = tt.get_preview_tree()
3298
self.assertTrue(tree.has_filename('unmodified'))
3299
self.assertFalse(tree.has_filename('not-present'))
3300
self.assertFalse(tree.has_filename('removed'))
3301
self.assertTrue(tree.has_filename('new'))
3302
self.assertTrue(tree.has_filename('modified'))
3304
def test_is_executable(self):
3305
tree = self.make_branch_and_tree('tree')
3306
preview = TransformPreview(tree)
3307
self.addCleanup(preview.finalize)
3308
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3309
preview_tree = preview.get_preview_tree()
3310
self.assertEqual(False, preview_tree.is_executable('baz-id',
3312
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3314
def test_commit_preview_tree(self):
3315
tree = self.make_branch_and_tree('tree')
3316
rev_id = tree.commit('rev1')
3317
tree.branch.lock_write()
3318
self.addCleanup(tree.branch.unlock)
3319
tt = TransformPreview(tree)
3320
tt.new_file('file', tt.root, 'contents', 'file_id')
3321
self.addCleanup(tt.finalize)
3322
preview = tt.get_preview_tree()
3323
preview.set_parent_ids([rev_id])
3324
builder = tree.branch.get_commit_builder([rev_id])
3325
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3326
builder.finish_inventory()
3327
rev2_id = builder.commit('rev2')
3328
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3329
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3331
def test_ascii_limbo_paths(self):
3332
self.requireFeature(tests.UnicodeFilenameFeature)
3333
branch = self.make_branch('any')
3334
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3335
tt = TransformPreview(tree)
3336
self.addCleanup(tt.finalize)
3337
foo_id = tt.new_directory('', ROOT_PARENT)
3338
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3339
limbo_path = tt._limbo_name(bar_id)
3340
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3343
class FakeSerializer(object):
3344
"""Serializer implementation that simply returns the input.
3346
The input is returned in the order used by pack.ContainerPushParser.
3349
def bytes_record(bytes, names):
3353
class TestSerializeTransform(tests.TestCaseWithTransport):
3355
_test_needs_features = [tests.UnicodeFilenameFeature]
3357
def get_preview(self, tree=None):
3359
tree = self.make_branch_and_tree('tree')
3360
tt = TransformPreview(tree)
3361
self.addCleanup(tt.finalize)
3364
def assertSerializesTo(self, expected, tt):
3365
records = list(tt.serialize(FakeSerializer()))
3366
self.assertEqual(expected, records)
3369
def default_attribs():
3374
'_new_executability': {},
3376
'_tree_path_ids': {'': 'new-0'},
3378
'_removed_contents': [],
3379
'_non_present_ids': {},
3382
def make_records(self, attribs, contents):
3384
(((('attribs'),),), bencode.bencode(attribs))]
3385
records.extend([(((n, k),), c) for n, k, c in contents])
3388
def creation_records(self):
3389
attribs = self.default_attribs()
3390
attribs['_id_number'] = 3
3391
attribs['_new_name'] = {
3392
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3393
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3394
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3395
attribs['_new_executability'] = {'new-1': 1}
3397
('new-1', 'file', 'i 1\nbar\n'),
3398
('new-2', 'directory', ''),
3400
return self.make_records(attribs, contents)
3402
def test_serialize_creation(self):
3403
tt = self.get_preview()
3404
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3405
tt.new_directory('qux', tt.root, 'quxx')
3406
self.assertSerializesTo(self.creation_records(), tt)
3408
def test_deserialize_creation(self):
3409
tt = self.get_preview()
3410
tt.deserialize(iter(self.creation_records()))
3411
self.assertEqual(3, tt._id_number)
3412
self.assertEqual({'new-1': u'foo\u1234',
3413
'new-2': 'qux'}, tt._new_name)
3414
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3415
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3416
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3417
self.assertEqual({'new-1': True}, tt._new_executability)
3418
self.assertEqual({'new-1': 'file',
3419
'new-2': 'directory'}, tt._new_contents)
3420
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3422
foo_content = foo_limbo.read()
3425
self.assertEqual('bar', foo_content)
3427
def symlink_creation_records(self):
3428
attribs = self.default_attribs()
3429
attribs['_id_number'] = 2
3430
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3431
attribs['_new_parent'] = {'new-1': 'new-0'}
3432
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3433
return self.make_records(attribs, contents)
3435
def test_serialize_symlink_creation(self):
3436
self.requireFeature(tests.SymlinkFeature)
3437
tt = self.get_preview()
3438
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3439
self.assertSerializesTo(self.symlink_creation_records(), tt)
3441
def test_deserialize_symlink_creation(self):
3442
self.requireFeature(tests.SymlinkFeature)
3443
tt = self.get_preview()
3444
tt.deserialize(iter(self.symlink_creation_records()))
3445
abspath = tt._limbo_name('new-1')
3446
foo_content = osutils.readlink(abspath)
3447
self.assertEqual(u'bar\u1234', foo_content)
3449
def make_destruction_preview(self):
3450
tree = self.make_branch_and_tree('.')
3451
self.build_tree([u'foo\u1234', 'bar'])
3452
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3453
return self.get_preview(tree)
3455
def destruction_records(self):
3456
attribs = self.default_attribs()
3457
attribs['_id_number'] = 3
3458
attribs['_removed_id'] = ['new-1']
3459
attribs['_removed_contents'] = ['new-2']
3460
attribs['_tree_path_ids'] = {
3462
u'foo\u1234'.encode('utf-8'): 'new-1',
3465
return self.make_records(attribs, [])
3467
def test_serialize_destruction(self):
3468
tt = self.make_destruction_preview()
3469
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3470
tt.unversion_file(foo_trans_id)
3471
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3472
tt.delete_contents(bar_trans_id)
3473
self.assertSerializesTo(self.destruction_records(), tt)
3475
def test_deserialize_destruction(self):
3476
tt = self.make_destruction_preview()
3477
tt.deserialize(iter(self.destruction_records()))
3478
self.assertEqual({u'foo\u1234': 'new-1',
3480
'': tt.root}, tt._tree_path_ids)
3481
self.assertEqual({'new-1': u'foo\u1234',
3483
tt.root: ''}, tt._tree_id_paths)
3484
self.assertEqual(set(['new-1']), tt._removed_id)
3485
self.assertEqual(set(['new-2']), tt._removed_contents)
3487
def missing_records(self):
3488
attribs = self.default_attribs()
3489
attribs['_id_number'] = 2
3490
attribs['_non_present_ids'] = {
3492
return self.make_records(attribs, [])
3494
def test_serialize_missing(self):
3495
tt = self.get_preview()
3496
boo_trans_id = tt.trans_id_file_id('boo')
3497
self.assertSerializesTo(self.missing_records(), tt)
3499
def test_deserialize_missing(self):
3500
tt = self.get_preview()
3501
tt.deserialize(iter(self.missing_records()))
3502
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3504
def make_modification_preview(self):
3505
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3506
LINES_TWO = 'z\nbb\nx\ndd\n'
3507
tree = self.make_branch_and_tree('tree')
3508
self.build_tree_contents([('tree/file', LINES_ONE)])
3509
tree.add('file', 'file-id')
3510
return self.get_preview(tree), LINES_TWO
3512
def modification_records(self):
3513
attribs = self.default_attribs()
3514
attribs['_id_number'] = 2
3515
attribs['_tree_path_ids'] = {
3518
attribs['_removed_contents'] = ['new-1']
3519
contents = [('new-1', 'file',
3520
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3521
return self.make_records(attribs, contents)
3523
def test_serialize_modification(self):
3524
tt, LINES = self.make_modification_preview()
3525
trans_id = tt.trans_id_file_id('file-id')
3526
tt.delete_contents(trans_id)
3527
tt.create_file(LINES, trans_id)
3528
self.assertSerializesTo(self.modification_records(), tt)
3530
def test_deserialize_modification(self):
3531
tt, LINES = self.make_modification_preview()
3532
tt.deserialize(iter(self.modification_records()))
3533
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3535
def make_kind_change_preview(self):
3536
LINES = 'a\nb\nc\nd\n'
3537
tree = self.make_branch_and_tree('tree')
3538
self.build_tree(['tree/foo/'])
3539
tree.add('foo', 'foo-id')
3540
return self.get_preview(tree), LINES
3542
def kind_change_records(self):
3543
attribs = self.default_attribs()
3544
attribs['_id_number'] = 2
3545
attribs['_tree_path_ids'] = {
3548
attribs['_removed_contents'] = ['new-1']
3549
contents = [('new-1', 'file',
3550
'i 4\na\nb\nc\nd\n\n')]
3551
return self.make_records(attribs, contents)
3553
def test_serialize_kind_change(self):
3554
tt, LINES = self.make_kind_change_preview()
3555
trans_id = tt.trans_id_file_id('foo-id')
3556
tt.delete_contents(trans_id)
3557
tt.create_file(LINES, trans_id)
3558
self.assertSerializesTo(self.kind_change_records(), tt)
3560
def test_deserialize_kind_change(self):
3561
tt, LINES = self.make_kind_change_preview()
3562
tt.deserialize(iter(self.kind_change_records()))
3563
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3565
def make_add_contents_preview(self):
3566
LINES = 'a\nb\nc\nd\n'
3567
tree = self.make_branch_and_tree('tree')
3568
self.build_tree(['tree/foo'])
3570
os.unlink('tree/foo')
3571
return self.get_preview(tree), LINES
3573
def add_contents_records(self):
3574
attribs = self.default_attribs()
3575
attribs['_id_number'] = 2
3576
attribs['_tree_path_ids'] = {
3579
contents = [('new-1', 'file',
3580
'i 4\na\nb\nc\nd\n\n')]
3581
return self.make_records(attribs, contents)
3583
def test_serialize_add_contents(self):
3584
tt, LINES = self.make_add_contents_preview()
3585
trans_id = tt.trans_id_tree_path('foo')
3586
tt.create_file(LINES, trans_id)
3587
self.assertSerializesTo(self.add_contents_records(), tt)
3589
def test_deserialize_add_contents(self):
3590
tt, LINES = self.make_add_contents_preview()
3591
tt.deserialize(iter(self.add_contents_records()))
3592
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3594
def test_get_parents_lines(self):
3595
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3596
LINES_TWO = 'z\nbb\nx\ndd\n'
3597
tree = self.make_branch_and_tree('tree')
3598
self.build_tree_contents([('tree/file', LINES_ONE)])
3599
tree.add('file', 'file-id')
3600
tt = self.get_preview(tree)
3601
trans_id = tt.trans_id_tree_path('file')
3602
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3603
tt._get_parents_lines(trans_id))
3605
def test_get_parents_texts(self):
3606
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3607
LINES_TWO = 'z\nbb\nx\ndd\n'
3608
tree = self.make_branch_and_tree('tree')
3609
self.build_tree_contents([('tree/file', LINES_ONE)])
3610
tree.add('file', 'file-id')
3611
tt = self.get_preview(tree)
3612
trans_id = tt.trans_id_tree_path('file')
3613
self.assertEqual((LINES_ONE,),
3614
tt._get_parents_texts(trans_id))
3617
class TestOrphan(tests.TestCaseWithTransport):
3619
def test_no_orphan_for_transform_preview(self):
3620
tree = self.make_branch_and_tree('tree')
3621
tt = transform.TransformPreview(tree)
3622
self.addCleanup(tt.finalize)
3623
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3625
def _set_orphan_policy(self, wt, policy):
3626
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3629
def _prepare_orphan(self, wt):
3630
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3631
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3632
wt.commit('add dir and file ignoring foo')
3633
tt = transform.TreeTransform(wt)
3634
self.addCleanup(tt.finalize)
3635
# dir and bar are deleted
3636
dir_tid = tt.trans_id_tree_path('dir')
3637
file_tid = tt.trans_id_tree_path('dir/file')
3638
orphan_tid = tt.trans_id_tree_path('dir/foo')
3639
tt.delete_contents(file_tid)
3640
tt.unversion_file(file_tid)
3641
tt.delete_contents(dir_tid)
3642
tt.unversion_file(dir_tid)
3643
# There should be a conflict because dir still contain foo
3644
raw_conflicts = tt.find_conflicts()
3645
self.assertLength(1, raw_conflicts)
3646
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3647
return tt, orphan_tid
3649
def test_new_orphan_created(self):
3650
wt = self.make_branch_and_tree('.')
3651
self._set_orphan_policy(wt, 'move')
3652
tt, orphan_tid = self._prepare_orphan(wt)
3655
warnings.append(args[0] % args[1:])
3656
self.overrideAttr(trace, 'warning', warning)
3657
remaining_conflicts = resolve_conflicts(tt)
3658
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3660
# Yeah for resolved conflicts !
3661
self.assertLength(0, remaining_conflicts)
3662
# We have a new orphan
3663
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3664
self.assertEquals('bzr-orphans',
3665
tt.final_name(tt.final_parent(orphan_tid)))
3667
def test_never_orphan(self):
3668
wt = self.make_branch_and_tree('.')
3669
self._set_orphan_policy(wt, 'conflict')
3670
tt, orphan_tid = self._prepare_orphan(wt)
3671
remaining_conflicts = resolve_conflicts(tt)
3672
self.assertLength(1, remaining_conflicts)
3673
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3674
remaining_conflicts.pop())
3676
def test_orphan_error(self):
3677
def bogus_orphan(tt, orphan_id, parent_id):
3678
raise transform.OrphaningError(tt.final_name(orphan_id),
3679
tt.final_name(parent_id))
3680
transform.orphaning_registry.register('bogus', bogus_orphan,
3681
'Raise an error when orphaning')
3682
wt = self.make_branch_and_tree('.')
3683
self._set_orphan_policy(wt, 'bogus')
3684
tt, orphan_tid = self._prepare_orphan(wt)
3685
remaining_conflicts = resolve_conflicts(tt)
3686
self.assertLength(1, remaining_conflicts)
3687
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3688
remaining_conflicts.pop())
3690
def test_unknown_orphan_policy(self):
3691
wt = self.make_branch_and_tree('.')
3692
# Set a fictional policy nobody ever implemented
3693
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3694
tt, orphan_tid = self._prepare_orphan(wt)
3697
warnings.append(args[0] % args[1:])
3698
self.overrideAttr(trace, 'warning', warning)
3699
remaining_conflicts = resolve_conflicts(tt)
3700
# We fallback to the default policy which create a conflict
3701
self.assertLength(1, remaining_conflicts)
3702
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3703
remaining_conflicts.pop())
3704
self.assertLength(1, warnings)
3705
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')