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_hardlink(self):
289
self.requireFeature(HardlinkFeature)
290
transform, root = self.get_transform()
291
transform.new_file('file1', root, 'contents')
293
target = self.make_branch_and_tree('target')
294
target_transform = TreeTransform(target)
295
trans_id = target_transform.create_path('file1', target_transform.root)
296
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
297
target_transform.apply()
298
self.assertPathExists('target/file1')
299
source_stat = os.stat(self.wt.abspath('file1'))
300
target_stat = os.stat('target/file1')
301
self.assertEqual(source_stat, target_stat)
303
def test_convenience(self):
304
transform, root = self.get_transform()
305
self.wt.lock_tree_write()
306
self.addCleanup(self.wt.unlock)
307
trans_id = transform.new_file('name', root, 'contents',
309
oz = transform.new_directory('oz', root, 'oz-id')
310
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
311
toto = transform.new_file('toto', dorothy, 'toto-contents',
314
self.assertEqual(len(transform.find_conflicts()), 0)
316
self.assertRaises(ReusingTransform, transform.find_conflicts)
317
self.assertEqual('contents', file(self.wt.abspath('name')).read())
318
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
319
self.assertIs(self.wt.is_executable('my_pretties'), True)
320
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
321
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
322
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
324
self.assertEqual('toto-contents',
325
self.wt.get_file_byname('oz/dorothy/toto').read())
326
self.assertIs(self.wt.is_executable('toto-id'), False)
328
def test_tree_reference(self):
329
transform, root = self.get_transform()
330
tree = transform._tree
331
trans_id = transform.new_directory('reference', root, 'subtree-id')
332
transform.set_tree_reference('subtree-revision', trans_id)
335
self.addCleanup(tree.unlock)
336
self.assertEqual('subtree-revision',
337
tree.inventory['subtree-id'].reference_revision)
339
def test_conflicts(self):
340
transform, root = self.get_transform()
341
trans_id = transform.new_file('name', root, 'contents',
343
self.assertEqual(len(transform.find_conflicts()), 0)
344
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
345
self.assertEqual(transform.find_conflicts(),
346
[('duplicate', trans_id, trans_id2, 'name')])
347
self.assertRaises(MalformedTransform, transform.apply)
348
transform.adjust_path('name', trans_id, trans_id2)
349
self.assertEqual(transform.find_conflicts(),
350
[('non-directory parent', trans_id)])
351
tinman_id = transform.trans_id_tree_path('tinman')
352
transform.adjust_path('name', tinman_id, trans_id2)
353
self.assertEqual(transform.find_conflicts(),
354
[('unversioned parent', tinman_id),
355
('missing parent', tinman_id)])
356
lion_id = transform.create_path('lion', root)
357
self.assertEqual(transform.find_conflicts(),
358
[('unversioned parent', tinman_id),
359
('missing parent', tinman_id)])
360
transform.adjust_path('name', lion_id, trans_id2)
361
self.assertEqual(transform.find_conflicts(),
362
[('unversioned parent', lion_id),
363
('missing parent', lion_id)])
364
transform.version_file("Courage", lion_id)
365
self.assertEqual(transform.find_conflicts(),
366
[('missing parent', lion_id),
367
('versioning no contents', lion_id)])
368
transform.adjust_path('name2', root, trans_id2)
369
self.assertEqual(transform.find_conflicts(),
370
[('versioning no contents', lion_id)])
371
transform.create_file('Contents, okay?', lion_id)
372
transform.adjust_path('name2', trans_id2, trans_id2)
373
self.assertEqual(transform.find_conflicts(),
374
[('parent loop', trans_id2),
375
('non-directory parent', trans_id2)])
376
transform.adjust_path('name2', root, trans_id2)
377
oz_id = transform.new_directory('oz', root)
378
transform.set_executability(True, oz_id)
379
self.assertEqual(transform.find_conflicts(),
380
[('unversioned executability', oz_id)])
381
transform.version_file('oz-id', oz_id)
382
self.assertEqual(transform.find_conflicts(),
383
[('non-file executability', oz_id)])
384
transform.set_executability(None, oz_id)
385
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
387
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
388
self.assertEqual('contents', file(self.wt.abspath('name')).read())
389
transform2, root = self.get_transform()
390
oz_id = transform2.trans_id_tree_file_id('oz-id')
391
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
392
result = transform2.find_conflicts()
393
fp = FinalPaths(transform2)
394
self.assert_('oz/tip' in transform2._tree_path_ids)
395
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
396
self.assertEqual(len(result), 2)
397
self.assertEqual((result[0][0], result[0][1]),
398
('duplicate', newtip))
399
self.assertEqual((result[1][0], result[1][2]),
400
('duplicate id', newtip))
401
transform2.finalize()
402
transform3 = TreeTransform(self.wt)
403
self.addCleanup(transform3.finalize)
404
oz_id = transform3.trans_id_tree_file_id('oz-id')
405
transform3.delete_contents(oz_id)
406
self.assertEqual(transform3.find_conflicts(),
407
[('missing parent', oz_id)])
408
root_id = transform3.root
409
tip_id = transform3.trans_id_tree_file_id('tip-id')
410
transform3.adjust_path('tip', root_id, tip_id)
413
def test_conflict_on_case_insensitive(self):
414
tree = self.make_branch_and_tree('tree')
415
# Don't try this at home, kids!
416
# Force the tree to report that it is case sensitive, for conflict
418
tree.case_sensitive = True
419
transform = TreeTransform(tree)
420
self.addCleanup(transform.finalize)
421
transform.new_file('file', transform.root, 'content')
422
transform.new_file('FiLe', transform.root, 'content')
423
result = transform.find_conflicts()
424
self.assertEqual([], result)
426
# Force the tree to report that it is case insensitive, for conflict
428
tree.case_sensitive = False
429
transform = TreeTransform(tree)
430
self.addCleanup(transform.finalize)
431
transform.new_file('file', transform.root, 'content')
432
transform.new_file('FiLe', transform.root, 'content')
433
result = transform.find_conflicts()
434
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
436
def test_conflict_on_case_insensitive_existing(self):
437
tree = self.make_branch_and_tree('tree')
438
self.build_tree(['tree/FiLe'])
439
# Don't try this at home, kids!
440
# Force the tree to report that it is case sensitive, for conflict
442
tree.case_sensitive = True
443
transform = TreeTransform(tree)
444
self.addCleanup(transform.finalize)
445
transform.new_file('file', transform.root, 'content')
446
result = transform.find_conflicts()
447
self.assertEqual([], result)
449
# Force the tree to report that it is case insensitive, for conflict
451
tree.case_sensitive = False
452
transform = TreeTransform(tree)
453
self.addCleanup(transform.finalize)
454
transform.new_file('file', transform.root, 'content')
455
result = transform.find_conflicts()
456
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
458
def test_resolve_case_insensitive_conflict(self):
459
tree = self.make_branch_and_tree('tree')
460
# Don't try this at home, kids!
461
# Force the tree to report that it is case insensitive, for conflict
463
tree.case_sensitive = False
464
transform = TreeTransform(tree)
465
self.addCleanup(transform.finalize)
466
transform.new_file('file', transform.root, 'content')
467
transform.new_file('FiLe', transform.root, 'content')
468
resolve_conflicts(transform)
470
self.assertPathExists('tree/file')
471
self.assertPathExists('tree/FiLe.moved')
473
def test_resolve_checkout_case_conflict(self):
474
tree = self.make_branch_and_tree('tree')
475
# Don't try this at home, kids!
476
# Force the tree to report that it is case insensitive, for conflict
478
tree.case_sensitive = False
479
transform = TreeTransform(tree)
480
self.addCleanup(transform.finalize)
481
transform.new_file('file', transform.root, 'content')
482
transform.new_file('FiLe', transform.root, 'content')
483
resolve_conflicts(transform,
484
pass_func=lambda t, c: resolve_checkout(t, c, []))
486
self.assertPathExists('tree/file')
487
self.assertPathExists('tree/FiLe.moved')
489
def test_apply_case_conflict(self):
490
"""Ensure that a transform with case conflicts can always be applied"""
491
tree = self.make_branch_and_tree('tree')
492
transform = TreeTransform(tree)
493
self.addCleanup(transform.finalize)
494
transform.new_file('file', transform.root, 'content')
495
transform.new_file('FiLe', transform.root, 'content')
496
dir = transform.new_directory('dir', transform.root)
497
transform.new_file('dirfile', dir, 'content')
498
transform.new_file('dirFiLe', dir, 'content')
499
resolve_conflicts(transform)
501
self.assertPathExists('tree/file')
502
if not os.path.exists('tree/FiLe.moved'):
503
self.assertPathExists('tree/FiLe')
504
self.assertPathExists('tree/dir/dirfile')
505
if not os.path.exists('tree/dir/dirFiLe.moved'):
506
self.assertPathExists('tree/dir/dirFiLe')
508
def test_case_insensitive_limbo(self):
509
tree = self.make_branch_and_tree('tree')
510
# Don't try this at home, kids!
511
# Force the tree to report that it is case insensitive
512
tree.case_sensitive = False
513
transform = TreeTransform(tree)
514
self.addCleanup(transform.finalize)
515
dir = transform.new_directory('dir', transform.root)
516
first = transform.new_file('file', dir, 'content')
517
second = transform.new_file('FiLe', dir, 'content')
518
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
519
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
521
def test_adjust_path_updates_child_limbo_names(self):
522
tree = self.make_branch_and_tree('tree')
523
transform = TreeTransform(tree)
524
self.addCleanup(transform.finalize)
525
foo_id = transform.new_directory('foo', transform.root)
526
bar_id = transform.new_directory('bar', foo_id)
527
baz_id = transform.new_directory('baz', bar_id)
528
qux_id = transform.new_directory('qux', baz_id)
529
transform.adjust_path('quxx', foo_id, bar_id)
530
self.assertStartsWith(transform._limbo_name(qux_id),
531
transform._limbo_name(bar_id))
533
def test_add_del(self):
534
start, root = self.get_transform()
535
start.new_directory('a', root, 'a')
537
transform, root = self.get_transform()
538
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
539
transform.new_directory('a', root, 'a')
542
def test_unversioning(self):
543
create_tree, root = self.get_transform()
544
parent_id = create_tree.new_directory('parent', root, 'parent-id')
545
create_tree.new_file('child', parent_id, 'child', 'child-id')
547
unversion = TreeTransform(self.wt)
548
self.addCleanup(unversion.finalize)
549
parent = unversion.trans_id_tree_path('parent')
550
unversion.unversion_file(parent)
551
self.assertEqual(unversion.find_conflicts(),
552
[('unversioned parent', parent_id)])
553
file_id = unversion.trans_id_tree_file_id('child-id')
554
unversion.unversion_file(file_id)
557
def test_name_invariants(self):
558
create_tree, root = self.get_transform()
560
root = create_tree.root
561
create_tree.new_file('name1', root, 'hello1', 'name1')
562
create_tree.new_file('name2', root, 'hello2', 'name2')
563
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
564
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
565
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
566
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
569
mangle_tree,root = self.get_transform()
570
root = mangle_tree.root
572
name1 = mangle_tree.trans_id_tree_file_id('name1')
573
name2 = mangle_tree.trans_id_tree_file_id('name2')
574
mangle_tree.adjust_path('name2', root, name1)
575
mangle_tree.adjust_path('name1', root, name2)
577
#tests for deleting parent directories
578
ddir = mangle_tree.trans_id_tree_file_id('ddir')
579
mangle_tree.delete_contents(ddir)
580
dfile = mangle_tree.trans_id_tree_file_id('dfile')
581
mangle_tree.delete_versioned(dfile)
582
mangle_tree.unversion_file(dfile)
583
mfile = mangle_tree.trans_id_tree_file_id('mfile')
584
mangle_tree.adjust_path('mfile', root, mfile)
586
#tests for adding parent directories
587
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
588
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
589
mangle_tree.adjust_path('mfile2', newdir, mfile2)
590
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
591
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
592
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
593
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
595
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
596
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
597
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
598
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
599
self.assertEqual(file(mfile2_path).read(), 'later2')
600
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
601
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
602
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
603
self.assertEqual(file(newfile_path).read(), 'hello3')
604
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
605
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
606
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
608
def test_both_rename(self):
609
create_tree,root = self.get_transform()
610
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
611
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
613
mangle_tree,root = self.get_transform()
614
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
615
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
616
mangle_tree.adjust_path('test', root, selftest)
617
mangle_tree.adjust_path('test_too_much', root, selftest)
618
mangle_tree.set_executability(True, blackbox)
621
def test_both_rename2(self):
622
create_tree,root = self.get_transform()
623
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
624
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
625
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
626
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
629
mangle_tree,root = self.get_transform()
630
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
631
tests = mangle_tree.trans_id_tree_file_id('tests-id')
632
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
633
mangle_tree.adjust_path('selftest', bzrlib, tests)
634
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
635
mangle_tree.set_executability(True, test_too_much)
638
def test_both_rename3(self):
639
create_tree,root = self.get_transform()
640
tests = create_tree.new_directory('tests', root, 'tests-id')
641
create_tree.new_file('test_too_much.py', tests, 'hello1',
644
mangle_tree,root = self.get_transform()
645
tests = mangle_tree.trans_id_tree_file_id('tests-id')
646
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
647
mangle_tree.adjust_path('selftest', root, tests)
648
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
649
mangle_tree.set_executability(True, test_too_much)
652
def test_move_dangling_ie(self):
653
create_tree, root = self.get_transform()
655
root = create_tree.root
656
create_tree.new_file('name1', root, 'hello1', 'name1')
658
delete_contents, root = self.get_transform()
659
file = delete_contents.trans_id_tree_file_id('name1')
660
delete_contents.delete_contents(file)
661
delete_contents.apply()
662
move_id, root = self.get_transform()
663
name1 = move_id.trans_id_tree_file_id('name1')
664
newdir = move_id.new_directory('dir', root, 'newdir')
665
move_id.adjust_path('name2', newdir, name1)
668
def test_replace_dangling_ie(self):
669
create_tree, root = self.get_transform()
671
root = create_tree.root
672
create_tree.new_file('name1', root, 'hello1', 'name1')
674
delete_contents = TreeTransform(self.wt)
675
self.addCleanup(delete_contents.finalize)
676
file = delete_contents.trans_id_tree_file_id('name1')
677
delete_contents.delete_contents(file)
678
delete_contents.apply()
679
delete_contents.finalize()
680
replace = TreeTransform(self.wt)
681
self.addCleanup(replace.finalize)
682
name2 = replace.new_file('name2', root, 'hello2', 'name1')
683
conflicts = replace.find_conflicts()
684
name1 = replace.trans_id_tree_file_id('name1')
685
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
686
resolve_conflicts(replace)
689
def _test_symlinks(self, link_name1,link_target1,
690
link_name2, link_target2):
692
def ozpath(p): return 'oz/' + p
694
self.requireFeature(SymlinkFeature)
695
transform, root = self.get_transform()
696
oz_id = transform.new_directory('oz', root, 'oz-id')
697
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
699
wiz_id = transform.create_path(link_name2, oz_id)
700
transform.create_symlink(link_target2, wiz_id)
701
transform.version_file('wiz-id2', wiz_id)
702
transform.set_executability(True, wiz_id)
703
self.assertEqual(transform.find_conflicts(),
704
[('non-file executability', wiz_id)])
705
transform.set_executability(None, wiz_id)
707
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
708
self.assertEqual('symlink',
709
file_kind(self.wt.abspath(ozpath(link_name1))))
710
self.assertEqual(link_target2,
711
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
712
self.assertEqual(link_target1,
713
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
715
def test_symlinks(self):
716
self._test_symlinks('wizard', 'wizard-target',
717
'wizard2', 'behind_curtain')
719
def test_symlinks_unicode(self):
720
self.requireFeature(tests.UnicodeFilenameFeature)
721
self._test_symlinks(u'\N{Euro Sign}wizard',
722
u'wizard-targ\N{Euro Sign}t',
723
u'\N{Euro Sign}wizard2',
724
u'b\N{Euro Sign}hind_curtain')
726
def test_unable_create_symlink(self):
728
wt = self.make_branch_and_tree('.')
729
tt = TreeTransform(wt) # TreeTransform obtains write lock
731
tt.new_symlink('foo', tt.root, 'bar')
735
os_symlink = getattr(os, 'symlink', None)
738
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
740
"Unable to create symlink 'foo' on this platform",
744
os.symlink = os_symlink
746
def get_conflicted(self):
747
create,root = self.get_transform()
748
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
749
oz = create.new_directory('oz', root, 'oz-id')
750
create.new_directory('emeraldcity', oz, 'emerald-id')
752
conflicts,root = self.get_transform()
753
# set up duplicate entry, duplicate id
754
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
756
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
757
oz = conflicts.trans_id_tree_file_id('oz-id')
758
# set up DeletedParent parent conflict
759
conflicts.delete_versioned(oz)
760
emerald = conflicts.trans_id_tree_file_id('emerald-id')
761
# set up MissingParent conflict
762
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
763
conflicts.adjust_path('munchkincity', root, munchkincity)
764
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
766
conflicts.adjust_path('emeraldcity', emerald, emerald)
767
return conflicts, emerald, oz, old_dorothy, new_dorothy
769
def test_conflict_resolution(self):
770
conflicts, emerald, oz, old_dorothy, new_dorothy =\
771
self.get_conflicted()
772
resolve_conflicts(conflicts)
773
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
774
self.assertIs(conflicts.final_file_id(old_dorothy), None)
775
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
776
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
777
self.assertEqual(conflicts.final_parent(emerald), oz)
780
def test_cook_conflicts(self):
781
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
782
raw_conflicts = resolve_conflicts(tt)
783
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
784
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
785
'dorothy', None, 'dorothy-id')
786
self.assertEqual(cooked_conflicts[0], duplicate)
787
duplicate_id = DuplicateID('Unversioned existing file',
788
'dorothy.moved', 'dorothy', None,
790
self.assertEqual(cooked_conflicts[1], duplicate_id)
791
missing_parent = MissingParent('Created directory', 'munchkincity',
793
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
794
self.assertEqual(cooked_conflicts[2], missing_parent)
795
unversioned_parent = UnversionedParent('Versioned directory',
798
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
800
self.assertEqual(cooked_conflicts[3], unversioned_parent)
801
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
802
'oz/emeraldcity', 'emerald-id', 'emerald-id')
803
self.assertEqual(cooked_conflicts[4], deleted_parent)
804
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
805
self.assertEqual(cooked_conflicts[6], parent_loop)
806
self.assertEqual(len(cooked_conflicts), 7)
809
def test_string_conflicts(self):
810
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
811
raw_conflicts = resolve_conflicts(tt)
812
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
814
conflicts_s = [unicode(c) for c in cooked_conflicts]
815
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
816
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
817
'Moved existing file to '
819
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
820
'Unversioned existing file '
822
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
823
' munchkincity. Created directory.')
824
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
825
' versioned, but has versioned'
826
' children. Versioned directory.')
827
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
828
" is not empty. Not deleting.")
829
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
830
' versioned, but has versioned'
831
' children. Versioned directory.')
832
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
833
' oz/emeraldcity. Cancelled move.')
835
def prepare_wrong_parent_kind(self):
836
tt, root = self.get_transform()
837
tt.new_file('parent', root, 'contents', 'parent-id')
839
tt, root = self.get_transform()
840
parent_id = tt.trans_id_file_id('parent-id')
841
tt.new_file('child,', parent_id, 'contents2', 'file-id')
844
def test_find_conflicts_wrong_parent_kind(self):
845
tt = self.prepare_wrong_parent_kind()
848
def test_resolve_conflicts_wrong_existing_parent_kind(self):
849
tt = self.prepare_wrong_parent_kind()
850
raw_conflicts = resolve_conflicts(tt)
851
self.assertEqual(set([('non-directory parent', 'Created directory',
852
'new-3')]), raw_conflicts)
853
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
854
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
855
'parent-id')], cooked_conflicts)
857
self.assertEqual(None, self.wt.path2id('parent'))
858
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
860
def test_resolve_conflicts_wrong_new_parent_kind(self):
861
tt, root = self.get_transform()
862
parent_id = tt.new_directory('parent', root, 'parent-id')
863
tt.new_file('child,', parent_id, 'contents2', 'file-id')
865
tt, root = self.get_transform()
866
parent_id = tt.trans_id_file_id('parent-id')
867
tt.delete_contents(parent_id)
868
tt.create_file('contents', parent_id)
869
raw_conflicts = resolve_conflicts(tt)
870
self.assertEqual(set([('non-directory parent', 'Created directory',
871
'new-3')]), raw_conflicts)
873
self.assertEqual(None, self.wt.path2id('parent'))
874
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
876
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
877
tt, root = self.get_transform()
878
parent_id = tt.new_directory('parent', root)
879
tt.new_file('child,', parent_id, 'contents2')
881
tt, root = self.get_transform()
882
parent_id = tt.trans_id_tree_path('parent')
883
tt.delete_contents(parent_id)
884
tt.create_file('contents', parent_id)
885
resolve_conflicts(tt)
887
self.assertIs(None, self.wt.path2id('parent'))
888
self.assertIs(None, self.wt.path2id('parent.new'))
890
def test_resolve_conflicts_missing_parent(self):
891
wt = self.make_branch_and_tree('.')
892
tt = TreeTransform(wt)
893
self.addCleanup(tt.finalize)
894
parent = tt.trans_id_file_id('parent-id')
895
tt.new_file('file', parent, 'Contents')
896
raw_conflicts = resolve_conflicts(tt)
897
# Since the directory doesn't exist it's seen as 'missing'. So
898
# 'resolve_conflicts' create a conflict asking for it to be created.
899
self.assertLength(1, raw_conflicts)
900
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
902
# apply fail since the missing directory doesn't exist
903
self.assertRaises(errors.NoFinalPath, tt.apply)
905
def test_moving_versioned_directories(self):
906
create, root = self.get_transform()
907
kansas = create.new_directory('kansas', root, 'kansas-id')
908
create.new_directory('house', kansas, 'house-id')
909
create.new_directory('oz', root, 'oz-id')
911
cyclone, root = self.get_transform()
912
oz = cyclone.trans_id_tree_file_id('oz-id')
913
house = cyclone.trans_id_tree_file_id('house-id')
914
cyclone.adjust_path('house', oz, house)
917
def test_moving_root(self):
918
create, root = self.get_transform()
919
fun = create.new_directory('fun', root, 'fun-id')
920
create.new_directory('sun', root, 'sun-id')
921
create.new_directory('moon', root, 'moon')
923
transform, root = self.get_transform()
924
transform.adjust_root_path('oldroot', fun)
925
new_root = transform.trans_id_tree_path('')
926
transform.version_file('new-root', new_root)
929
def test_renames(self):
930
create, root = self.get_transform()
931
old = create.new_directory('old-parent', root, 'old-id')
932
intermediate = create.new_directory('intermediate', old, 'im-id')
933
myfile = create.new_file('myfile', intermediate, 'myfile-text',
936
rename, root = self.get_transform()
937
old = rename.trans_id_file_id('old-id')
938
rename.adjust_path('new', root, old)
939
myfile = rename.trans_id_file_id('myfile-id')
940
rename.set_executability(True, myfile)
943
def test_rename_fails(self):
944
self.requireFeature(features.not_running_as_root)
945
# see https://bugs.launchpad.net/bzr/+bug/491763
946
create, root_id = self.get_transform()
947
first_dir = create.new_directory('first-dir', root_id, 'first-id')
948
myfile = create.new_file('myfile', root_id, 'myfile-text',
951
if os.name == "posix" and sys.platform != "cygwin":
952
# posix filesystems fail on renaming if the readonly bit is set
953
osutils.make_readonly(self.wt.abspath('first-dir'))
954
elif os.name == "nt":
955
# windows filesystems fail on renaming open files
956
self.addCleanup(file(self.wt.abspath('myfile')).close)
958
self.skip("Don't know how to force a permissions error on rename")
959
# now transform to rename
960
rename_transform, root_id = self.get_transform()
961
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
962
dir_id = rename_transform.trans_id_file_id('first-id')
963
rename_transform.adjust_path('newname', dir_id, file_trans_id)
964
e = self.assertRaises(errors.TransformRenameFailed,
965
rename_transform.apply)
967
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
968
# to .../first-dir/newname: [Errno 13] Permission denied"
969
# On windows looks like:
970
# "Failed to rename .../work/myfile to
971
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
972
# This test isn't concerned with exactly what the error looks like,
973
# and the strerror will vary across OS and locales, but the assert
974
# that the exeception attributes are what we expect
975
self.assertEqual(e.errno, errno.EACCES)
976
if os.name == "posix":
977
self.assertEndsWith(e.to_path, "/first-dir/newname")
979
self.assertEqual(os.path.basename(e.from_path), "myfile")
981
def test_set_executability_order(self):
982
"""Ensure that executability behaves the same, no matter what order.
984
- create file and set executability simultaneously
985
- create file and set executability afterward
986
- unsetting the executability of a file whose executability has not been
987
declared should throw an exception (this may happen when a
988
merge attempts to create a file with a duplicate ID)
990
transform, root = self.get_transform()
993
self.addCleanup(wt.unlock)
994
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
996
sac = transform.new_file('set_after_creation', root,
997
'Set after creation', 'sac')
998
transform.set_executability(True, sac)
999
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1001
self.assertRaises(KeyError, transform.set_executability, None, uws)
1003
self.assertTrue(wt.is_executable('soc'))
1004
self.assertTrue(wt.is_executable('sac'))
1006
def test_preserve_mode(self):
1007
"""File mode is preserved when replacing content"""
1008
if sys.platform == 'win32':
1009
raise TestSkipped('chmod has no effect on win32')
1010
transform, root = self.get_transform()
1011
transform.new_file('file1', root, 'contents', 'file1-id', True)
1013
self.wt.lock_write()
1014
self.addCleanup(self.wt.unlock)
1015
self.assertTrue(self.wt.is_executable('file1-id'))
1016
transform, root = self.get_transform()
1017
file1_id = transform.trans_id_tree_file_id('file1-id')
1018
transform.delete_contents(file1_id)
1019
transform.create_file('contents2', file1_id)
1021
self.assertTrue(self.wt.is_executable('file1-id'))
1023
def test__set_mode_stats_correctly(self):
1024
"""_set_mode stats to determine file mode."""
1025
if sys.platform == 'win32':
1026
raise TestSkipped('chmod has no effect on win32')
1030
def instrumented_stat(path):
1031
stat_paths.append(path)
1032
return real_stat(path)
1034
transform, root = self.get_transform()
1036
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1037
file_id='bar-id-1', executable=False)
1040
transform, root = self.get_transform()
1041
bar1_id = transform.trans_id_tree_path('bar')
1042
bar2_id = transform.trans_id_tree_path('bar2')
1044
os.stat = instrumented_stat
1045
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1048
transform.finalize()
1050
bar1_abspath = self.wt.abspath('bar')
1051
self.assertEqual([bar1_abspath], stat_paths)
1053
def test_iter_changes(self):
1054
self.wt.set_root_id('eert_toor')
1055
transform, root = self.get_transform()
1056
transform.new_file('old', root, 'blah', 'id-1', True)
1058
transform, root = self.get_transform()
1060
self.assertEqual([], list(transform.iter_changes()))
1061
old = transform.trans_id_tree_file_id('id-1')
1062
transform.unversion_file(old)
1063
self.assertEqual([('id-1', ('old', None), False, (True, False),
1064
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1065
(True, True))], list(transform.iter_changes()))
1066
transform.new_directory('new', root, 'id-1')
1067
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1068
('eert_toor', 'eert_toor'), ('old', 'new'),
1069
('file', 'directory'),
1070
(True, False))], list(transform.iter_changes()))
1072
transform.finalize()
1074
def test_iter_changes_new(self):
1075
self.wt.set_root_id('eert_toor')
1076
transform, root = self.get_transform()
1077
transform.new_file('old', root, 'blah')
1079
transform, root = self.get_transform()
1081
old = transform.trans_id_tree_path('old')
1082
transform.version_file('id-1', old)
1083
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1084
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1085
(False, False))], list(transform.iter_changes()))
1087
transform.finalize()
1089
def test_iter_changes_modifications(self):
1090
self.wt.set_root_id('eert_toor')
1091
transform, root = self.get_transform()
1092
transform.new_file('old', root, 'blah', 'id-1')
1093
transform.new_file('new', root, 'blah')
1094
transform.new_directory('subdir', root, 'subdir-id')
1096
transform, root = self.get_transform()
1098
old = transform.trans_id_tree_path('old')
1099
subdir = transform.trans_id_tree_file_id('subdir-id')
1100
new = transform.trans_id_tree_path('new')
1101
self.assertEqual([], list(transform.iter_changes()))
1104
transform.delete_contents(old)
1105
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1106
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1107
(False, False))], list(transform.iter_changes()))
1110
transform.create_file('blah', old)
1111
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1112
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1113
(False, False))], list(transform.iter_changes()))
1114
transform.cancel_deletion(old)
1115
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1116
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1117
(False, False))], list(transform.iter_changes()))
1118
transform.cancel_creation(old)
1120
# move file_id to a different file
1121
self.assertEqual([], list(transform.iter_changes()))
1122
transform.unversion_file(old)
1123
transform.version_file('id-1', new)
1124
transform.adjust_path('old', root, new)
1125
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1126
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1127
(False, False))], list(transform.iter_changes()))
1128
transform.cancel_versioning(new)
1129
transform._removed_id = set()
1132
self.assertEqual([], list(transform.iter_changes()))
1133
transform.set_executability(True, old)
1134
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1135
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1136
(False, True))], list(transform.iter_changes()))
1137
transform.set_executability(None, old)
1140
self.assertEqual([], list(transform.iter_changes()))
1141
transform.adjust_path('new', root, old)
1142
transform._new_parent = {}
1143
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1144
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1145
(False, False))], list(transform.iter_changes()))
1146
transform._new_name = {}
1149
self.assertEqual([], list(transform.iter_changes()))
1150
transform.adjust_path('new', subdir, old)
1151
transform._new_name = {}
1152
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1153
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1154
('file', 'file'), (False, False))],
1155
list(transform.iter_changes()))
1156
transform._new_path = {}
1159
transform.finalize()
1161
def test_iter_changes_modified_bleed(self):
1162
self.wt.set_root_id('eert_toor')
1163
"""Modified flag should not bleed from one change to another"""
1164
# unfortunately, we have no guarantee that file1 (which is modified)
1165
# will be applied before file2. And if it's applied after file2, it
1166
# obviously can't bleed into file2's change output. But for now, it
1168
transform, root = self.get_transform()
1169
transform.new_file('file1', root, 'blah', 'id-1')
1170
transform.new_file('file2', root, 'blah', 'id-2')
1172
transform, root = self.get_transform()
1174
transform.delete_contents(transform.trans_id_file_id('id-1'))
1175
transform.set_executability(True,
1176
transform.trans_id_file_id('id-2'))
1177
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1178
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1179
('file', None), (False, False)),
1180
('id-2', (u'file2', u'file2'), False, (True, True),
1181
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1182
('file', 'file'), (False, True))],
1183
list(transform.iter_changes()))
1185
transform.finalize()
1187
def test_iter_changes_move_missing(self):
1188
"""Test moving ids with no files around"""
1189
self.wt.set_root_id('toor_eert')
1190
# Need two steps because versioning a non-existant file is a conflict.
1191
transform, root = self.get_transform()
1192
transform.new_directory('floater', root, 'floater-id')
1194
transform, root = self.get_transform()
1195
transform.delete_contents(transform.trans_id_tree_path('floater'))
1197
transform, root = self.get_transform()
1198
floater = transform.trans_id_tree_path('floater')
1200
transform.adjust_path('flitter', root, floater)
1201
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1202
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1203
(None, None), (False, False))], list(transform.iter_changes()))
1205
transform.finalize()
1207
def test_iter_changes_pointless(self):
1208
"""Ensure that no-ops are not treated as modifications"""
1209
self.wt.set_root_id('eert_toor')
1210
transform, root = self.get_transform()
1211
transform.new_file('old', root, 'blah', 'id-1')
1212
transform.new_directory('subdir', root, 'subdir-id')
1214
transform, root = self.get_transform()
1216
old = transform.trans_id_tree_path('old')
1217
subdir = transform.trans_id_tree_file_id('subdir-id')
1218
self.assertEqual([], list(transform.iter_changes()))
1219
transform.delete_contents(subdir)
1220
transform.create_directory(subdir)
1221
transform.set_executability(False, old)
1222
transform.unversion_file(old)
1223
transform.version_file('id-1', old)
1224
transform.adjust_path('old', root, old)
1225
self.assertEqual([], list(transform.iter_changes()))
1227
transform.finalize()
1229
def test_rename_count(self):
1230
transform, root = self.get_transform()
1231
transform.new_file('name1', root, 'contents')
1232
self.assertEqual(transform.rename_count, 0)
1234
self.assertEqual(transform.rename_count, 1)
1235
transform2, root = self.get_transform()
1236
transform2.adjust_path('name2', root,
1237
transform2.trans_id_tree_path('name1'))
1238
self.assertEqual(transform2.rename_count, 0)
1240
self.assertEqual(transform2.rename_count, 2)
1242
def test_change_parent(self):
1243
"""Ensure that after we change a parent, the results are still right.
1245
Renames and parent changes on pending transforms can happen as part
1246
of conflict resolution, and are explicitly permitted by the
1249
This test ensures they work correctly with the rename-avoidance
1252
transform, root = self.get_transform()
1253
parent1 = transform.new_directory('parent1', root)
1254
child1 = transform.new_file('child1', parent1, 'contents')
1255
parent2 = transform.new_directory('parent2', root)
1256
transform.adjust_path('child1', parent2, child1)
1258
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1259
self.assertPathExists(self.wt.abspath('parent2/child1'))
1260
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1261
# no rename for child1 (counting only renames during apply)
1262
self.assertEqual(2, transform.rename_count)
1264
def test_cancel_parent(self):
1265
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1267
This is like the test_change_parent, except that we cancel the parent
1268
before adjusting the path. The transform must detect that the
1269
directory is non-empty, and move children to safe locations.
1271
transform, root = self.get_transform()
1272
parent1 = transform.new_directory('parent1', root)
1273
child1 = transform.new_file('child1', parent1, 'contents')
1274
child2 = transform.new_file('child2', parent1, 'contents')
1276
transform.cancel_creation(parent1)
1278
self.fail('Failed to move child1 before deleting parent1')
1279
transform.cancel_creation(child2)
1280
transform.create_directory(parent1)
1282
transform.cancel_creation(parent1)
1283
# If the transform incorrectly believes that child2 is still in
1284
# parent1's limbo directory, it will try to rename it and fail
1285
# because was already moved by the first cancel_creation.
1287
self.fail('Transform still thinks child2 is a child of parent1')
1288
parent2 = transform.new_directory('parent2', root)
1289
transform.adjust_path('child1', parent2, child1)
1291
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1292
self.assertPathExists(self.wt.abspath('parent2/child1'))
1293
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1294
self.assertEqual(2, transform.rename_count)
1296
def test_adjust_and_cancel(self):
1297
"""Make sure adjust_path keeps track of limbo children properly"""
1298
transform, root = self.get_transform()
1299
parent1 = transform.new_directory('parent1', root)
1300
child1 = transform.new_file('child1', parent1, 'contents')
1301
parent2 = transform.new_directory('parent2', root)
1302
transform.adjust_path('child1', parent2, child1)
1303
transform.cancel_creation(child1)
1305
transform.cancel_creation(parent1)
1306
# if the transform thinks child1 is still in parent1's limbo
1307
# directory, it will attempt to move it and fail.
1309
self.fail('Transform still thinks child1 is a child of parent1')
1310
transform.finalize()
1312
def test_noname_contents(self):
1313
"""TreeTransform should permit deferring naming files."""
1314
transform, root = self.get_transform()
1315
parent = transform.trans_id_file_id('parent-id')
1317
transform.create_directory(parent)
1319
self.fail("Can't handle contents with no name")
1320
transform.finalize()
1322
def test_noname_contents_nested(self):
1323
"""TreeTransform should permit deferring naming files."""
1324
transform, root = self.get_transform()
1325
parent = transform.trans_id_file_id('parent-id')
1327
transform.create_directory(parent)
1329
self.fail("Can't handle contents with no name")
1330
child = transform.new_directory('child', parent)
1331
transform.adjust_path('parent', root, parent)
1333
self.assertPathExists(self.wt.abspath('parent/child'))
1334
self.assertEqual(1, transform.rename_count)
1336
def test_reuse_name(self):
1337
"""Avoid reusing the same limbo name for different files"""
1338
transform, root = self.get_transform()
1339
parent = transform.new_directory('parent', root)
1340
child1 = transform.new_directory('child', parent)
1342
child2 = transform.new_directory('child', parent)
1344
self.fail('Tranform tried to use the same limbo name twice')
1345
transform.adjust_path('child2', parent, child2)
1347
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1348
# child2 is put into top-level limbo because child1 has already
1349
# claimed the direct limbo path when child2 is created. There is no
1350
# advantage in renaming files once they're in top-level limbo, except
1352
self.assertEqual(2, transform.rename_count)
1354
def test_reuse_when_first_moved(self):
1355
"""Don't avoid direct paths when it is safe to use them"""
1356
transform, root = self.get_transform()
1357
parent = transform.new_directory('parent', root)
1358
child1 = transform.new_directory('child', parent)
1359
transform.adjust_path('child1', parent, child1)
1360
child2 = transform.new_directory('child', parent)
1362
# limbo/new-1 => parent
1363
self.assertEqual(1, transform.rename_count)
1365
def test_reuse_after_cancel(self):
1366
"""Don't avoid direct paths when it is safe to use them"""
1367
transform, root = self.get_transform()
1368
parent2 = transform.new_directory('parent2', root)
1369
child1 = transform.new_directory('child1', parent2)
1370
transform.cancel_creation(parent2)
1371
transform.create_directory(parent2)
1372
child2 = transform.new_directory('child1', parent2)
1373
transform.adjust_path('child2', parent2, child1)
1375
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1376
self.assertEqual(2, transform.rename_count)
1378
def test_finalize_order(self):
1379
"""Finalize must be done in child-to-parent order"""
1380
transform, root = self.get_transform()
1381
parent = transform.new_directory('parent', root)
1382
child = transform.new_directory('child', parent)
1384
transform.finalize()
1386
self.fail('Tried to remove parent before child1')
1388
def test_cancel_with_cancelled_child_should_succeed(self):
1389
transform, root = self.get_transform()
1390
parent = transform.new_directory('parent', root)
1391
child = transform.new_directory('child', parent)
1392
transform.cancel_creation(child)
1393
transform.cancel_creation(parent)
1394
transform.finalize()
1396
def test_rollback_on_directory_clash(self):
1398
wt = self.make_branch_and_tree('.')
1399
tt = TreeTransform(wt) # TreeTransform obtains write lock
1401
foo = tt.new_directory('foo', tt.root)
1402
tt.new_file('bar', foo, 'foobar')
1403
baz = tt.new_directory('baz', tt.root)
1404
tt.new_file('qux', baz, 'quux')
1405
# Ask for a rename 'foo' -> 'baz'
1406
tt.adjust_path('baz', tt.root, foo)
1407
# Lie to tt that we've already resolved all conflicts.
1408
tt.apply(no_conflicts=True)
1412
# The rename will fail because the target directory is not empty (but
1413
# raises FileExists anyway).
1414
err = self.assertRaises(errors.FileExists, tt_helper)
1415
self.assertContainsRe(str(err),
1416
"^File exists: .+/baz")
1418
def test_two_directories_clash(self):
1420
wt = self.make_branch_and_tree('.')
1421
tt = TreeTransform(wt) # TreeTransform obtains write lock
1423
foo_1 = tt.new_directory('foo', tt.root)
1424
tt.new_directory('bar', foo_1)
1425
# Adding the same directory with a different content
1426
foo_2 = tt.new_directory('foo', tt.root)
1427
tt.new_directory('baz', foo_2)
1428
# Lie to tt that we've already resolved all conflicts.
1429
tt.apply(no_conflicts=True)
1433
err = self.assertRaises(errors.FileExists, tt_helper)
1434
self.assertContainsRe(str(err),
1435
"^File exists: .+/foo")
1437
def test_two_directories_clash_finalize(self):
1439
wt = self.make_branch_and_tree('.')
1440
tt = TreeTransform(wt) # TreeTransform obtains write lock
1442
foo_1 = tt.new_directory('foo', tt.root)
1443
tt.new_directory('bar', foo_1)
1444
# Adding the same directory with a different content
1445
foo_2 = tt.new_directory('foo', tt.root)
1446
tt.new_directory('baz', foo_2)
1447
# Lie to tt that we've already resolved all conflicts.
1448
tt.apply(no_conflicts=True)
1452
err = self.assertRaises(errors.FileExists, tt_helper)
1453
self.assertContainsRe(str(err),
1454
"^File exists: .+/foo")
1456
def test_file_to_directory(self):
1457
wt = self.make_branch_and_tree('.')
1458
self.build_tree(['foo'])
1461
tt = TreeTransform(wt)
1462
self.addCleanup(tt.finalize)
1463
foo_trans_id = tt.trans_id_tree_path("foo")
1464
tt.delete_contents(foo_trans_id)
1465
tt.create_directory(foo_trans_id)
1466
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1467
tt.create_file(["aa\n"], bar_trans_id)
1468
tt.version_file("bar-1", bar_trans_id)
1470
self.assertPathExists("foo/bar")
1473
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1478
changes = wt.changes_from(wt.basis_tree())
1479
self.assertFalse(changes.has_changed(), changes)
1481
def test_file_to_symlink(self):
1482
self.requireFeature(SymlinkFeature)
1483
wt = self.make_branch_and_tree('.')
1484
self.build_tree(['foo'])
1487
tt = TreeTransform(wt)
1488
self.addCleanup(tt.finalize)
1489
foo_trans_id = tt.trans_id_tree_path("foo")
1490
tt.delete_contents(foo_trans_id)
1491
tt.create_symlink("bar", foo_trans_id)
1493
self.assertPathExists("foo")
1495
self.addCleanup(wt.unlock)
1496
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1499
def test_dir_to_file(self):
1500
wt = self.make_branch_and_tree('.')
1501
self.build_tree(['foo/', 'foo/bar'])
1502
wt.add(['foo', 'foo/bar'])
1504
tt = TreeTransform(wt)
1505
self.addCleanup(tt.finalize)
1506
foo_trans_id = tt.trans_id_tree_path("foo")
1507
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1508
tt.delete_contents(foo_trans_id)
1509
tt.delete_versioned(bar_trans_id)
1510
tt.create_file(["aa\n"], foo_trans_id)
1512
self.assertPathExists("foo")
1514
self.addCleanup(wt.unlock)
1515
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1518
def test_dir_to_hardlink(self):
1519
self.requireFeature(HardlinkFeature)
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo/', 'foo/bar'])
1522
wt.add(['foo', 'foo/bar'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1528
tt.delete_contents(foo_trans_id)
1529
tt.delete_versioned(bar_trans_id)
1530
self.build_tree(['baz'])
1531
tt.create_hardlink("baz", foo_trans_id)
1533
self.assertPathExists("foo")
1534
self.assertPathExists("baz")
1536
self.addCleanup(wt.unlock)
1537
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1540
def test_no_final_path(self):
1541
transform, root = self.get_transform()
1542
trans_id = transform.trans_id_file_id('foo')
1543
transform.create_file('bar', trans_id)
1544
transform.cancel_creation(trans_id)
1547
def test_create_from_tree(self):
1548
tree1 = self.make_branch_and_tree('tree1')
1549
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1550
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1551
tree2 = self.make_branch_and_tree('tree2')
1552
tt = TreeTransform(tree2)
1553
foo_trans_id = tt.create_path('foo', tt.root)
1554
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1555
bar_trans_id = tt.create_path('bar', tt.root)
1556
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1558
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1559
self.assertFileEqual('baz', 'tree2/bar')
1561
def test_create_from_tree_bytes(self):
1562
"""Provided lines are used instead of tree content."""
1563
tree1 = self.make_branch_and_tree('tree1')
1564
self.build_tree_contents([('tree1/foo', 'bar'),])
1565
tree1.add('foo', 'foo-id')
1566
tree2 = self.make_branch_and_tree('tree2')
1567
tt = TreeTransform(tree2)
1568
foo_trans_id = tt.create_path('foo', tt.root)
1569
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1571
self.assertFileEqual('qux', 'tree2/foo')
1573
def test_create_from_tree_symlink(self):
1574
self.requireFeature(SymlinkFeature)
1575
tree1 = self.make_branch_and_tree('tree1')
1576
os.symlink('bar', 'tree1/foo')
1577
tree1.add('foo', 'foo-id')
1578
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1579
foo_trans_id = tt.create_path('foo', tt.root)
1580
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1582
self.assertEqual('bar', os.readlink('tree2/foo'))
1585
class TransformGroup(object):
1587
def __init__(self, dirname, root_id):
1590
self.wt = BzrDir.create_standalone_workingtree(dirname)
1591
self.wt.set_root_id(root_id)
1592
self.b = self.wt.branch
1593
self.tt = TreeTransform(self.wt)
1594
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1597
def conflict_text(tree, merge):
1598
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1599
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1602
class TestTransformMerge(TestCaseInTempDir):
1604
def test_text_merge(self):
1605
root_id = generate_ids.gen_root_id()
1606
base = TransformGroup("base", root_id)
1607
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1608
base.tt.new_file('b', base.root, 'b1', 'b')
1609
base.tt.new_file('c', base.root, 'c', 'c')
1610
base.tt.new_file('d', base.root, 'd', 'd')
1611
base.tt.new_file('e', base.root, 'e', 'e')
1612
base.tt.new_file('f', base.root, 'f', 'f')
1613
base.tt.new_directory('g', base.root, 'g')
1614
base.tt.new_directory('h', base.root, 'h')
1616
other = TransformGroup("other", root_id)
1617
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1618
other.tt.new_file('b', other.root, 'b2', 'b')
1619
other.tt.new_file('c', other.root, 'c2', 'c')
1620
other.tt.new_file('d', other.root, 'd', 'd')
1621
other.tt.new_file('e', other.root, 'e2', 'e')
1622
other.tt.new_file('f', other.root, 'f', 'f')
1623
other.tt.new_file('g', other.root, 'g', 'g')
1624
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1625
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1627
this = TransformGroup("this", root_id)
1628
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1629
this.tt.new_file('b', this.root, 'b', 'b')
1630
this.tt.new_file('c', this.root, 'c', 'c')
1631
this.tt.new_file('d', this.root, 'd2', 'd')
1632
this.tt.new_file('e', this.root, 'e2', 'e')
1633
this.tt.new_file('f', this.root, 'f', 'f')
1634
this.tt.new_file('g', this.root, 'g', 'g')
1635
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1636
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1638
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1641
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1642
# three-way text conflict
1643
self.assertEqual(this.wt.get_file('b').read(),
1644
conflict_text('b', 'b2'))
1646
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1648
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1649
# Ambigious clean merge
1650
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1652
self.assertEqual(this.wt.get_file('f').read(), 'f')
1653
# Correct correct results when THIS == OTHER
1654
self.assertEqual(this.wt.get_file('g').read(), 'g')
1655
# Text conflict when THIS & OTHER are text and BASE is dir
1656
self.assertEqual(this.wt.get_file('h').read(),
1657
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1658
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1660
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1662
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1663
self.assertEqual(this.wt.get_file('i').read(),
1664
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1665
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1667
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1669
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1670
modified = ['a', 'b', 'c', 'h', 'i']
1671
merge_modified = this.wt.merge_modified()
1672
self.assertSubset(merge_modified, modified)
1673
self.assertEqual(len(merge_modified), len(modified))
1674
file(this.wt.id2abspath('a'), 'wb').write('booga')
1676
merge_modified = this.wt.merge_modified()
1677
self.assertSubset(merge_modified, modified)
1678
self.assertEqual(len(merge_modified), len(modified))
1682
def test_file_merge(self):
1683
self.requireFeature(SymlinkFeature)
1684
root_id = generate_ids.gen_root_id()
1685
base = TransformGroup("BASE", root_id)
1686
this = TransformGroup("THIS", root_id)
1687
other = TransformGroup("OTHER", root_id)
1688
for tg in this, base, other:
1689
tg.tt.new_directory('a', tg.root, 'a')
1690
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1691
tg.tt.new_file('c', tg.root, 'c', 'c')
1692
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1693
targets = ((base, 'base-e', 'base-f', None, None),
1694
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1695
(other, 'other-e', None, 'other-g', 'other-h'))
1696
for tg, e_target, f_target, g_target, h_target in targets:
1697
for link, target in (('e', e_target), ('f', f_target),
1698
('g', g_target), ('h', h_target)):
1699
if target is not None:
1700
tg.tt.new_symlink(link, tg.root, target, link)
1702
for tg in this, base, other:
1704
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1705
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1706
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1707
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1708
for suffix in ('THIS', 'BASE', 'OTHER'):
1709
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1710
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1711
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1712
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1713
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1714
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1715
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1716
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1717
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1718
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1719
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1720
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1721
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1722
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1724
def test_filename_merge(self):
1725
root_id = generate_ids.gen_root_id()
1726
base = TransformGroup("BASE", root_id)
1727
this = TransformGroup("THIS", root_id)
1728
other = TransformGroup("OTHER", root_id)
1729
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1730
for t in [base, this, other]]
1731
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1732
for t in [base, this, other]]
1733
base.tt.new_directory('c', base_a, 'c')
1734
this.tt.new_directory('c1', this_a, 'c')
1735
other.tt.new_directory('c', other_b, 'c')
1737
base.tt.new_directory('d', base_a, 'd')
1738
this.tt.new_directory('d1', this_b, 'd')
1739
other.tt.new_directory('d', other_a, 'd')
1741
base.tt.new_directory('e', base_a, 'e')
1742
this.tt.new_directory('e', this_a, 'e')
1743
other.tt.new_directory('e1', other_b, 'e')
1745
base.tt.new_directory('f', base_a, 'f')
1746
this.tt.new_directory('f1', this_b, 'f')
1747
other.tt.new_directory('f1', other_b, 'f')
1749
for tg in [this, base, other]:
1751
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1752
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1753
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1754
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1755
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1757
def test_filename_merge_conflicts(self):
1758
root_id = generate_ids.gen_root_id()
1759
base = TransformGroup("BASE", root_id)
1760
this = TransformGroup("THIS", root_id)
1761
other = TransformGroup("OTHER", root_id)
1762
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1763
for t in [base, this, other]]
1764
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1765
for t in [base, this, other]]
1767
base.tt.new_file('g', base_a, 'g', 'g')
1768
other.tt.new_file('g1', other_b, 'g1', 'g')
1770
base.tt.new_file('h', base_a, 'h', 'h')
1771
this.tt.new_file('h1', this_b, 'h1', 'h')
1773
base.tt.new_file('i', base.root, 'i', 'i')
1774
other.tt.new_directory('i1', this_b, 'i')
1776
for tg in [this, base, other]:
1778
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1780
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1781
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1782
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1783
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1784
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1785
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1786
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1789
class TestBuildTree(tests.TestCaseWithTransport):
1791
def test_build_tree_with_symlinks(self):
1792
self.requireFeature(SymlinkFeature)
1794
a = BzrDir.create_standalone_workingtree('a')
1796
file('a/foo/bar', 'wb').write('contents')
1797
os.symlink('a/foo/bar', 'a/foo/baz')
1798
a.add(['foo', 'foo/bar', 'foo/baz'])
1799
a.commit('initial commit')
1800
b = BzrDir.create_standalone_workingtree('b')
1801
basis = a.basis_tree()
1803
self.addCleanup(basis.unlock)
1804
build_tree(basis, b)
1805
self.assertIs(os.path.isdir('b/foo'), True)
1806
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1807
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1809
def test_build_with_references(self):
1810
tree = self.make_branch_and_tree('source',
1811
format='dirstate-with-subtree')
1812
subtree = self.make_branch_and_tree('source/subtree',
1813
format='dirstate-with-subtree')
1814
tree.add_reference(subtree)
1815
tree.commit('a revision')
1816
tree.branch.create_checkout('target')
1817
self.assertPathExists('target')
1818
self.assertPathExists('target/subtree')
1820
def test_file_conflict_handling(self):
1821
"""Ensure that when building trees, conflict handling is done"""
1822
source = self.make_branch_and_tree('source')
1823
target = self.make_branch_and_tree('target')
1824
self.build_tree(['source/file', 'target/file'])
1825
source.add('file', 'new-file')
1826
source.commit('added file')
1827
build_tree(source.basis_tree(), target)
1828
self.assertEqual([DuplicateEntry('Moved existing file to',
1829
'file.moved', 'file', None, 'new-file')],
1831
target2 = self.make_branch_and_tree('target2')
1832
target_file = file('target2/file', 'wb')
1834
source_file = file('source/file', 'rb')
1836
target_file.write(source_file.read())
1841
build_tree(source.basis_tree(), target2)
1842
self.assertEqual([], target2.conflicts())
1844
def test_symlink_conflict_handling(self):
1845
"""Ensure that when building trees, conflict handling is done"""
1846
self.requireFeature(SymlinkFeature)
1847
source = self.make_branch_and_tree('source')
1848
os.symlink('foo', 'source/symlink')
1849
source.add('symlink', 'new-symlink')
1850
source.commit('added file')
1851
target = self.make_branch_and_tree('target')
1852
os.symlink('bar', 'target/symlink')
1853
build_tree(source.basis_tree(), target)
1854
self.assertEqual([DuplicateEntry('Moved existing file to',
1855
'symlink.moved', 'symlink', None, 'new-symlink')],
1857
target = self.make_branch_and_tree('target2')
1858
os.symlink('foo', 'target2/symlink')
1859
build_tree(source.basis_tree(), target)
1860
self.assertEqual([], target.conflicts())
1862
def test_directory_conflict_handling(self):
1863
"""Ensure that when building trees, conflict handling is done"""
1864
source = self.make_branch_and_tree('source')
1865
target = self.make_branch_and_tree('target')
1866
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1867
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1868
source.commit('added file')
1869
build_tree(source.basis_tree(), target)
1870
self.assertEqual([], target.conflicts())
1871
self.assertPathExists('target/dir1/file')
1873
# Ensure contents are merged
1874
target = self.make_branch_and_tree('target2')
1875
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1876
build_tree(source.basis_tree(), target)
1877
self.assertEqual([], target.conflicts())
1878
self.assertPathExists('target2/dir1/file2')
1879
self.assertPathExists('target2/dir1/file')
1881
# Ensure new contents are suppressed for existing branches
1882
target = self.make_branch_and_tree('target3')
1883
self.make_branch('target3/dir1')
1884
self.build_tree(['target3/dir1/file2'])
1885
build_tree(source.basis_tree(), target)
1886
self.assertPathDoesNotExist('target3/dir1/file')
1887
self.assertPathExists('target3/dir1/file2')
1888
self.assertPathExists('target3/dir1.diverted/file')
1889
self.assertEqual([DuplicateEntry('Diverted to',
1890
'dir1.diverted', 'dir1', 'new-dir1', None)],
1893
target = self.make_branch_and_tree('target4')
1894
self.build_tree(['target4/dir1/'])
1895
self.make_branch('target4/dir1/file')
1896
build_tree(source.basis_tree(), target)
1897
self.assertPathExists('target4/dir1/file')
1898
self.assertEqual('directory', file_kind('target4/dir1/file'))
1899
self.assertPathExists('target4/dir1/file.diverted')
1900
self.assertEqual([DuplicateEntry('Diverted to',
1901
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1904
def test_mixed_conflict_handling(self):
1905
"""Ensure that when building trees, conflict handling is done"""
1906
source = self.make_branch_and_tree('source')
1907
target = self.make_branch_and_tree('target')
1908
self.build_tree(['source/name', 'target/name/'])
1909
source.add('name', 'new-name')
1910
source.commit('added file')
1911
build_tree(source.basis_tree(), target)
1912
self.assertEqual([DuplicateEntry('Moved existing file to',
1913
'name.moved', 'name', None, 'new-name')], target.conflicts())
1915
def test_raises_in_populated(self):
1916
source = self.make_branch_and_tree('source')
1917
self.build_tree(['source/name'])
1919
source.commit('added name')
1920
target = self.make_branch_and_tree('target')
1921
self.build_tree(['target/name'])
1923
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1924
build_tree, source.basis_tree(), target)
1926
def test_build_tree_rename_count(self):
1927
source = self.make_branch_and_tree('source')
1928
self.build_tree(['source/file1', 'source/dir1/'])
1929
source.add(['file1', 'dir1'])
1930
source.commit('add1')
1931
target1 = self.make_branch_and_tree('target1')
1932
transform_result = build_tree(source.basis_tree(), target1)
1933
self.assertEqual(2, transform_result.rename_count)
1935
self.build_tree(['source/dir1/file2'])
1936
source.add(['dir1/file2'])
1937
source.commit('add3')
1938
target2 = self.make_branch_and_tree('target2')
1939
transform_result = build_tree(source.basis_tree(), target2)
1940
# children of non-root directories should not be renamed
1941
self.assertEqual(2, transform_result.rename_count)
1943
def create_ab_tree(self):
1944
"""Create a committed test tree with two files"""
1945
source = self.make_branch_and_tree('source')
1946
self.build_tree_contents([('source/file1', 'A')])
1947
self.build_tree_contents([('source/file2', 'B')])
1948
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1949
source.commit('commit files')
1951
self.addCleanup(source.unlock)
1954
def test_build_tree_accelerator_tree(self):
1955
source = self.create_ab_tree()
1956
self.build_tree_contents([('source/file2', 'C')])
1958
real_source_get_file = source.get_file
1959
def get_file(file_id, path=None):
1960
calls.append(file_id)
1961
return real_source_get_file(file_id, path)
1962
source.get_file = get_file
1963
target = self.make_branch_and_tree('target')
1964
revision_tree = source.basis_tree()
1965
revision_tree.lock_read()
1966
self.addCleanup(revision_tree.unlock)
1967
build_tree(revision_tree, target, source)
1968
self.assertEqual(['file1-id'], calls)
1970
self.addCleanup(target.unlock)
1971
self.assertEqual([], list(target.iter_changes(revision_tree)))
1973
def test_build_tree_accelerator_tree_observes_sha1(self):
1974
source = self.create_ab_tree()
1975
sha1 = osutils.sha_string('A')
1976
target = self.make_branch_and_tree('target')
1978
self.addCleanup(target.unlock)
1979
state = target.current_dirstate()
1980
state._cutoff_time = time.time() + 60
1981
build_tree(source.basis_tree(), target, source)
1982
entry = state._get_entry(0, path_utf8='file1')
1983
self.assertEqual(sha1, entry[1][0][1])
1985
def test_build_tree_accelerator_tree_missing_file(self):
1986
source = self.create_ab_tree()
1987
os.unlink('source/file1')
1988
source.remove(['file2'])
1989
target = self.make_branch_and_tree('target')
1990
revision_tree = source.basis_tree()
1991
revision_tree.lock_read()
1992
self.addCleanup(revision_tree.unlock)
1993
build_tree(revision_tree, target, source)
1995
self.addCleanup(target.unlock)
1996
self.assertEqual([], list(target.iter_changes(revision_tree)))
1998
def test_build_tree_accelerator_wrong_kind(self):
1999
self.requireFeature(SymlinkFeature)
2000
source = self.make_branch_and_tree('source')
2001
self.build_tree_contents([('source/file1', '')])
2002
self.build_tree_contents([('source/file2', '')])
2003
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2004
source.commit('commit files')
2005
os.unlink('source/file2')
2006
self.build_tree_contents([('source/file2/', 'C')])
2007
os.unlink('source/file1')
2008
os.symlink('file2', 'source/file1')
2010
real_source_get_file = source.get_file
2011
def get_file(file_id, path=None):
2012
calls.append(file_id)
2013
return real_source_get_file(file_id, path)
2014
source.get_file = get_file
2015
target = self.make_branch_and_tree('target')
2016
revision_tree = source.basis_tree()
2017
revision_tree.lock_read()
2018
self.addCleanup(revision_tree.unlock)
2019
build_tree(revision_tree, target, source)
2020
self.assertEqual([], calls)
2022
self.addCleanup(target.unlock)
2023
self.assertEqual([], list(target.iter_changes(revision_tree)))
2025
def test_build_tree_hardlink(self):
2026
self.requireFeature(HardlinkFeature)
2027
source = self.create_ab_tree()
2028
target = self.make_branch_and_tree('target')
2029
revision_tree = source.basis_tree()
2030
revision_tree.lock_read()
2031
self.addCleanup(revision_tree.unlock)
2032
build_tree(revision_tree, target, source, hardlink=True)
2034
self.addCleanup(target.unlock)
2035
self.assertEqual([], list(target.iter_changes(revision_tree)))
2036
source_stat = os.stat('source/file1')
2037
target_stat = os.stat('target/file1')
2038
self.assertEqual(source_stat, target_stat)
2040
# Explicitly disallowing hardlinks should prevent them.
2041
target2 = self.make_branch_and_tree('target2')
2042
build_tree(revision_tree, target2, source, hardlink=False)
2044
self.addCleanup(target2.unlock)
2045
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2046
source_stat = os.stat('source/file1')
2047
target2_stat = os.stat('target2/file1')
2048
self.assertNotEqual(source_stat, target2_stat)
2050
def test_build_tree_accelerator_tree_moved(self):
2051
source = self.make_branch_and_tree('source')
2052
self.build_tree_contents([('source/file1', 'A')])
2053
source.add(['file1'], ['file1-id'])
2054
source.commit('commit files')
2055
source.rename_one('file1', 'file2')
2057
self.addCleanup(source.unlock)
2058
target = self.make_branch_and_tree('target')
2059
revision_tree = source.basis_tree()
2060
revision_tree.lock_read()
2061
self.addCleanup(revision_tree.unlock)
2062
build_tree(revision_tree, target, source)
2064
self.addCleanup(target.unlock)
2065
self.assertEqual([], list(target.iter_changes(revision_tree)))
2067
def test_build_tree_hardlinks_preserve_execute(self):
2068
self.requireFeature(HardlinkFeature)
2069
source = self.create_ab_tree()
2070
tt = TreeTransform(source)
2071
trans_id = tt.trans_id_tree_file_id('file1-id')
2072
tt.set_executability(True, trans_id)
2074
self.assertTrue(source.is_executable('file1-id'))
2075
target = self.make_branch_and_tree('target')
2076
revision_tree = source.basis_tree()
2077
revision_tree.lock_read()
2078
self.addCleanup(revision_tree.unlock)
2079
build_tree(revision_tree, target, source, hardlink=True)
2081
self.addCleanup(target.unlock)
2082
self.assertEqual([], list(target.iter_changes(revision_tree)))
2083
self.assertTrue(source.is_executable('file1-id'))
2085
def install_rot13_content_filter(self, pattern):
2087
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2088
# below, but that looks a bit... hard to read even if it's exactly
2090
original_registry = filters._reset_registry()
2091
def restore_registry():
2092
filters._reset_registry(original_registry)
2093
self.addCleanup(restore_registry)
2094
def rot13(chunks, context=None):
2095
return [''.join(chunks).encode('rot13')]
2096
rot13filter = filters.ContentFilter(rot13, rot13)
2097
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2098
os.mkdir(self.test_home_dir + '/.bazaar')
2099
rules_filename = self.test_home_dir + '/.bazaar/rules'
2100
f = open(rules_filename, 'wb')
2101
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2103
def uninstall_rules():
2104
os.remove(rules_filename)
2106
self.addCleanup(uninstall_rules)
2109
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2110
"""build_tree will not hardlink files that have content filtering rules
2111
applied to them (but will still hardlink other files from the same tree
2114
self.requireFeature(HardlinkFeature)
2115
self.install_rot13_content_filter('file1')
2116
source = self.create_ab_tree()
2117
target = self.make_branch_and_tree('target')
2118
revision_tree = source.basis_tree()
2119
revision_tree.lock_read()
2120
self.addCleanup(revision_tree.unlock)
2121
build_tree(revision_tree, target, source, hardlink=True)
2123
self.addCleanup(target.unlock)
2124
self.assertEqual([], list(target.iter_changes(revision_tree)))
2125
source_stat = os.stat('source/file1')
2126
target_stat = os.stat('target/file1')
2127
self.assertNotEqual(source_stat, target_stat)
2128
source_stat = os.stat('source/file2')
2129
target_stat = os.stat('target/file2')
2130
self.assertEqualStat(source_stat, target_stat)
2132
def test_case_insensitive_build_tree_inventory(self):
2133
if (tests.CaseInsensitiveFilesystemFeature.available()
2134
or tests.CaseInsCasePresFilenameFeature.available()):
2135
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2136
source = self.make_branch_and_tree('source')
2137
self.build_tree(['source/file', 'source/FILE'])
2138
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2139
source.commit('added files')
2140
# Don't try this at home, kids!
2141
# Force the tree to report that it is case insensitive
2142
target = self.make_branch_and_tree('target')
2143
target.case_sensitive = False
2144
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2145
self.assertEqual('file.moved', target.id2path('lower-id'))
2146
self.assertEqual('FILE', target.id2path('upper-id'))
2148
def test_build_tree_observes_sha(self):
2149
source = self.make_branch_and_tree('source')
2150
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2151
source.add(['file1', 'dir', 'dir/file2'],
2152
['file1-id', 'dir-id', 'file2-id'])
2153
source.commit('new files')
2154
target = self.make_branch_and_tree('target')
2156
self.addCleanup(target.unlock)
2157
# We make use of the fact that DirState caches its cutoff time. So we
2158
# set the 'safe' time to one minute in the future.
2159
state = target.current_dirstate()
2160
state._cutoff_time = time.time() + 60
2161
build_tree(source.basis_tree(), target)
2162
entry1_sha = osutils.sha_file_by_name('source/file1')
2163
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2164
# entry[1] is the state information, entry[1][0] is the state of the
2165
# working tree, entry[1][0][1] is the sha value for the current working
2167
entry1 = state._get_entry(0, path_utf8='file1')
2168
self.assertEqual(entry1_sha, entry1[1][0][1])
2169
# The 'size' field must also be set.
2170
self.assertEqual(25, entry1[1][0][2])
2171
entry1_state = entry1[1][0]
2172
entry2 = state._get_entry(0, path_utf8='dir/file2')
2173
self.assertEqual(entry2_sha, entry2[1][0][1])
2174
self.assertEqual(29, entry2[1][0][2])
2175
entry2_state = entry2[1][0]
2176
# Now, make sure that we don't have to re-read the content. The
2177
# packed_stat should match exactly.
2178
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2179
self.assertEqual(entry2_sha,
2180
target.get_file_sha1('file2-id', 'dir/file2'))
2181
self.assertEqual(entry1_state, entry1[1][0])
2182
self.assertEqual(entry2_state, entry2[1][0])
2185
class TestCommitTransform(tests.TestCaseWithTransport):
2187
def get_branch(self):
2188
tree = self.make_branch_and_tree('tree')
2190
self.addCleanup(tree.unlock)
2191
tree.commit('empty commit')
2194
def get_branch_and_transform(self):
2195
branch = self.get_branch()
2196
tt = TransformPreview(branch.basis_tree())
2197
self.addCleanup(tt.finalize)
2200
def test_commit_wrong_basis(self):
2201
branch = self.get_branch()
2202
basis = branch.repository.revision_tree(
2203
_mod_revision.NULL_REVISION)
2204
tt = TransformPreview(basis)
2205
self.addCleanup(tt.finalize)
2206
e = self.assertRaises(ValueError, tt.commit, branch, '')
2207
self.assertEqual('TreeTransform not based on branch basis: null:',
2210
def test_empy_commit(self):
2211
branch, tt = self.get_branch_and_transform()
2212
rev = tt.commit(branch, 'my message')
2213
self.assertEqual(2, branch.revno())
2214
repo = branch.repository
2215
self.assertEqual('my message', repo.get_revision(rev).message)
2217
def test_merge_parents(self):
2218
branch, tt = self.get_branch_and_transform()
2219
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2220
self.assertEqual(['rev1b', 'rev1c'],
2221
branch.basis_tree().get_parent_ids()[1:])
2223
def test_first_commit(self):
2224
branch = self.make_branch('branch')
2226
self.addCleanup(branch.unlock)
2227
tt = TransformPreview(branch.basis_tree())
2228
self.addCleanup(tt.finalize)
2229
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2230
rev = tt.commit(branch, 'my message')
2231
self.assertEqual([], branch.basis_tree().get_parent_ids())
2232
self.assertNotEqual(_mod_revision.NULL_REVISION,
2233
branch.last_revision())
2235
def test_first_commit_with_merge_parents(self):
2236
branch = self.make_branch('branch')
2238
self.addCleanup(branch.unlock)
2239
tt = TransformPreview(branch.basis_tree())
2240
self.addCleanup(tt.finalize)
2241
e = self.assertRaises(ValueError, tt.commit, branch,
2242
'my message', ['rev1b-id'])
2243
self.assertEqual('Cannot supply merge parents for first commit.',
2245
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2247
def test_add_files(self):
2248
branch, tt = self.get_branch_and_transform()
2249
tt.new_file('file', tt.root, 'contents', 'file-id')
2250
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2251
if SymlinkFeature.available():
2252
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2253
rev = tt.commit(branch, 'message')
2254
tree = branch.basis_tree()
2255
self.assertEqual('file', tree.id2path('file-id'))
2256
self.assertEqual('contents', tree.get_file_text('file-id'))
2257
self.assertEqual('dir', tree.id2path('dir-id'))
2258
if SymlinkFeature.available():
2259
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2260
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2262
def test_add_unversioned(self):
2263
branch, tt = self.get_branch_and_transform()
2264
tt.new_file('file', tt.root, 'contents')
2265
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2266
'message', strict=True)
2268
def test_modify_strict(self):
2269
branch, tt = self.get_branch_and_transform()
2270
tt.new_file('file', tt.root, 'contents', 'file-id')
2271
tt.commit(branch, 'message', strict=True)
2272
tt = TransformPreview(branch.basis_tree())
2273
self.addCleanup(tt.finalize)
2274
trans_id = tt.trans_id_file_id('file-id')
2275
tt.delete_contents(trans_id)
2276
tt.create_file('contents', trans_id)
2277
tt.commit(branch, 'message', strict=True)
2279
def test_commit_malformed(self):
2280
"""Committing a malformed transform should raise an exception.
2282
In this case, we are adding a file without adding its parent.
2284
branch, tt = self.get_branch_and_transform()
2285
parent_id = tt.trans_id_file_id('parent-id')
2286
tt.new_file('file', parent_id, 'contents', 'file-id')
2287
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2290
def test_commit_rich_revision_data(self):
2291
branch, tt = self.get_branch_and_transform()
2292
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2293
committer='me <me@example.com>',
2294
revprops={'foo': 'bar'}, revision_id='revid-1',
2295
authors=['Author1 <author1@example.com>',
2296
'Author2 <author2@example.com>',
2298
self.assertEqual('revid-1', rev_id)
2299
revision = branch.repository.get_revision(rev_id)
2300
self.assertEqual(1, revision.timestamp)
2301
self.assertEqual(43201, revision.timezone)
2302
self.assertEqual('me <me@example.com>', revision.committer)
2303
self.assertEqual(['Author1 <author1@example.com>',
2304
'Author2 <author2@example.com>'],
2305
revision.get_apparent_authors())
2306
del revision.properties['authors']
2307
self.assertEqual({'foo': 'bar',
2308
'branch-nick': 'tree'},
2309
revision.properties)
2311
def test_no_explicit_revprops(self):
2312
branch, tt = self.get_branch_and_transform()
2313
rev_id = tt.commit(branch, 'message', authors=[
2314
'Author1 <author1@example.com>',
2315
'Author2 <author2@example.com>', ])
2316
revision = branch.repository.get_revision(rev_id)
2317
self.assertEqual(['Author1 <author1@example.com>',
2318
'Author2 <author2@example.com>'],
2319
revision.get_apparent_authors())
2320
self.assertEqual('tree', revision.properties['branch-nick'])
2323
class TestBackupName(tests.TestCase):
2325
def test_deprecations(self):
2326
class MockTransform(object):
2328
def has_named_child(self, by_parent, parent_id, name):
2329
return name in by_parent.get(parent_id, [])
2331
class MockEntry(object):
2334
object.__init__(self)
2337
tt = MockTransform()
2338
name1 = self.applyDeprecated(
2339
symbol_versioning.deprecated_in((2, 3, 0)),
2340
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2341
self.assertEqual('name.~1~', name1)
2342
name2 = self.applyDeprecated(
2343
symbol_versioning.deprecated_in((2, 3, 0)),
2344
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2345
self.assertEqual('name.~2~', name2)
2348
class TestFileMover(tests.TestCaseWithTransport):
2350
def test_file_mover(self):
2351
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2352
mover = _FileMover()
2353
mover.rename('a', 'q')
2354
self.assertPathExists('q')
2355
self.assertPathDoesNotExist('a')
2356
self.assertPathExists('q/b')
2357
self.assertPathExists('c')
2358
self.assertPathExists('c/d')
2360
def test_pre_delete_rollback(self):
2361
self.build_tree(['a/'])
2362
mover = _FileMover()
2363
mover.pre_delete('a', 'q')
2364
self.assertPathExists('q')
2365
self.assertPathDoesNotExist('a')
2367
self.assertPathDoesNotExist('q')
2368
self.assertPathExists('a')
2370
def test_apply_deletions(self):
2371
self.build_tree(['a/', 'b/'])
2372
mover = _FileMover()
2373
mover.pre_delete('a', 'q')
2374
mover.pre_delete('b', 'r')
2375
self.assertPathExists('q')
2376
self.assertPathExists('r')
2377
self.assertPathDoesNotExist('a')
2378
self.assertPathDoesNotExist('b')
2379
mover.apply_deletions()
2380
self.assertPathDoesNotExist('q')
2381
self.assertPathDoesNotExist('r')
2382
self.assertPathDoesNotExist('a')
2383
self.assertPathDoesNotExist('b')
2385
def test_file_mover_rollback(self):
2386
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2387
mover = _FileMover()
2388
mover.rename('c/d', 'c/f')
2389
mover.rename('c/e', 'c/d')
2391
mover.rename('a', 'c')
2392
except errors.FileExists, e:
2394
self.assertPathExists('a')
2395
self.assertPathExists('c/d')
2398
class Bogus(Exception):
2402
class TestTransformRollback(tests.TestCaseWithTransport):
2404
class ExceptionFileMover(_FileMover):
2406
def __init__(self, bad_source=None, bad_target=None):
2407
_FileMover.__init__(self)
2408
self.bad_source = bad_source
2409
self.bad_target = bad_target
2411
def rename(self, source, target):
2412
if (self.bad_source is not None and
2413
source.endswith(self.bad_source)):
2415
elif (self.bad_target is not None and
2416
target.endswith(self.bad_target)):
2419
_FileMover.rename(self, source, target)
2421
def test_rollback_rename(self):
2422
tree = self.make_branch_and_tree('.')
2423
self.build_tree(['a/', 'a/b'])
2424
tt = TreeTransform(tree)
2425
self.addCleanup(tt.finalize)
2426
a_id = tt.trans_id_tree_path('a')
2427
tt.adjust_path('c', tt.root, a_id)
2428
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2429
self.assertRaises(Bogus, tt.apply,
2430
_mover=self.ExceptionFileMover(bad_source='a'))
2431
self.assertPathExists('a')
2432
self.assertPathExists('a/b')
2434
self.assertPathExists('c')
2435
self.assertPathExists('c/d')
2437
def test_rollback_rename_into_place(self):
2438
tree = self.make_branch_and_tree('.')
2439
self.build_tree(['a/', 'a/b'])
2440
tt = TreeTransform(tree)
2441
self.addCleanup(tt.finalize)
2442
a_id = tt.trans_id_tree_path('a')
2443
tt.adjust_path('c', tt.root, a_id)
2444
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2445
self.assertRaises(Bogus, tt.apply,
2446
_mover=self.ExceptionFileMover(bad_target='c/d'))
2447
self.assertPathExists('a')
2448
self.assertPathExists('a/b')
2450
self.assertPathExists('c')
2451
self.assertPathExists('c/d')
2453
def test_rollback_deletion(self):
2454
tree = self.make_branch_and_tree('.')
2455
self.build_tree(['a/', 'a/b'])
2456
tt = TreeTransform(tree)
2457
self.addCleanup(tt.finalize)
2458
a_id = tt.trans_id_tree_path('a')
2459
tt.delete_contents(a_id)
2460
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2461
self.assertRaises(Bogus, tt.apply,
2462
_mover=self.ExceptionFileMover(bad_target='d'))
2463
self.assertPathExists('a')
2464
self.assertPathExists('a/b')
2467
class TestTransformMissingParent(tests.TestCaseWithTransport):
2469
def make_tt_with_versioned_dir(self):
2470
wt = self.make_branch_and_tree('.')
2471
self.build_tree(['dir/',])
2472
wt.add(['dir'], ['dir-id'])
2473
wt.commit('Create dir')
2474
tt = TreeTransform(wt)
2475
self.addCleanup(tt.finalize)
2478
def test_resolve_create_parent_for_versioned_file(self):
2479
wt, tt = self.make_tt_with_versioned_dir()
2480
dir_tid = tt.trans_id_tree_file_id('dir-id')
2481
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2482
tt.delete_contents(dir_tid)
2483
tt.unversion_file(dir_tid)
2484
conflicts = resolve_conflicts(tt)
2485
# one conflict for the missing directory, one for the unversioned
2487
self.assertLength(2, conflicts)
2489
def test_non_versioned_file_create_conflict(self):
2490
wt, tt = self.make_tt_with_versioned_dir()
2491
dir_tid = tt.trans_id_tree_file_id('dir-id')
2492
tt.new_file('file', dir_tid, 'Contents')
2493
tt.delete_contents(dir_tid)
2494
tt.unversion_file(dir_tid)
2495
conflicts = resolve_conflicts(tt)
2496
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2497
self.assertLength(1, conflicts)
2498
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2502
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2503
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2505
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2506
('', ''), ('directory', 'directory'), (False, False))
2509
class TestTransformPreview(tests.TestCaseWithTransport):
2511
def create_tree(self):
2512
tree = self.make_branch_and_tree('.')
2513
self.build_tree_contents([('a', 'content 1')])
2514
tree.set_root_id('TREE_ROOT')
2515
tree.add('a', 'a-id')
2516
tree.commit('rev1', rev_id='rev1')
2517
return tree.branch.repository.revision_tree('rev1')
2519
def get_empty_preview(self):
2520
repository = self.make_repository('repo')
2521
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2522
preview = TransformPreview(tree)
2523
self.addCleanup(preview.finalize)
2526
def test_transform_preview(self):
2527
revision_tree = self.create_tree()
2528
preview = TransformPreview(revision_tree)
2529
self.addCleanup(preview.finalize)
2531
def test_transform_preview_tree(self):
2532
revision_tree = self.create_tree()
2533
preview = TransformPreview(revision_tree)
2534
self.addCleanup(preview.finalize)
2535
preview.get_preview_tree()
2537
def test_transform_new_file(self):
2538
revision_tree = self.create_tree()
2539
preview = TransformPreview(revision_tree)
2540
self.addCleanup(preview.finalize)
2541
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2542
preview_tree = preview.get_preview_tree()
2543
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2545
preview_tree.get_file('file2-id').read(), 'content B\n')
2547
def test_diff_preview_tree(self):
2548
revision_tree = self.create_tree()
2549
preview = TransformPreview(revision_tree)
2550
self.addCleanup(preview.finalize)
2551
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2552
preview_tree = preview.get_preview_tree()
2554
show_diff_trees(revision_tree, preview_tree, out)
2555
lines = out.getvalue().splitlines()
2556
self.assertEqual(lines[0], "=== added file 'file2'")
2557
# 3 lines of diff administrivia
2558
self.assertEqual(lines[4], "+content B")
2560
def test_transform_conflicts(self):
2561
revision_tree = self.create_tree()
2562
preview = TransformPreview(revision_tree)
2563
self.addCleanup(preview.finalize)
2564
preview.new_file('a', preview.root, 'content 2')
2565
resolve_conflicts(preview)
2566
trans_id = preview.trans_id_file_id('a-id')
2567
self.assertEqual('a.moved', preview.final_name(trans_id))
2569
def get_tree_and_preview_tree(self):
2570
revision_tree = self.create_tree()
2571
preview = TransformPreview(revision_tree)
2572
self.addCleanup(preview.finalize)
2573
a_trans_id = preview.trans_id_file_id('a-id')
2574
preview.delete_contents(a_trans_id)
2575
preview.create_file('b content', a_trans_id)
2576
preview_tree = preview.get_preview_tree()
2577
return revision_tree, preview_tree
2579
def test_iter_changes(self):
2580
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2581
root = revision_tree.inventory.root.file_id
2582
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2583
(root, root), ('a', 'a'), ('file', 'file'),
2585
list(preview_tree.iter_changes(revision_tree)))
2587
def test_include_unchanged_succeeds(self):
2588
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2589
changes = preview_tree.iter_changes(revision_tree,
2590
include_unchanged=True)
2591
root = revision_tree.inventory.root.file_id
2593
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2595
def test_specific_files(self):
2596
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2597
changes = preview_tree.iter_changes(revision_tree,
2598
specific_files=[''])
2599
self.assertEqual([A_ENTRY], list(changes))
2601
def test_want_unversioned(self):
2602
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2603
changes = preview_tree.iter_changes(revision_tree,
2604
want_unversioned=True)
2605
self.assertEqual([A_ENTRY], list(changes))
2607
def test_ignore_extra_trees_no_specific_files(self):
2608
# extra_trees is harmless without specific_files, so we'll silently
2609
# accept it, even though we won't use it.
2610
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2611
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2613
def test_ignore_require_versioned_no_specific_files(self):
2614
# require_versioned is meaningless without specific_files.
2615
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2616
preview_tree.iter_changes(revision_tree, require_versioned=False)
2618
def test_ignore_pb(self):
2619
# pb could be supported, but TT.iter_changes doesn't support it.
2620
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2621
preview_tree.iter_changes(revision_tree)
2623
def test_kind(self):
2624
revision_tree = self.create_tree()
2625
preview = TransformPreview(revision_tree)
2626
self.addCleanup(preview.finalize)
2627
preview.new_file('file', preview.root, 'contents', 'file-id')
2628
preview.new_directory('directory', preview.root, 'dir-id')
2629
preview_tree = preview.get_preview_tree()
2630
self.assertEqual('file', preview_tree.kind('file-id'))
2631
self.assertEqual('directory', preview_tree.kind('dir-id'))
2633
def test_get_file_mtime(self):
2634
preview = self.get_empty_preview()
2635
file_trans_id = preview.new_file('file', preview.root, 'contents',
2637
limbo_path = preview._limbo_name(file_trans_id)
2638
preview_tree = preview.get_preview_tree()
2639
self.assertEqual(os.stat(limbo_path).st_mtime,
2640
preview_tree.get_file_mtime('file-id'))
2642
def test_get_file_mtime_renamed(self):
2643
work_tree = self.make_branch_and_tree('tree')
2644
self.build_tree(['tree/file'])
2645
work_tree.add('file', 'file-id')
2646
preview = TransformPreview(work_tree)
2647
self.addCleanup(preview.finalize)
2648
file_trans_id = preview.trans_id_tree_file_id('file-id')
2649
preview.adjust_path('renamed', preview.root, file_trans_id)
2650
preview_tree = preview.get_preview_tree()
2651
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2652
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2654
def test_get_file_size(self):
2655
work_tree = self.make_branch_and_tree('tree')
2656
self.build_tree_contents([('tree/old', 'old')])
2657
work_tree.add('old', 'old-id')
2658
preview = TransformPreview(work_tree)
2659
self.addCleanup(preview.finalize)
2660
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2662
tree = preview.get_preview_tree()
2663
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2664
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2666
def test_get_file(self):
2667
preview = self.get_empty_preview()
2668
preview.new_file('file', preview.root, 'contents', 'file-id')
2669
preview_tree = preview.get_preview_tree()
2670
tree_file = preview_tree.get_file('file-id')
2672
self.assertEqual('contents', tree_file.read())
2676
def test_get_symlink_target(self):
2677
self.requireFeature(SymlinkFeature)
2678
preview = self.get_empty_preview()
2679
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2680
preview_tree = preview.get_preview_tree()
2681
self.assertEqual('target',
2682
preview_tree.get_symlink_target('symlink-id'))
2684
def test_all_file_ids(self):
2685
tree = self.make_branch_and_tree('tree')
2686
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2687
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2688
preview = TransformPreview(tree)
2689
self.addCleanup(preview.finalize)
2690
preview.unversion_file(preview.trans_id_file_id('b-id'))
2691
c_trans_id = preview.trans_id_file_id('c-id')
2692
preview.unversion_file(c_trans_id)
2693
preview.version_file('c-id', c_trans_id)
2694
preview_tree = preview.get_preview_tree()
2695
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2696
preview_tree.all_file_ids())
2698
def test_path2id_deleted_unchanged(self):
2699
tree = self.make_branch_and_tree('tree')
2700
self.build_tree(['tree/unchanged', 'tree/deleted'])
2701
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2702
preview = TransformPreview(tree)
2703
self.addCleanup(preview.finalize)
2704
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2705
preview_tree = preview.get_preview_tree()
2706
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2707
self.assertIs(None, preview_tree.path2id('deleted'))
2709
def test_path2id_created(self):
2710
tree = self.make_branch_and_tree('tree')
2711
self.build_tree(['tree/unchanged'])
2712
tree.add(['unchanged'], ['unchanged-id'])
2713
preview = TransformPreview(tree)
2714
self.addCleanup(preview.finalize)
2715
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2716
'contents', 'new-id')
2717
preview_tree = preview.get_preview_tree()
2718
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2720
def test_path2id_moved(self):
2721
tree = self.make_branch_and_tree('tree')
2722
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2723
tree.add(['old_parent', 'old_parent/child'],
2724
['old_parent-id', 'child-id'])
2725
preview = TransformPreview(tree)
2726
self.addCleanup(preview.finalize)
2727
new_parent = preview.new_directory('new_parent', preview.root,
2729
preview.adjust_path('child', new_parent,
2730
preview.trans_id_file_id('child-id'))
2731
preview_tree = preview.get_preview_tree()
2732
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2733
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2735
def test_path2id_renamed_parent(self):
2736
tree = self.make_branch_and_tree('tree')
2737
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2738
tree.add(['old_name', 'old_name/child'],
2739
['parent-id', 'child-id'])
2740
preview = TransformPreview(tree)
2741
self.addCleanup(preview.finalize)
2742
preview.adjust_path('new_name', preview.root,
2743
preview.trans_id_file_id('parent-id'))
2744
preview_tree = preview.get_preview_tree()
2745
self.assertIs(None, preview_tree.path2id('old_name/child'))
2746
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2748
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2749
preview_tree = tt.get_preview_tree()
2750
preview_result = list(preview_tree.iter_entries_by_dir(
2754
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2755
self.assertEqual(actual_result, preview_result)
2757
def test_iter_entries_by_dir_new(self):
2758
tree = self.make_branch_and_tree('tree')
2759
tt = TreeTransform(tree)
2760
tt.new_file('new', tt.root, 'contents', 'new-id')
2761
self.assertMatchingIterEntries(tt)
2763
def test_iter_entries_by_dir_deleted(self):
2764
tree = self.make_branch_and_tree('tree')
2765
self.build_tree(['tree/deleted'])
2766
tree.add('deleted', 'deleted-id')
2767
tt = TreeTransform(tree)
2768
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2769
self.assertMatchingIterEntries(tt)
2771
def test_iter_entries_by_dir_unversioned(self):
2772
tree = self.make_branch_and_tree('tree')
2773
self.build_tree(['tree/removed'])
2774
tree.add('removed', 'removed-id')
2775
tt = TreeTransform(tree)
2776
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2777
self.assertMatchingIterEntries(tt)
2779
def test_iter_entries_by_dir_moved(self):
2780
tree = self.make_branch_and_tree('tree')
2781
self.build_tree(['tree/moved', 'tree/new_parent/'])
2782
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2783
tt = TreeTransform(tree)
2784
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2785
tt.trans_id_file_id('moved-id'))
2786
self.assertMatchingIterEntries(tt)
2788
def test_iter_entries_by_dir_specific_file_ids(self):
2789
tree = self.make_branch_and_tree('tree')
2790
tree.set_root_id('tree-root-id')
2791
self.build_tree(['tree/parent/', 'tree/parent/child'])
2792
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2793
tt = TreeTransform(tree)
2794
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2796
def test_symlink_content_summary(self):
2797
self.requireFeature(SymlinkFeature)
2798
preview = self.get_empty_preview()
2799
preview.new_symlink('path', preview.root, 'target', 'path-id')
2800
summary = preview.get_preview_tree().path_content_summary('path')
2801
self.assertEqual(('symlink', None, None, 'target'), summary)
2803
def test_missing_content_summary(self):
2804
preview = self.get_empty_preview()
2805
summary = preview.get_preview_tree().path_content_summary('path')
2806
self.assertEqual(('missing', None, None, None), summary)
2808
def test_deleted_content_summary(self):
2809
tree = self.make_branch_and_tree('tree')
2810
self.build_tree(['tree/path/'])
2812
preview = TransformPreview(tree)
2813
self.addCleanup(preview.finalize)
2814
preview.delete_contents(preview.trans_id_tree_path('path'))
2815
summary = preview.get_preview_tree().path_content_summary('path')
2816
self.assertEqual(('missing', None, None, None), summary)
2818
def test_file_content_summary_executable(self):
2819
preview = self.get_empty_preview()
2820
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2821
preview.set_executability(True, path_id)
2822
summary = preview.get_preview_tree().path_content_summary('path')
2823
self.assertEqual(4, len(summary))
2824
self.assertEqual('file', summary[0])
2825
# size must be known
2826
self.assertEqual(len('contents'), summary[1])
2828
self.assertEqual(True, summary[2])
2829
# will not have hash (not cheap to determine)
2830
self.assertIs(None, summary[3])
2832
def test_change_executability(self):
2833
tree = self.make_branch_and_tree('tree')
2834
self.build_tree(['tree/path'])
2836
preview = TransformPreview(tree)
2837
self.addCleanup(preview.finalize)
2838
path_id = preview.trans_id_tree_path('path')
2839
preview.set_executability(True, path_id)
2840
summary = preview.get_preview_tree().path_content_summary('path')
2841
self.assertEqual(True, summary[2])
2843
def test_file_content_summary_non_exec(self):
2844
preview = self.get_empty_preview()
2845
preview.new_file('path', preview.root, 'contents', 'path-id')
2846
summary = preview.get_preview_tree().path_content_summary('path')
2847
self.assertEqual(4, len(summary))
2848
self.assertEqual('file', summary[0])
2849
# size must be known
2850
self.assertEqual(len('contents'), summary[1])
2852
self.assertEqual(False, summary[2])
2853
# will not have hash (not cheap to determine)
2854
self.assertIs(None, summary[3])
2856
def test_dir_content_summary(self):
2857
preview = self.get_empty_preview()
2858
preview.new_directory('path', preview.root, 'path-id')
2859
summary = preview.get_preview_tree().path_content_summary('path')
2860
self.assertEqual(('directory', None, None, None), summary)
2862
def test_tree_content_summary(self):
2863
preview = self.get_empty_preview()
2864
path = preview.new_directory('path', preview.root, 'path-id')
2865
preview.set_tree_reference('rev-1', path)
2866
summary = preview.get_preview_tree().path_content_summary('path')
2867
self.assertEqual(4, len(summary))
2868
self.assertEqual('tree-reference', summary[0])
2870
def test_annotate(self):
2871
tree = self.make_branch_and_tree('tree')
2872
self.build_tree_contents([('tree/file', 'a\n')])
2873
tree.add('file', 'file-id')
2874
tree.commit('a', rev_id='one')
2875
self.build_tree_contents([('tree/file', 'a\nb\n')])
2876
preview = TransformPreview(tree)
2877
self.addCleanup(preview.finalize)
2878
file_trans_id = preview.trans_id_file_id('file-id')
2879
preview.delete_contents(file_trans_id)
2880
preview.create_file('a\nb\nc\n', file_trans_id)
2881
preview_tree = preview.get_preview_tree()
2887
annotation = preview_tree.annotate_iter('file-id', 'me:')
2888
self.assertEqual(expected, annotation)
2890
def test_annotate_missing(self):
2891
preview = self.get_empty_preview()
2892
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2893
preview_tree = preview.get_preview_tree()
2899
annotation = preview_tree.annotate_iter('file-id', 'me:')
2900
self.assertEqual(expected, annotation)
2902
def test_annotate_rename(self):
2903
tree = self.make_branch_and_tree('tree')
2904
self.build_tree_contents([('tree/file', 'a\n')])
2905
tree.add('file', 'file-id')
2906
tree.commit('a', rev_id='one')
2907
preview = TransformPreview(tree)
2908
self.addCleanup(preview.finalize)
2909
file_trans_id = preview.trans_id_file_id('file-id')
2910
preview.adjust_path('newname', preview.root, file_trans_id)
2911
preview_tree = preview.get_preview_tree()
2915
annotation = preview_tree.annotate_iter('file-id', 'me:')
2916
self.assertEqual(expected, annotation)
2918
def test_annotate_deleted(self):
2919
tree = self.make_branch_and_tree('tree')
2920
self.build_tree_contents([('tree/file', 'a\n')])
2921
tree.add('file', 'file-id')
2922
tree.commit('a', rev_id='one')
2923
self.build_tree_contents([('tree/file', 'a\nb\n')])
2924
preview = TransformPreview(tree)
2925
self.addCleanup(preview.finalize)
2926
file_trans_id = preview.trans_id_file_id('file-id')
2927
preview.delete_contents(file_trans_id)
2928
preview_tree = preview.get_preview_tree()
2929
annotation = preview_tree.annotate_iter('file-id', 'me:')
2930
self.assertIs(None, annotation)
2932
def test_stored_kind(self):
2933
preview = self.get_empty_preview()
2934
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2935
preview_tree = preview.get_preview_tree()
2936
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2938
def test_is_executable(self):
2939
preview = self.get_empty_preview()
2940
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2941
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2942
preview_tree = preview.get_preview_tree()
2943
self.assertEqual(True, preview_tree.is_executable('file-id'))
2945
def test_get_set_parent_ids(self):
2946
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2947
self.assertEqual([], preview_tree.get_parent_ids())
2948
preview_tree.set_parent_ids(['rev-1'])
2949
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2951
def test_plan_file_merge(self):
2952
work_a = self.make_branch_and_tree('wta')
2953
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2954
work_a.add('file', 'file-id')
2955
base_id = work_a.commit('base version')
2956
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2957
preview = TransformPreview(work_a)
2958
self.addCleanup(preview.finalize)
2959
trans_id = preview.trans_id_file_id('file-id')
2960
preview.delete_contents(trans_id)
2961
preview.create_file('b\nc\nd\ne\n', trans_id)
2962
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2963
tree_a = preview.get_preview_tree()
2964
tree_a.set_parent_ids([base_id])
2966
('killed-a', 'a\n'),
2967
('killed-b', 'b\n'),
2968
('unchanged', 'c\n'),
2969
('unchanged', 'd\n'),
2972
], list(tree_a.plan_file_merge('file-id', tree_b)))
2974
def test_plan_file_merge_revision_tree(self):
2975
work_a = self.make_branch_and_tree('wta')
2976
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2977
work_a.add('file', 'file-id')
2978
base_id = work_a.commit('base version')
2979
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2980
preview = TransformPreview(work_a.basis_tree())
2981
self.addCleanup(preview.finalize)
2982
trans_id = preview.trans_id_file_id('file-id')
2983
preview.delete_contents(trans_id)
2984
preview.create_file('b\nc\nd\ne\n', trans_id)
2985
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2986
tree_a = preview.get_preview_tree()
2987
tree_a.set_parent_ids([base_id])
2989
('killed-a', 'a\n'),
2990
('killed-b', 'b\n'),
2991
('unchanged', 'c\n'),
2992
('unchanged', 'd\n'),
2995
], list(tree_a.plan_file_merge('file-id', tree_b)))
2997
def test_walkdirs(self):
2998
preview = self.get_empty_preview()
2999
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3000
# FIXME: new_directory should mark root.
3001
preview.fixup_new_roots()
3002
preview_tree = preview.get_preview_tree()
3003
file_trans_id = preview.new_file('a', preview.root, 'contents',
3005
expected = [(('', 'tree-root'),
3006
[('a', 'a', 'file', None, 'a-id', 'file')])]
3007
self.assertEqual(expected, list(preview_tree.walkdirs()))
3009
def test_extras(self):
3010
work_tree = self.make_branch_and_tree('tree')
3011
self.build_tree(['tree/removed-file', 'tree/existing-file',
3012
'tree/not-removed-file'])
3013
work_tree.add(['removed-file', 'not-removed-file'])
3014
preview = TransformPreview(work_tree)
3015
self.addCleanup(preview.finalize)
3016
preview.new_file('new-file', preview.root, 'contents')
3017
preview.new_file('new-versioned-file', preview.root, 'contents',
3019
tree = preview.get_preview_tree()
3020
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3021
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3024
def test_merge_into_preview(self):
3025
work_tree = self.make_branch_and_tree('tree')
3026
self.build_tree_contents([('tree/file','b\n')])
3027
work_tree.add('file', 'file-id')
3028
work_tree.commit('first commit')
3029
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3030
self.build_tree_contents([('child/file','b\nc\n')])
3031
child_tree.commit('child commit')
3032
child_tree.lock_write()
3033
self.addCleanup(child_tree.unlock)
3034
work_tree.lock_write()
3035
self.addCleanup(work_tree.unlock)
3036
preview = TransformPreview(work_tree)
3037
self.addCleanup(preview.finalize)
3038
file_trans_id = preview.trans_id_file_id('file-id')
3039
preview.delete_contents(file_trans_id)
3040
preview.create_file('a\nb\n', file_trans_id)
3041
preview_tree = preview.get_preview_tree()
3042
merger = Merger.from_revision_ids(None, preview_tree,
3043
child_tree.branch.last_revision(),
3044
other_branch=child_tree.branch,
3045
tree_branch=work_tree.branch)
3046
merger.merge_type = Merge3Merger
3047
tt = merger.make_merger().make_preview_transform()
3048
self.addCleanup(tt.finalize)
3049
final_tree = tt.get_preview_tree()
3050
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3052
def test_merge_preview_into_workingtree(self):
3053
tree = self.make_branch_and_tree('tree')
3054
tree.set_root_id('TREE_ROOT')
3055
tt = TransformPreview(tree)
3056
self.addCleanup(tt.finalize)
3057
tt.new_file('name', tt.root, 'content', 'file-id')
3058
tree2 = self.make_branch_and_tree('tree2')
3059
tree2.set_root_id('TREE_ROOT')
3060
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3061
None, tree.basis_tree())
3062
merger.merge_type = Merge3Merger
3065
def test_merge_preview_into_workingtree_handles_conflicts(self):
3066
tree = self.make_branch_and_tree('tree')
3067
self.build_tree_contents([('tree/foo', 'bar')])
3068
tree.add('foo', 'foo-id')
3070
tt = TransformPreview(tree)
3071
self.addCleanup(tt.finalize)
3072
trans_id = tt.trans_id_file_id('foo-id')
3073
tt.delete_contents(trans_id)
3074
tt.create_file('baz', trans_id)
3075
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3076
self.build_tree_contents([('tree2/foo', 'qux')])
3078
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3079
pb, tree.basis_tree())
3080
merger.merge_type = Merge3Merger
3083
def test_has_filename(self):
3084
wt = self.make_branch_and_tree('tree')
3085
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3086
tt = TransformPreview(wt)
3087
removed_id = tt.trans_id_tree_path('removed')
3088
tt.delete_contents(removed_id)
3089
tt.new_file('new', tt.root, 'contents')
3090
modified_id = tt.trans_id_tree_path('modified')
3091
tt.delete_contents(modified_id)
3092
tt.create_file('modified-contents', modified_id)
3093
self.addCleanup(tt.finalize)
3094
tree = tt.get_preview_tree()
3095
self.assertTrue(tree.has_filename('unmodified'))
3096
self.assertFalse(tree.has_filename('not-present'))
3097
self.assertFalse(tree.has_filename('removed'))
3098
self.assertTrue(tree.has_filename('new'))
3099
self.assertTrue(tree.has_filename('modified'))
3101
def test_is_executable(self):
3102
tree = self.make_branch_and_tree('tree')
3103
preview = TransformPreview(tree)
3104
self.addCleanup(preview.finalize)
3105
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3106
preview_tree = preview.get_preview_tree()
3107
self.assertEqual(False, preview_tree.is_executable('baz-id',
3109
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3111
def test_commit_preview_tree(self):
3112
tree = self.make_branch_and_tree('tree')
3113
rev_id = tree.commit('rev1')
3114
tree.branch.lock_write()
3115
self.addCleanup(tree.branch.unlock)
3116
tt = TransformPreview(tree)
3117
tt.new_file('file', tt.root, 'contents', 'file_id')
3118
self.addCleanup(tt.finalize)
3119
preview = tt.get_preview_tree()
3120
preview.set_parent_ids([rev_id])
3121
builder = tree.branch.get_commit_builder([rev_id])
3122
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3123
builder.finish_inventory()
3124
rev2_id = builder.commit('rev2')
3125
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3126
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3128
def test_ascii_limbo_paths(self):
3129
self.requireFeature(tests.UnicodeFilenameFeature)
3130
branch = self.make_branch('any')
3131
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3132
tt = TransformPreview(tree)
3133
self.addCleanup(tt.finalize)
3134
foo_id = tt.new_directory('', ROOT_PARENT)
3135
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3136
limbo_path = tt._limbo_name(bar_id)
3137
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3140
class FakeSerializer(object):
3141
"""Serializer implementation that simply returns the input.
3143
The input is returned in the order used by pack.ContainerPushParser.
3146
def bytes_record(bytes, names):
3150
class TestSerializeTransform(tests.TestCaseWithTransport):
3152
_test_needs_features = [tests.UnicodeFilenameFeature]
3154
def get_preview(self, tree=None):
3156
tree = self.make_branch_and_tree('tree')
3157
tt = TransformPreview(tree)
3158
self.addCleanup(tt.finalize)
3161
def assertSerializesTo(self, expected, tt):
3162
records = list(tt.serialize(FakeSerializer()))
3163
self.assertEqual(expected, records)
3166
def default_attribs():
3171
'_new_executability': {},
3173
'_tree_path_ids': {'': 'new-0'},
3175
'_removed_contents': [],
3176
'_non_present_ids': {},
3179
def make_records(self, attribs, contents):
3181
(((('attribs'),),), bencode.bencode(attribs))]
3182
records.extend([(((n, k),), c) for n, k, c in contents])
3185
def creation_records(self):
3186
attribs = self.default_attribs()
3187
attribs['_id_number'] = 3
3188
attribs['_new_name'] = {
3189
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3190
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3191
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3192
attribs['_new_executability'] = {'new-1': 1}
3194
('new-1', 'file', 'i 1\nbar\n'),
3195
('new-2', 'directory', ''),
3197
return self.make_records(attribs, contents)
3199
def test_serialize_creation(self):
3200
tt = self.get_preview()
3201
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3202
tt.new_directory('qux', tt.root, 'quxx')
3203
self.assertSerializesTo(self.creation_records(), tt)
3205
def test_deserialize_creation(self):
3206
tt = self.get_preview()
3207
tt.deserialize(iter(self.creation_records()))
3208
self.assertEqual(3, tt._id_number)
3209
self.assertEqual({'new-1': u'foo\u1234',
3210
'new-2': 'qux'}, tt._new_name)
3211
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3212
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3213
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3214
self.assertEqual({'new-1': True}, tt._new_executability)
3215
self.assertEqual({'new-1': 'file',
3216
'new-2': 'directory'}, tt._new_contents)
3217
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3219
foo_content = foo_limbo.read()
3222
self.assertEqual('bar', foo_content)
3224
def symlink_creation_records(self):
3225
attribs = self.default_attribs()
3226
attribs['_id_number'] = 2
3227
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3228
attribs['_new_parent'] = {'new-1': 'new-0'}
3229
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3230
return self.make_records(attribs, contents)
3232
def test_serialize_symlink_creation(self):
3233
self.requireFeature(tests.SymlinkFeature)
3234
tt = self.get_preview()
3235
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3236
self.assertSerializesTo(self.symlink_creation_records(), tt)
3238
def test_deserialize_symlink_creation(self):
3239
self.requireFeature(tests.SymlinkFeature)
3240
tt = self.get_preview()
3241
tt.deserialize(iter(self.symlink_creation_records()))
3242
abspath = tt._limbo_name('new-1')
3243
foo_content = osutils.readlink(abspath)
3244
self.assertEqual(u'bar\u1234', foo_content)
3246
def make_destruction_preview(self):
3247
tree = self.make_branch_and_tree('.')
3248
self.build_tree([u'foo\u1234', 'bar'])
3249
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3250
return self.get_preview(tree)
3252
def destruction_records(self):
3253
attribs = self.default_attribs()
3254
attribs['_id_number'] = 3
3255
attribs['_removed_id'] = ['new-1']
3256
attribs['_removed_contents'] = ['new-2']
3257
attribs['_tree_path_ids'] = {
3259
u'foo\u1234'.encode('utf-8'): 'new-1',
3262
return self.make_records(attribs, [])
3264
def test_serialize_destruction(self):
3265
tt = self.make_destruction_preview()
3266
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3267
tt.unversion_file(foo_trans_id)
3268
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3269
tt.delete_contents(bar_trans_id)
3270
self.assertSerializesTo(self.destruction_records(), tt)
3272
def test_deserialize_destruction(self):
3273
tt = self.make_destruction_preview()
3274
tt.deserialize(iter(self.destruction_records()))
3275
self.assertEqual({u'foo\u1234': 'new-1',
3277
'': tt.root}, tt._tree_path_ids)
3278
self.assertEqual({'new-1': u'foo\u1234',
3280
tt.root: ''}, tt._tree_id_paths)
3281
self.assertEqual(set(['new-1']), tt._removed_id)
3282
self.assertEqual(set(['new-2']), tt._removed_contents)
3284
def missing_records(self):
3285
attribs = self.default_attribs()
3286
attribs['_id_number'] = 2
3287
attribs['_non_present_ids'] = {
3289
return self.make_records(attribs, [])
3291
def test_serialize_missing(self):
3292
tt = self.get_preview()
3293
boo_trans_id = tt.trans_id_file_id('boo')
3294
self.assertSerializesTo(self.missing_records(), tt)
3296
def test_deserialize_missing(self):
3297
tt = self.get_preview()
3298
tt.deserialize(iter(self.missing_records()))
3299
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3301
def make_modification_preview(self):
3302
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3303
LINES_TWO = 'z\nbb\nx\ndd\n'
3304
tree = self.make_branch_and_tree('tree')
3305
self.build_tree_contents([('tree/file', LINES_ONE)])
3306
tree.add('file', 'file-id')
3307
return self.get_preview(tree), LINES_TWO
3309
def modification_records(self):
3310
attribs = self.default_attribs()
3311
attribs['_id_number'] = 2
3312
attribs['_tree_path_ids'] = {
3315
attribs['_removed_contents'] = ['new-1']
3316
contents = [('new-1', 'file',
3317
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3318
return self.make_records(attribs, contents)
3320
def test_serialize_modification(self):
3321
tt, LINES = self.make_modification_preview()
3322
trans_id = tt.trans_id_file_id('file-id')
3323
tt.delete_contents(trans_id)
3324
tt.create_file(LINES, trans_id)
3325
self.assertSerializesTo(self.modification_records(), tt)
3327
def test_deserialize_modification(self):
3328
tt, LINES = self.make_modification_preview()
3329
tt.deserialize(iter(self.modification_records()))
3330
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3332
def make_kind_change_preview(self):
3333
LINES = 'a\nb\nc\nd\n'
3334
tree = self.make_branch_and_tree('tree')
3335
self.build_tree(['tree/foo/'])
3336
tree.add('foo', 'foo-id')
3337
return self.get_preview(tree), LINES
3339
def kind_change_records(self):
3340
attribs = self.default_attribs()
3341
attribs['_id_number'] = 2
3342
attribs['_tree_path_ids'] = {
3345
attribs['_removed_contents'] = ['new-1']
3346
contents = [('new-1', 'file',
3347
'i 4\na\nb\nc\nd\n\n')]
3348
return self.make_records(attribs, contents)
3350
def test_serialize_kind_change(self):
3351
tt, LINES = self.make_kind_change_preview()
3352
trans_id = tt.trans_id_file_id('foo-id')
3353
tt.delete_contents(trans_id)
3354
tt.create_file(LINES, trans_id)
3355
self.assertSerializesTo(self.kind_change_records(), tt)
3357
def test_deserialize_kind_change(self):
3358
tt, LINES = self.make_kind_change_preview()
3359
tt.deserialize(iter(self.kind_change_records()))
3360
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3362
def make_add_contents_preview(self):
3363
LINES = 'a\nb\nc\nd\n'
3364
tree = self.make_branch_and_tree('tree')
3365
self.build_tree(['tree/foo'])
3367
os.unlink('tree/foo')
3368
return self.get_preview(tree), LINES
3370
def add_contents_records(self):
3371
attribs = self.default_attribs()
3372
attribs['_id_number'] = 2
3373
attribs['_tree_path_ids'] = {
3376
contents = [('new-1', 'file',
3377
'i 4\na\nb\nc\nd\n\n')]
3378
return self.make_records(attribs, contents)
3380
def test_serialize_add_contents(self):
3381
tt, LINES = self.make_add_contents_preview()
3382
trans_id = tt.trans_id_tree_path('foo')
3383
tt.create_file(LINES, trans_id)
3384
self.assertSerializesTo(self.add_contents_records(), tt)
3386
def test_deserialize_add_contents(self):
3387
tt, LINES = self.make_add_contents_preview()
3388
tt.deserialize(iter(self.add_contents_records()))
3389
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3391
def test_get_parents_lines(self):
3392
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3393
LINES_TWO = 'z\nbb\nx\ndd\n'
3394
tree = self.make_branch_and_tree('tree')
3395
self.build_tree_contents([('tree/file', LINES_ONE)])
3396
tree.add('file', 'file-id')
3397
tt = self.get_preview(tree)
3398
trans_id = tt.trans_id_tree_path('file')
3399
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3400
tt._get_parents_lines(trans_id))
3402
def test_get_parents_texts(self):
3403
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3404
LINES_TWO = 'z\nbb\nx\ndd\n'
3405
tree = self.make_branch_and_tree('tree')
3406
self.build_tree_contents([('tree/file', LINES_ONE)])
3407
tree.add('file', 'file-id')
3408
tt = self.get_preview(tree)
3409
trans_id = tt.trans_id_tree_path('file')
3410
self.assertEqual((LINES_ONE,),
3411
tt._get_parents_texts(trans_id))
3414
class TestOrphan(tests.TestCaseWithTransport):
3416
def test_no_orphan_for_transform_preview(self):
3417
tree = self.make_branch_and_tree('tree')
3418
tt = transform.TransformPreview(tree)
3419
self.addCleanup(tt.finalize)
3420
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3422
def _set_orphan_policy(self, wt, policy):
3423
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3426
def _prepare_orphan(self, wt):
3427
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3428
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3429
wt.commit('add dir and file ignoring foo')
3430
tt = transform.TreeTransform(wt)
3431
self.addCleanup(tt.finalize)
3432
# dir and bar are deleted
3433
dir_tid = tt.trans_id_tree_path('dir')
3434
file_tid = tt.trans_id_tree_path('dir/file')
3435
orphan_tid = tt.trans_id_tree_path('dir/foo')
3436
tt.delete_contents(file_tid)
3437
tt.unversion_file(file_tid)
3438
tt.delete_contents(dir_tid)
3439
tt.unversion_file(dir_tid)
3440
# There should be a conflict because dir still contain foo
3441
raw_conflicts = tt.find_conflicts()
3442
self.assertLength(1, raw_conflicts)
3443
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3444
return tt, orphan_tid
3446
def test_new_orphan_created(self):
3447
wt = self.make_branch_and_tree('.')
3448
self._set_orphan_policy(wt, 'move')
3449
tt, orphan_tid = self._prepare_orphan(wt)
3452
warnings.append(args[0] % args[1:])
3453
self.overrideAttr(trace, 'warning', warning)
3454
remaining_conflicts = resolve_conflicts(tt)
3455
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3457
# Yeah for resolved conflicts !
3458
self.assertLength(0, remaining_conflicts)
3459
# We have a new orphan
3460
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3461
self.assertEquals('bzr-orphans',
3462
tt.final_name(tt.final_parent(orphan_tid)))
3464
def test_never_orphan(self):
3465
wt = self.make_branch_and_tree('.')
3466
self._set_orphan_policy(wt, 'conflict')
3467
tt, orphan_tid = self._prepare_orphan(wt)
3468
remaining_conflicts = resolve_conflicts(tt)
3469
self.assertLength(1, remaining_conflicts)
3470
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3471
remaining_conflicts.pop())
3473
def test_orphan_error(self):
3474
def bogus_orphan(tt, orphan_id, parent_id):
3475
raise transform.OrphaningError(tt.final_name(orphan_id),
3476
tt.final_name(parent_id))
3477
transform.orphaning_registry.register('bogus', bogus_orphan,
3478
'Raise an error when orphaning')
3479
wt = self.make_branch_and_tree('.')
3480
self._set_orphan_policy(wt, 'bogus')
3481
tt, orphan_tid = self._prepare_orphan(wt)
3482
remaining_conflicts = resolve_conflicts(tt)
3483
self.assertLength(1, remaining_conflicts)
3484
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3485
remaining_conflicts.pop())
3487
def test_unknown_orphan_policy(self):
3488
wt = self.make_branch_and_tree('.')
3489
# Set a fictional policy nobody ever implemented
3490
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3491
tt, orphan_tid = self._prepare_orphan(wt)
3494
warnings.append(args[0] % args[1:])
3495
self.overrideAttr(trace, 'warning', warning)
3496
remaining_conflicts = resolve_conflicts(tt)
3497
# We fallback to the default policy which create a conflict
3498
self.assertLength(1, remaining_conflicts)
3499
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3500
remaining_conflicts.pop())
3501
self.assertLength(1, warnings)
3502
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')