1
# Copyright (C) 2006-2010 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
17
"""Tests for repository commit builder."""
29
revision as _mod_revision,
32
from bzrlib.graph import Graph
33
from bzrlib.tests.per_repository import test_repository
36
class TestCommitBuilder(test_repository.TestCaseWithRepository):
38
def test_get_commit_builder(self):
39
branch = self.make_branch('.')
40
branch.repository.lock_write()
41
builder = branch.repository.get_commit_builder(
42
branch, [], branch.get_config())
43
self.assertIsInstance(builder, repository.CommitBuilder)
44
self.assertTrue(builder.random_revid)
45
branch.repository.commit_write_group()
46
branch.repository.unlock()
48
def record_root(self, builder, tree):
49
if builder.record_root_entry is True:
52
ie = tree.inventory.root
55
parent_tree = tree.branch.repository.revision_tree(
56
_mod_revision.NULL_REVISION)
58
builder.record_entry_contents(ie, parent_invs, '', tree,
59
tree.path_content_summary(''))
61
def test_finish_inventory_with_record_root(self):
62
tree = self.make_branch_and_tree(".")
65
builder = tree.branch.get_commit_builder([])
66
repo = tree.branch.repository
67
self.record_root(builder, tree)
68
builder.finish_inventory()
69
repo.commit_write_group()
73
def test_finish_inventory_record_iter_changes(self):
74
tree = self.make_branch_and_tree(".")
77
builder = tree.branch.get_commit_builder([])
79
list(builder.record_iter_changes(tree, tree.last_revision(),
80
tree.iter_changes(tree.basis_tree())))
81
builder.finish_inventory()
85
repo = tree.branch.repository
86
repo.commit_write_group()
91
tree = self.make_branch_and_tree(".")
94
builder = tree.branch.get_commit_builder([])
95
self.record_root(builder, tree)
96
builder.finish_inventory()
101
def test_abort_record_iter_changes(self):
102
tree = self.make_branch_and_tree(".")
105
builder = tree.branch.get_commit_builder([])
107
basis = tree.basis_tree()
108
last_rev = tree.last_revision()
109
changes = tree.iter_changes(basis)
110
list(builder.record_iter_changes(tree, last_rev, changes))
111
builder.finish_inventory()
117
def test_commit_message(self):
118
tree = self.make_branch_and_tree(".")
121
builder = tree.branch.get_commit_builder([])
122
self.record_root(builder, tree)
123
builder.finish_inventory()
124
rev_id = builder.commit('foo bar blah')
127
rev = tree.branch.repository.get_revision(rev_id)
128
self.assertEqual('foo bar blah', rev.message)
130
def test_commit_with_revision_id(self):
131
tree = self.make_branch_and_tree(".")
134
# use a unicode revision id to test more corner cases.
135
# The repository layer is meant to handle this.
136
revision_id = u'\xc8abc'.encode('utf8')
139
builder = tree.branch.get_commit_builder([],
140
revision_id=revision_id)
141
except errors.NonAsciiRevisionId:
143
builder = tree.branch.get_commit_builder([],
144
revision_id=revision_id)
145
except errors.CannotSetRevisionId:
146
# This format doesn't support supplied revision ids
148
self.assertFalse(builder.random_revid)
149
self.record_root(builder, tree)
150
builder.finish_inventory()
151
self.assertEqual(revision_id, builder.commit('foo bar'))
154
self.assertTrue(tree.branch.repository.has_revision(revision_id))
155
# the revision id must be set on the inventory when saving it. This
156
# does not precisely test that - a repository that wants to can add it
157
# on deserialisation, but thats all the current contract guarantees
159
self.assertEqual(revision_id,
160
tree.branch.repository.get_inventory(revision_id).revision_id)
162
def test_commit_with_revision_id_record_iter_changes(self):
163
tree = self.make_branch_and_tree(".")
166
# use a unicode revision id to test more corner cases.
167
# The repository layer is meant to handle this.
168
revision_id = u'\xc8abc'.encode('utf8')
171
builder = tree.branch.get_commit_builder([],
172
revision_id=revision_id)
173
except errors.NonAsciiRevisionId:
175
builder = tree.branch.get_commit_builder([],
176
revision_id=revision_id)
177
except errors.CannotSetRevisionId:
178
# This format doesn't support supplied revision ids
180
self.assertFalse(builder.random_revid)
182
list(builder.record_iter_changes(tree, tree.last_revision(),
183
tree.iter_changes(tree.basis_tree())))
184
builder.finish_inventory()
188
self.assertEqual(revision_id, builder.commit('foo bar'))
191
self.assertTrue(tree.branch.repository.has_revision(revision_id))
192
# the revision id must be set on the inventory when saving it. This
193
# does not precisely test that - a repository that wants to can add it
194
# on deserialisation, but thats all the current contract guarantees
196
self.assertEqual(revision_id,
197
tree.branch.repository.get_inventory(revision_id).revision_id)
199
def test_commit_without_root_or_record_iter_changes_errors(self):
200
tree = self.make_branch_and_tree(".")
203
self.build_tree(['foo'])
204
tree.add('foo', 'foo-id')
205
entry = tree.inventory['foo-id']
206
builder = tree.branch.get_commit_builder([])
207
self.assertRaises(errors.RootMissing,
208
builder.record_entry_contents, entry, [], 'foo', tree,
209
tree.path_content_summary('foo'))
214
def test_commit_unchanged_root(self):
215
tree = self.make_branch_and_tree(".")
216
old_revision_id = tree.commit('')
218
parent_tree = tree.basis_tree()
219
parent_tree.lock_read()
220
self.addCleanup(parent_tree.unlock)
221
builder = tree.branch.get_commit_builder([old_revision_id])
223
ie = inventory.make_entry('directory', '', None,
225
delta, version_recorded, fs_hash = builder.record_entry_contents(
226
ie, [parent_tree.inventory], '', tree,
227
tree.path_content_summary(''))
228
# Regardless of repository root behaviour we should consider this a
230
self.assertFalse(builder.any_changes())
231
self.assertFalse(version_recorded)
232
# if the repository format recorded a new root revision, that
233
# should be in the delta
234
got_new_revision = ie.revision != old_revision_id
236
self.assertEqual(('', '', ie.file_id, ie), delta)
237
# The delta should be tracked
238
self.assertEqual(delta, builder._basis_delta[-1])
240
self.assertEqual(None, delta)
241
# Directories do not get hashed.
242
self.assertEqual(None, fs_hash)
251
def test_commit_unchanged_root_record_iter_changes(self):
252
tree = self.make_branch_and_tree(".")
253
old_revision_id = tree.commit('')
255
builder = tree.branch.get_commit_builder([old_revision_id])
257
list(builder.record_iter_changes(tree, old_revision_id, []))
258
# Regardless of repository root behaviour we should consider this a
260
self.assertFalse(builder.any_changes())
261
builder.finish_inventory()
262
new_root = tree.branch.repository.get_inventory(
263
builder._new_revision_id).root
264
if tree.branch.repository.supports_rich_root():
265
# We should not have seen a new root revision
266
self.assertEqual(old_revision_id, new_root.revision)
268
# We should see a new root revision
269
self.assertNotEqual(old_revision_id, new_root.revision)
274
def test_commit(self):
275
tree = self.make_branch_and_tree(".")
278
builder = tree.branch.get_commit_builder([])
279
self.record_root(builder, tree)
280
builder.finish_inventory()
281
rev_id = builder.commit('foo bar')
284
self.assertNotEqual(None, rev_id)
285
self.assertTrue(tree.branch.repository.has_revision(rev_id))
286
# the revision id must be set on the inventory when saving it. This does not
287
# precisely test that - a repository that wants to can add it on deserialisation,
288
# but thats all the current contract guarantees anyway.
289
self.assertEqual(rev_id, tree.branch.repository.get_inventory(rev_id).revision_id)
291
def test_get_basis_delta(self):
292
tree = self.make_branch_and_tree(".")
293
self.build_tree(["foo"])
294
tree.add(["foo"], ["foo-id"])
295
old_revision_id = tree.commit("added foo")
298
self.build_tree(['bar'])
299
tree.add(['bar'], ['bar-id'])
300
basis = tree.branch.repository.revision_tree(old_revision_id)
302
self.addCleanup(basis.unlock)
303
builder = tree.branch.get_commit_builder([old_revision_id])
306
parent_invs = [basis.inventory]
307
builder.will_record_deletes()
308
if builder.record_root_entry:
309
ie = basis.inventory.root.copy()
310
delta, _, _ = builder.record_entry_contents(ie, parent_invs,
311
'', tree, tree.path_content_summary(''))
312
if delta is not None:
313
total_delta.append(delta)
314
delta = builder.record_delete("foo", "foo-id")
315
total_delta.append(delta)
316
new_bar = inventory.make_entry('file', 'bar',
317
parent_id=tree.get_root_id(), file_id='bar-id')
318
delta, _, _ = builder.record_entry_contents(new_bar, parent_invs,
319
'bar', tree, tree.path_content_summary('bar'))
320
total_delta.append(delta)
321
# All actions should have been recorded in the basis_delta
322
self.assertEqual(total_delta, builder.get_basis_delta())
323
builder.finish_inventory()
324
builder.commit('delete foo, add bar')
326
tree.branch.repository.abort_write_group()
331
def test_get_basis_delta_without_notification(self):
332
tree = self.make_branch_and_tree(".")
333
old_revision_id = tree.commit('')
336
parent_tree = tree.basis_tree()
337
parent_tree.lock_read()
338
self.addCleanup(parent_tree.unlock)
339
builder = tree.branch.get_commit_builder([old_revision_id])
340
# It is an error to expect builder.get_basis_delta() to be correct,
341
# if you have not also called will_record_deletes() to indicate you
342
# will be calling record_delete() when appropriate
343
self.assertRaises(AssertionError, builder.get_basis_delta)
344
tree.branch.repository.abort_write_group()
348
def test_record_delete(self):
349
tree = self.make_branch_and_tree(".")
350
self.build_tree(["foo"])
351
tree.add(["foo"], ["foo-id"])
352
rev_id = tree.commit("added foo")
353
# Remove the inventory details for foo-id, because
354
# record_entry_contents ends up copying root verbatim.
355
tree.unversion(["foo-id"])
358
basis = tree.branch.repository.revision_tree(rev_id)
359
builder = tree.branch.get_commit_builder([rev_id])
361
builder.will_record_deletes()
362
if builder.record_root_entry is True:
363
parent_invs = [basis.inventory]
364
del basis.inventory.root.children['foo']
365
builder.record_entry_contents(basis.inventory.root,
366
parent_invs, '', tree, tree.path_content_summary(''))
367
# the delta should be returned, and recorded in _basis_delta
368
delta = builder.record_delete("foo", "foo-id")
369
self.assertEqual(("foo", None, "foo-id", None), delta)
370
self.assertEqual(delta, builder._basis_delta[-1])
371
builder.finish_inventory()
372
rev_id2 = builder.commit('delete foo')
374
tree.branch.repository.abort_write_group()
378
rev_tree = builder.revision_tree()
380
self.addCleanup(rev_tree.unlock)
381
self.assertFalse(rev_tree.path2id('foo'))
383
def test_record_delete_record_iter_changes(self):
384
tree = self.make_branch_and_tree(".")
385
self.build_tree(["foo"])
386
tree.add(["foo"], ["foo-id"])
387
rev_id = tree.commit("added foo")
390
builder = tree.branch.get_commit_builder([rev_id])
392
delete_change = ('foo-id', ('foo', None), True, (True, False),
393
(tree.path2id(''), None), ('foo', None), ('file', None),
395
list(builder.record_iter_changes(tree, rev_id,
397
self.assertEqual(("foo", None, "foo-id", None),
398
builder._basis_delta[0])
399
self.assertTrue(builder.any_changes())
400
builder.finish_inventory()
401
rev_id2 = builder.commit('delete foo')
407
rev_tree = builder.revision_tree()
409
self.addCleanup(rev_tree.unlock)
410
self.assertFalse(rev_tree.path2id('foo'))
412
def test_record_delete_without_notification(self):
413
tree = self.make_branch_and_tree(".")
414
self.build_tree(["foo"])
415
tree.add(["foo"], ["foo-id"])
416
rev_id = tree.commit("added foo")
419
builder = tree.branch.get_commit_builder([rev_id])
421
self.record_root(builder, tree)
422
self.assertRaises(AssertionError,
423
builder.record_delete, "foo", "foo-id")
425
tree.branch.repository.abort_write_group()
429
def test_revision_tree(self):
430
tree = self.make_branch_and_tree(".")
433
builder = tree.branch.get_commit_builder([])
434
self.record_root(builder, tree)
435
builder.finish_inventory()
436
rev_id = builder.commit('foo bar')
439
rev_tree = builder.revision_tree()
440
# Just a couple simple tests to ensure that it actually follows
441
# the RevisionTree api.
442
self.assertEqual(rev_id, rev_tree.get_revision_id())
443
self.assertEqual([], rev_tree.get_parent_ids())
445
def test_revision_tree_record_iter_changes(self):
446
tree = self.make_branch_and_tree(".")
449
builder = tree.branch.get_commit_builder([])
451
list(builder.record_iter_changes(tree,
452
_mod_revision.NULL_REVISION,
453
tree.iter_changes(tree.basis_tree())))
454
builder.finish_inventory()
455
rev_id = builder.commit('foo bar')
459
rev_tree = builder.revision_tree()
460
# Just a couple simple tests to ensure that it actually follows
461
# the RevisionTree api.
462
self.assertEqual(rev_id, rev_tree.get_revision_id())
463
self.assertEqual([], rev_tree.get_parent_ids())
467
def test_root_entry_has_revision(self):
468
# test the root revision created and put in the basis
469
# has the right rev id.
470
# XXX: RBC 20081118 - this test is too big, it depends on the exact
471
# behaviour of tree methods and so on; it should be written to the
472
# commit builder interface directly.
473
tree = self.make_branch_and_tree('.')
474
rev_id = tree.commit('message')
475
basis_tree = tree.basis_tree()
476
basis_tree.lock_read()
477
self.addCleanup(basis_tree.unlock)
478
self.assertEqual(rev_id, basis_tree.inventory.root.revision)
480
def _get_revtrees(self, tree, revision_ids):
483
trees = list(tree.branch.repository.revision_trees(revision_ids))
486
self.addCleanup(_tree.unlock)
491
def test_last_modified_revision_after_commit_root_unchanged(self):
492
# commiting without changing the root does not change the
493
# last modified except on non-rich-root-repositories.
494
tree = self.make_branch_and_tree('.')
495
rev1 = tree.commit('')
496
rev2 = tree.commit('')
497
tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
498
self.assertEqual(rev1, tree1.inventory.root.revision)
499
if tree.branch.repository.supports_rich_root():
500
self.assertEqual(rev1, tree2.inventory.root.revision)
502
self.assertEqual(rev2, tree2.inventory.root.revision)
504
def _add_commit_check_unchanged(self, tree, name, mini_commit=None):
505
tree.add([name], [name + 'id'])
506
self._commit_check_unchanged(tree, name, name + 'id',
507
mini_commit=mini_commit)
509
def _commit_check_unchanged(self, tree, name, file_id, mini_commit=None):
510
rev1 = tree.commit('')
511
if mini_commit is None:
512
mini_commit = self.mini_commit
513
rev2 = mini_commit(tree, name, name, False, False)
514
tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
515
self.assertEqual(rev1, tree1.inventory[file_id].revision)
516
self.assertEqual(rev1, tree2.inventory[file_id].revision)
518
expected_graph[(file_id, rev1)] = ()
519
self.assertFileGraph(expected_graph, tree, (file_id, rev1))
521
def test_last_modified_revision_after_commit_dir_unchanged(self):
522
# committing without changing a dir does not change the last modified.
523
tree = self.make_branch_and_tree('.')
524
self.build_tree(['dir/'])
525
self._add_commit_check_unchanged(tree, 'dir')
527
def test_last_modified_revision_after_commit_dir_unchanged_ric(self):
528
# committing without changing a dir does not change the last modified.
529
tree = self.make_branch_and_tree('.')
530
self.build_tree(['dir/'])
531
self._add_commit_check_unchanged(tree, 'dir',
532
mini_commit=self.mini_commit_record_iter_changes)
534
def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
535
# committing without changing a dir does not change the last modified
536
# of the dir even the dirs contents are changed.
537
tree = self.make_branch_and_tree('.')
538
self.build_tree(['dir/'])
539
tree.add(['dir'], ['dirid'])
540
rev1 = tree.commit('')
541
self.build_tree(['dir/content'])
542
tree.add(['dir/content'], ['contentid'])
543
rev2 = tree.commit('')
544
tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
545
self.assertEqual(rev1, tree1.inventory['dirid'].revision)
546
self.assertEqual(rev1, tree2.inventory['dirid'].revision)
549
expected_graph[(file_id, rev1)] = ()
550
self.assertFileGraph(expected_graph, tree, (file_id, rev1))
552
def test_last_modified_revision_after_commit_file_unchanged(self):
553
# committing without changing a file does not change the last modified.
554
tree = self.make_branch_and_tree('.')
555
self.build_tree(['file'])
556
self._add_commit_check_unchanged(tree, 'file')
558
def test_last_modified_revision_after_commit_file_unchanged_ric(self):
559
# committing without changing a file does not change the last modified.
560
tree = self.make_branch_and_tree('.')
561
self.build_tree(['file'])
562
self._add_commit_check_unchanged(tree, 'file',
563
mini_commit=self.mini_commit_record_iter_changes)
565
def test_last_modified_revision_after_commit_link_unchanged(self):
566
# committing without changing a link does not change the last modified.
567
self.requireFeature(tests.SymlinkFeature)
568
tree = self.make_branch_and_tree('.')
569
os.symlink('target', 'link')
570
self._add_commit_check_unchanged(tree, 'link')
572
def test_last_modified_revision_after_commit_link_unchanged_ric(self):
573
# committing without changing a link does not change the last modified.
574
self.requireFeature(tests.SymlinkFeature)
575
tree = self.make_branch_and_tree('.')
576
os.symlink('target', 'link')
577
self._add_commit_check_unchanged(tree, 'link',
578
mini_commit=self.mini_commit_record_iter_changes)
580
def test_last_modified_revision_after_commit_reference_unchanged(self):
581
# committing without changing a subtree does not change the last
583
tree = self.make_branch_and_tree('.')
584
subtree = self.make_reference('reference')
586
tree.add_reference(subtree)
587
self._commit_check_unchanged(tree, 'reference',
588
subtree.get_root_id())
589
except errors.UnsupportedOperation:
592
def test_last_modified_revision_after_commit_reference_unchanged_ric(self):
593
# committing without changing a subtree does not change the last
595
tree = self.make_branch_and_tree('.')
596
subtree = self.make_reference('reference')
598
tree.add_reference(subtree)
599
self._commit_check_unchanged(tree, 'reference',
600
subtree.get_root_id(),
601
mini_commit=self.mini_commit_record_iter_changes)
602
except errors.UnsupportedOperation:
605
def _add_commit_renamed_check_changed(self, tree, name,
606
expect_fs_hash=False, mini_commit=None):
608
tree.rename_one(name, 'new_' + name)
609
self._add_commit_change_check_changed(tree, name, rename,
610
expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
612
def _commit_renamed_check_changed(self, tree, name, file_id,
613
expect_fs_hash=False, mini_commit=None):
615
tree.rename_one(name, 'new_' + name)
616
self._commit_change_check_changed(tree, name, file_id, rename,
617
expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
619
def test_last_modified_revision_after_rename_dir_changes(self):
620
# renaming a dir changes the last modified.
621
tree = self.make_branch_and_tree('.')
622
self.build_tree(['dir/'])
623
self._add_commit_renamed_check_changed(tree, 'dir')
625
def test_last_modified_revision_after_rename_dir_changes_ric(self):
626
# renaming a dir changes the last modified.
627
tree = self.make_branch_and_tree('.')
628
self.build_tree(['dir/'])
629
self._add_commit_renamed_check_changed(tree, 'dir',
630
mini_commit=self.mini_commit_record_iter_changes)
632
def test_last_modified_revision_after_rename_file_changes(self):
633
# renaming a file changes the last modified.
634
tree = self.make_branch_and_tree('.')
635
self.build_tree(['file'])
636
self._add_commit_renamed_check_changed(tree, 'file',
639
def test_last_modified_revision_after_rename_file_changes_ric(self):
640
# renaming a file changes the last modified.
641
tree = self.make_branch_and_tree('.')
642
self.build_tree(['file'])
643
self._add_commit_renamed_check_changed(tree, 'file',
645
mini_commit=self.mini_commit_record_iter_changes)
647
def test_last_modified_revision_after_rename_link_changes(self):
648
# renaming a link changes the last modified.
649
self.requireFeature(tests.SymlinkFeature)
650
tree = self.make_branch_and_tree('.')
651
os.symlink('target', 'link')
652
self._add_commit_renamed_check_changed(tree, 'link')
654
def test_last_modified_revision_after_rename_link_changes_ric(self):
655
# renaming a link changes the last modified.
656
self.requireFeature(tests.SymlinkFeature)
657
tree = self.make_branch_and_tree('.')
658
os.symlink('target', 'link')
659
self._add_commit_renamed_check_changed(tree, 'link',
660
mini_commit=self.mini_commit_record_iter_changes)
662
def test_last_modified_revision_after_rename_ref_changes(self):
663
# renaming a reference changes the last modified.
664
tree = self.make_branch_and_tree('.')
665
subtree = self.make_reference('reference')
667
tree.add_reference(subtree)
668
self._commit_renamed_check_changed(tree, 'reference',
669
subtree.get_root_id())
670
except errors.UnsupportedOperation:
673
def test_last_modified_revision_after_rename_ref_changes_ric(self):
674
# renaming a reference changes the last modified.
675
tree = self.make_branch_and_tree('.')
676
subtree = self.make_reference('reference')
678
tree.add_reference(subtree)
679
self._commit_renamed_check_changed(tree, 'reference',
680
subtree.get_root_id(),
681
mini_commit=self.mini_commit_record_iter_changes)
682
except errors.UnsupportedOperation:
685
def _add_commit_reparent_check_changed(self, tree, name,
686
expect_fs_hash=False, mini_commit=None):
687
self.build_tree(['newparent/'])
688
tree.add(['newparent'])
690
tree.rename_one(name, 'newparent/new_' + name)
691
self._add_commit_change_check_changed(tree, name, reparent,
692
expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
694
def test_last_modified_revision_after_reparent_dir_changes(self):
695
# reparenting a dir changes the last modified.
696
tree = self.make_branch_and_tree('.')
697
self.build_tree(['dir/'])
698
self._add_commit_reparent_check_changed(tree, 'dir')
700
def test_last_modified_revision_after_reparent_dir_changes_ric(self):
701
# reparenting a dir changes the last modified.
702
tree = self.make_branch_and_tree('.')
703
self.build_tree(['dir/'])
704
self._add_commit_reparent_check_changed(tree, 'dir',
705
mini_commit=self.mini_commit_record_iter_changes)
707
def test_last_modified_revision_after_reparent_file_changes(self):
708
# reparenting a file changes the last modified.
709
tree = self.make_branch_and_tree('.')
710
self.build_tree(['file'])
711
self._add_commit_reparent_check_changed(tree, 'file',
714
def test_last_modified_revision_after_reparent_file_changes_ric(self):
715
# reparenting a file changes the last modified.
716
tree = self.make_branch_and_tree('.')
717
self.build_tree(['file'])
718
self._add_commit_reparent_check_changed(tree, 'file',
720
mini_commit=self.mini_commit_record_iter_changes)
722
def test_last_modified_revision_after_reparent_link_changes(self):
723
# reparenting a link changes the last modified.
724
self.requireFeature(tests.SymlinkFeature)
725
tree = self.make_branch_and_tree('.')
726
os.symlink('target', 'link')
727
self._add_commit_reparent_check_changed(tree, 'link')
729
def test_last_modified_revision_after_reparent_link_changes_ric(self):
730
# reparenting a link changes the last modified.
731
self.requireFeature(tests.SymlinkFeature)
732
tree = self.make_branch_and_tree('.')
733
os.symlink('target', 'link')
734
self._add_commit_reparent_check_changed(tree, 'link',
735
mini_commit=self.mini_commit_record_iter_changes)
737
def _add_commit_change_check_changed(self, tree, name, changer,
738
expect_fs_hash=False, mini_commit=None, file_id=None):
740
file_id = name + 'id'
741
tree.add([name], [file_id])
742
self._commit_change_check_changed(
744
changer, expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
746
def _commit_change_check_changed(self, tree, name, file_id, changer,
747
expect_fs_hash=False, mini_commit=None):
748
rev1 = tree.commit('')
750
if mini_commit is None:
751
mini_commit = self.mini_commit
752
rev2 = mini_commit(tree, name, tree.id2path(file_id),
753
expect_fs_hash=expect_fs_hash)
754
tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
755
self.assertEqual(rev1, tree1.inventory[file_id].revision)
756
self.assertEqual(rev2, tree2.inventory[file_id].revision)
758
expected_graph[(file_id, rev1)] = ()
759
expected_graph[(file_id, rev2)] = ((file_id, rev1),)
760
self.assertFileGraph(expected_graph, tree, (file_id, rev2))
762
def mini_commit(self, tree, name, new_name, records_version=True,
763
delta_against_basis=True, expect_fs_hash=False):
764
"""Perform a miniature commit looking for record entry results.
766
:param tree: The tree to commit.
767
:param name: The path in the basis tree of the tree being committed.
768
:param new_name: The path in the tree being committed.
769
:param records_version: True if the commit of new_name is expected to
770
record a new version.
771
:param delta_against_basis: True of the commit of new_name is expected
772
to have a delta against the basis.
773
:param expect_fs_hash: True or false to indicate whether we expect a
774
file hash to be returned from the record_entry_contents call.
778
# mini manual commit here so we can check the return of
779
# record_entry_contents.
780
parent_ids = tree.get_parent_ids()
781
builder = tree.branch.get_commit_builder(parent_ids)
782
parent_tree = tree.basis_tree()
783
parent_tree.lock_read()
784
self.addCleanup(parent_tree.unlock)
785
parent_invs = [parent_tree.inventory]
786
for parent_id in parent_ids[1:]:
787
parent_invs.append(tree.branch.repository.revision_tree(
788
parent_id).inventory)
790
builder.record_entry_contents(
791
inventory.make_entry('directory', '', None,
792
tree.get_root_id()), parent_invs, '', tree,
793
tree.path_content_summary(''))
794
def commit_id(file_id):
795
old_ie = tree.inventory[file_id]
796
path = tree.id2path(file_id)
797
ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
798
old_ie.parent_id, file_id)
799
content_summary = tree.path_content_summary(path)
800
if content_summary[0] == 'tree-reference':
801
content_summary = content_summary[:3] + (
802
tree.get_reference_revision(file_id),)
803
return builder.record_entry_contents(ie, parent_invs, path,
804
tree, content_summary)
806
file_id = tree.path2id(new_name)
807
parent_id = tree.inventory[file_id].parent_id
808
if parent_id != tree.get_root_id():
810
# because a change of some sort is meant to have occurred,
811
# recording the entry must return True.
812
delta, version_recorded, fs_hash = commit_id(file_id)
814
self.assertTrue(version_recorded)
816
self.assertFalse(version_recorded)
818
tree_file_stat = tree.get_file_with_stat(file_id)
819
tree_file_stat[0].close()
820
self.assertEqual(2, len(fs_hash))
821
self.assertEqual(tree.get_file_sha1(file_id), fs_hash[0])
822
self.assertEqualStat(tree_file_stat[1], fs_hash[1])
824
self.assertEqual(None, fs_hash)
825
new_entry = builder.new_inventory[file_id]
826
if delta_against_basis:
827
expected_delta = (name, new_name, file_id, new_entry)
828
# The delta should be recorded
829
self.assertEqual(expected_delta, builder._basis_delta[-1])
831
expected_delta = None
832
self.assertEqual(expected_delta, delta)
833
builder.finish_inventory()
834
rev2 = builder.commit('')
840
tree.set_parent_ids([rev2])
845
def mini_commit_record_iter_changes(self, tree, name, new_name,
846
records_version=True, delta_against_basis=True, expect_fs_hash=False):
847
"""Perform a miniature commit looking for record entry results.
849
This version uses the record_iter_changes interface.
851
:param tree: The tree to commit.
852
:param name: The path in the basis tree of the tree being committed.
853
:param new_name: The path in the tree being committed.
854
:param records_version: True if the commit of new_name is expected to
855
record a new version.
856
:param delta_against_basis: True of the commit of new_name is expected
857
to have a delta against the basis.
858
:param expect_fs_hash: If true, looks for a fs hash output from
863
# mini manual commit here so we can check the return of
864
# record_entry_contents.
865
parent_ids = tree.get_parent_ids()
866
builder = tree.branch.get_commit_builder(parent_ids)
867
parent_tree = tree.basis_tree()
868
parent_tree.lock_read()
869
self.addCleanup(parent_tree.unlock)
870
parent_invs = [parent_tree.inventory]
871
for parent_id in parent_ids[1:]:
872
parent_invs.append(tree.branch.repository.revision_tree(
873
parent_id).inventory)
874
changes = list(tree.iter_changes(parent_tree))
875
result = list(builder.record_iter_changes(tree, parent_ids[0],
877
file_id = tree.path2id(new_name)
879
tree_file_stat = tree.get_file_with_stat(file_id)
880
tree_file_stat[0].close()
881
self.assertLength(1, result)
883
self.assertEqual(result[:2], (file_id, new_name))
884
self.assertEqual(result[2][0], tree.get_file_sha1(file_id))
885
self.assertEqualStat(result[2][1], tree_file_stat[1])
887
self.assertEqual([], result)
888
delta = builder._basis_delta
889
delta_dict = dict((change[2], change) for change in delta)
890
version_recorded = (file_id in delta_dict and
891
delta_dict[file_id][3] is not None and
892
delta_dict[file_id][3].revision == builder._new_revision_id)
894
self.assertTrue(version_recorded)
896
self.assertFalse(version_recorded)
897
self.assertIs(None, builder.new_inventory)
898
builder.finish_inventory()
899
inv_key = (builder._new_revision_id,)
900
inv_sha1 = tree.branch.repository.inventories.get_sha1s(
902
self.assertEqual(inv_sha1, builder.inv_sha1)
903
self.assertIs(None, builder.new_inventory)
904
new_inventory = builder.revision_tree().inventory
905
new_entry = new_inventory[file_id]
906
if delta_against_basis:
907
expected_delta = (name, new_name, file_id, new_entry)
908
self.assertEqual(expected_delta, delta_dict[file_id])
910
expected_delta = None
911
self.assertFalse(version_recorded)
912
rev2 = builder.commit('')
913
tree.set_parent_ids([rev2])
922
def assertFileGraph(self, expected_graph, tree, tip):
923
# all the changes that have occured should be in the ancestry
924
# (closest to a public per-file graph API we have today)
926
self.addCleanup(tree.unlock)
927
graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
928
self.assertEqual(expected_graph, graph)
930
def test_last_modified_revision_after_content_file_changes(self):
931
# altering a file changes the last modified.
932
tree = self.make_branch_and_tree('.')
933
self.build_tree(['file'])
935
tree.put_file_bytes_non_atomic('fileid', 'new content')
936
self._add_commit_change_check_changed(tree, 'file', change_file,
939
def test_last_modified_revision_after_content_file_changes_ric(self):
940
# altering a file changes the last modified.
941
tree = self.make_branch_and_tree('.')
942
self.build_tree(['file'])
944
tree.put_file_bytes_non_atomic('fileid', 'new content')
945
self._add_commit_change_check_changed(tree, 'file', change_file,
947
mini_commit=self.mini_commit_record_iter_changes)
949
def test_last_modified_revision_after_content_link_changes(self):
950
# changing a link changes the last modified.
951
self.requireFeature(tests.SymlinkFeature)
952
tree = self.make_branch_and_tree('.')
953
os.symlink('target', 'link')
956
os.symlink('newtarget', 'link')
957
self._add_commit_change_check_changed(tree, 'link', change_link)
959
def _test_last_mod_rev_after_content_link_changes_ric(
960
self, link, target, newtarget, file_id=None):
963
# changing a link changes the last modified.
964
self.requireFeature(tests.SymlinkFeature)
965
tree = self.make_branch_and_tree('.')
966
os.symlink(target, link)
969
os.symlink(newtarget, link)
970
self._add_commit_change_check_changed(
971
tree, link, change_link,
972
mini_commit=self.mini_commit_record_iter_changes,
975
def test_last_modified_rev_after_content_link_changes_ric(self):
976
self._test_last_mod_rev_after_content_link_changes_ric(
977
'link', 'target', 'newtarget')
979
def test_last_modified_rev_after_content_unicode_link_changes_ric(self):
980
self.requireFeature(tests.UnicodeFilenameFeature)
981
self._test_last_mod_rev_after_content_link_changes_ric(
982
u'li\u1234nk', u'targ\N{Euro Sign}t', u'n\N{Euro Sign}wtarget',
984
file_id=u'li\u1234nk'.encode('UTF-8'))
986
def _commit_sprout(self, tree, name):
987
tree.add([name], [name + 'id'])
988
rev_id = tree.commit('')
989
return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
991
def _rename_in_tree(self, tree, name):
992
tree.rename_one(name, 'new_' + name)
993
return tree.commit('')
995
def _commit_sprout_rename_merge(self, tree1, name, expect_fs_hash=False,
997
"""Do a rename in both trees."""
998
rev1, tree2 = self._commit_sprout(tree1, name)
999
# change both sides equally
1000
rev2 = self._rename_in_tree(tree1, name)
1001
rev3 = self._rename_in_tree(tree2, name)
1002
tree1.merge_from_branch(tree2.branch)
1003
if mini_commit is None:
1004
mini_commit = self.mini_commit
1005
rev4 = mini_commit(tree1, 'new_' + name, 'new_' + name,
1006
expect_fs_hash=expect_fs_hash)
1007
tree3, = self._get_revtrees(tree1, [rev4])
1008
self.assertEqual(rev4, tree3.inventory[name + 'id'].revision)
1009
file_id = name + 'id'
1011
expected_graph[(file_id, rev1)] = ()
1012
expected_graph[(file_id, rev2)] = ((file_id, rev1),)
1013
expected_graph[(file_id, rev3)] = ((file_id, rev1),)
1014
expected_graph[(file_id, rev4)] = ((file_id, rev2), (file_id, rev3),)
1015
self.assertFileGraph(expected_graph, tree1, (file_id, rev4))
1017
def test_last_modified_revision_after_merge_dir_changes(self):
1018
# merge a dir changes the last modified.
1019
tree1 = self.make_branch_and_tree('t1')
1020
self.build_tree(['t1/dir/'])
1021
self._commit_sprout_rename_merge(tree1, 'dir')
1023
def test_last_modified_revision_after_merge_dir_changes_ric(self):
1024
# merge a dir changes the last modified.
1025
tree1 = self.make_branch_and_tree('t1')
1026
self.build_tree(['t1/dir/'])
1027
self._commit_sprout_rename_merge(tree1, 'dir',
1028
mini_commit=self.mini_commit_record_iter_changes)
1030
def test_last_modified_revision_after_merge_file_changes(self):
1031
# merge a file changes the last modified.
1032
tree1 = self.make_branch_and_tree('t1')
1033
self.build_tree(['t1/file'])
1034
self._commit_sprout_rename_merge(tree1, 'file', expect_fs_hash=True)
1036
def test_last_modified_revision_after_merge_file_changes_ric(self):
1037
# merge a file changes the last modified.
1038
tree1 = self.make_branch_and_tree('t1')
1039
self.build_tree(['t1/file'])
1040
self._commit_sprout_rename_merge(tree1, 'file', expect_fs_hash=True,
1041
mini_commit=self.mini_commit_record_iter_changes)
1043
def test_last_modified_revision_after_merge_link_changes(self):
1044
# merge a link changes the last modified.
1045
self.requireFeature(tests.SymlinkFeature)
1046
tree1 = self.make_branch_and_tree('t1')
1047
os.symlink('target', 't1/link')
1048
self._commit_sprout_rename_merge(tree1, 'link')
1050
def test_last_modified_revision_after_merge_link_changes_ric(self):
1051
# merge a link changes the last modified.
1052
self.requireFeature(tests.SymlinkFeature)
1053
tree1 = self.make_branch_and_tree('t1')
1054
os.symlink('target', 't1/link')
1055
self._commit_sprout_rename_merge(tree1, 'link',
1056
mini_commit=self.mini_commit_record_iter_changes)
1058
def _commit_sprout_rename_merge_converged(self, tree1, name,
1060
# Make a merge which just incorporates a change from a branch:
1061
# The per-file graph is straight line, and no alteration occurs
1063
# Part 1: change in the merged branch.
1064
rev1, tree2 = self._commit_sprout(tree1, name)
1065
# change on the other side to merge back
1066
rev2 = self._rename_in_tree(tree2, name)
1067
tree1.merge_from_branch(tree2.branch)
1068
if mini_commit is None:
1069
mini_commit = self.mini_commit
1070
def _check_graph(in_tree, changed_in_tree):
1071
rev3 = mini_commit(in_tree, name, 'new_' + name, False,
1072
delta_against_basis=changed_in_tree)
1073
tree3, = self._get_revtrees(in_tree, [rev2])
1074
self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
1075
file_id = name + 'id'
1077
expected_graph[(file_id, rev1)] = ()
1078
expected_graph[(file_id, rev2)] = ((file_id, rev1),)
1079
self.assertFileGraph(expected_graph, in_tree, (file_id, rev2))
1080
_check_graph(tree1, True)
1081
# Part 2: change in the merged into branch - we use tree2 that has a
1082
# change to name, branch tree1 and give it an unrelated change, then
1084
other_tree = tree1.bzrdir.sprout('t3').open_workingtree()
1085
other_rev = other_tree.commit('')
1086
tree2.merge_from_branch(other_tree.branch)
1087
_check_graph(tree2, False)
1089
def _commit_sprout_make_merge(self, tree1, make, mini_commit=None):
1090
# Make a merge which incorporates the addition of a new object to
1091
# another branch. The per-file graph shows no additional change
1092
# in the merge because its a straight line.
1093
rev1 = tree1.commit('')
1094
tree2 = tree1.bzrdir.sprout('t2').open_workingtree()
1095
# make and commit on the other side to merge back
1098
tree2.add(['name'], [file_id])
1099
rev2 = tree2.commit('')
1100
tree1.merge_from_branch(tree2.branch)
1101
if mini_commit is None:
1102
mini_commit = self.mini_commit
1103
rev3 = mini_commit(tree1, None, 'name', False)
1104
tree3, = self._get_revtrees(tree1, [rev2])
1105
# in rev2, name should be only changed in rev2
1106
self.assertEqual(rev2, tree3.inventory[file_id].revision)
1108
expected_graph[(file_id, rev2)] = ()
1109
self.assertFileGraph(expected_graph, tree1, (file_id, rev2))
1111
def test_last_modified_revision_after_converged_merge_dir_unchanged(self):
1112
# merge a dir that changed preserves the last modified.
1113
tree1 = self.make_branch_and_tree('t1')
1114
self.build_tree(['t1/dir/'])
1115
self._commit_sprout_rename_merge_converged(tree1, 'dir')
1117
def test_last_modified_revision_after_converged_merge_dir_unchanged_ric(self):
1118
# merge a dir that changed preserves the last modified.
1119
tree1 = self.make_branch_and_tree('t1')
1120
self.build_tree(['t1/dir/'])
1121
self._commit_sprout_rename_merge_converged(tree1, 'dir',
1122
mini_commit=self.mini_commit_record_iter_changes)
1124
def test_last_modified_revision_after_converged_merge_file_unchanged(self):
1125
# merge a file that changed preserves the last modified.
1126
tree1 = self.make_branch_and_tree('t1')
1127
self.build_tree(['t1/file'])
1128
self._commit_sprout_rename_merge_converged(tree1, 'file')
1130
def test_last_modified_revision_after_converged_merge_file_unchanged_ric(self):
1131
# merge a file that changed preserves the last modified.
1132
tree1 = self.make_branch_and_tree('t1')
1133
self.build_tree(['t1/file'])
1134
self._commit_sprout_rename_merge_converged(tree1, 'file',
1135
mini_commit=self.mini_commit_record_iter_changes)
1137
def test_last_modified_revision_after_converged_merge_link_unchanged(self):
1138
# merge a link that changed preserves the last modified.
1139
self.requireFeature(tests.SymlinkFeature)
1140
tree1 = self.make_branch_and_tree('t1')
1141
os.symlink('target', 't1/link')
1142
self._commit_sprout_rename_merge_converged(tree1, 'link')
1144
def test_last_modified_revision_after_converged_merge_link_unchanged_ric(self):
1145
# merge a link that changed preserves the last modified.
1146
self.requireFeature(tests.SymlinkFeature)
1147
tree1 = self.make_branch_and_tree('t1')
1148
os.symlink('target', 't1/link')
1149
self._commit_sprout_rename_merge_converged(tree1, 'link',
1150
mini_commit=self.mini_commit_record_iter_changes)
1152
def test_last_modified_revision_after_merge_new_dir_unchanged(self):
1153
# merge a new dir does not change the last modified.
1154
tree1 = self.make_branch_and_tree('t1')
1155
self._commit_sprout_make_merge(tree1, self.make_dir)
1157
def test_last_modified_revision_after_merge_new_dir_unchanged_ric(self):
1158
# merge a new dir does not change the last modified.
1159
tree1 = self.make_branch_and_tree('t1')
1160
self._commit_sprout_make_merge(tree1, self.make_dir,
1161
mini_commit=self.mini_commit_record_iter_changes)
1163
def test_last_modified_revision_after_merge_new_file_unchanged(self):
1164
# merge a new file does not change the last modified.
1165
tree1 = self.make_branch_and_tree('t1')
1166
self._commit_sprout_make_merge(tree1, self.make_file)
1168
def test_last_modified_revision_after_merge_new_file_unchanged_ric(self):
1169
# merge a new file does not change the last modified.
1170
tree1 = self.make_branch_and_tree('t1')
1171
self._commit_sprout_make_merge(tree1, self.make_file,
1172
mini_commit=self.mini_commit_record_iter_changes)
1174
def test_last_modified_revision_after_merge_new_link_unchanged(self):
1175
# merge a new link does not change the last modified.
1176
tree1 = self.make_branch_and_tree('t1')
1177
self._commit_sprout_make_merge(tree1, self.make_link)
1179
def test_last_modified_revision_after_merge_new_link_unchanged_ric(self):
1180
# merge a new link does not change the last modified.
1181
tree1 = self.make_branch_and_tree('t1')
1182
self._commit_sprout_make_merge(tree1, self.make_link,
1183
mini_commit=self.mini_commit_record_iter_changes)
1185
def make_dir(self, name):
1186
self.build_tree([name + '/'])
1188
def make_file(self, name):
1189
self.build_tree([name])
1191
def make_link(self, name):
1192
self.requireFeature(tests.SymlinkFeature)
1193
os.symlink('target', name)
1195
def make_reference(self, name):
1196
tree = self.make_branch_and_tree(name, format='1.9-rich-root')
1200
def _check_kind_change(self, make_before, make_after, expect_fs_hash=False,
1202
tree = self.make_branch_and_tree('.')
1207
osutils.delete_any(path)
1210
self._add_commit_change_check_changed(tree, path, change_kind,
1211
expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
1213
def test_last_modified_dir_file(self):
1214
self._check_kind_change(self.make_dir, self.make_file,
1215
expect_fs_hash=True)
1217
def test_last_modified_dir_file_ric(self):
1218
self._check_kind_change(self.make_dir, self.make_file,
1219
expect_fs_hash=True,
1220
mini_commit=self.mini_commit_record_iter_changes)
1222
def test_last_modified_dir_link(self):
1223
self._check_kind_change(self.make_dir, self.make_link)
1225
def test_last_modified_dir_link_ric(self):
1226
self._check_kind_change(self.make_dir, self.make_link,
1227
mini_commit=self.mini_commit_record_iter_changes)
1229
def test_last_modified_link_file(self):
1230
self._check_kind_change(self.make_link, self.make_file,
1231
expect_fs_hash=True)
1233
def test_last_modified_link_file_ric(self):
1234
self._check_kind_change(self.make_link, self.make_file,
1235
expect_fs_hash=True,
1236
mini_commit=self.mini_commit_record_iter_changes)
1238
def test_last_modified_link_dir(self):
1239
self._check_kind_change(self.make_link, self.make_dir)
1241
def test_last_modified_link_dir_ric(self):
1242
self._check_kind_change(self.make_link, self.make_dir,
1243
mini_commit=self.mini_commit_record_iter_changes)
1245
def test_last_modified_file_dir(self):
1246
self._check_kind_change(self.make_file, self.make_dir)
1248
def test_last_modified_file_dir_ric(self):
1249
self._check_kind_change(self.make_file, self.make_dir,
1250
mini_commit=self.mini_commit_record_iter_changes)
1252
def test_last_modified_file_link(self):
1253
self._check_kind_change(self.make_file, self.make_link)
1255
def test_last_modified_file_link_ric(self):
1256
self._check_kind_change(self.make_file, self.make_link,
1257
mini_commit=self.mini_commit_record_iter_changes)
1259
def test_get_commit_builder_with_invalid_revprops(self):
1260
branch = self.make_branch('.')
1261
branch.repository.lock_write()
1262
self.addCleanup(branch.repository.unlock)
1263
self.assertRaises(ValueError, branch.repository.get_commit_builder,
1264
branch, [], branch.get_config(),
1265
revprops={'invalid': u'property\rwith\r\ninvalid chars'})
1267
def test_commit_builder_commit_with_invalid_message(self):
1268
branch = self.make_branch('.')
1269
branch.repository.lock_write()
1270
self.addCleanup(branch.repository.unlock)
1271
builder = branch.repository.get_commit_builder(branch, [],
1272
branch.get_config())
1273
self.addCleanup(branch.repository.abort_write_group)
1274
self.assertRaises(ValueError, builder.commit,
1275
u'Invalid\r\ncommit message\r\n')
1277
def test_stacked_repositories_reject_commit_builder(self):
1278
# As per bug 375013, committing to stacked repositories is currently
1279
# broken, so repositories with fallbacks refuse to hand out a commit
1281
repo_basis = self.make_repository('basis')
1282
branch = self.make_branch('local')
1283
repo_local = branch.repository
1285
repo_local.add_fallback_repository(repo_basis)
1286
except errors.UnstackableRepositoryFormat:
1287
raise tests.TestNotApplicable("not a stackable format.")
1288
repo_local.lock_write()
1289
self.addCleanup(repo_local.unlock)
1290
self.assertRaises(errors.BzrError, repo_local.get_commit_builder,
1291
branch, [], branch.get_config())